blob: ea31f9653e4c77fce2329fbcef353c204a2d29d2 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/flow_graph_builder.h"
#include "lib/invocation_mirror.h"
#include "vm/ast_printer.h"
#include "vm/bit_vector.h"
#include "vm/class_finalizer.h"
#include "vm/exceptions.h"
#include "vm/flags.h"
#include "vm/flow_graph.h"
#include "vm/flow_graph_compiler.h"
#include "vm/heap.h"
#include "vm/il_printer.h"
#include "vm/intermediate_language.h"
#include "vm/isolate.h"
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/os.h"
#include "vm/parser.h"
#include "vm/report.h"
#include "vm/resolver.h"
#include "vm/scopes.h"
#include "vm/stack_frame.h"
#include "vm/stub_code.h"
#include "vm/symbols.h"
#include "vm/token.h"
#include "vm/zone.h"
namespace dart {
DEFINE_FLAG(bool, eliminate_type_checks, true,
"Eliminate type checks when allowed by static type analysis.");
DEFINE_FLAG(bool, print_ast, false, "Print abstract syntax tree.");
DEFINE_FLAG(bool, print_scopes, false, "Print scopes of local variables.");
DEFINE_FLAG(bool, support_debugger, true, "Emit code needed for debugging");
DEFINE_FLAG(bool, trace_type_check_elimination, false,
"Trace type check elimination at compile time.");
DEFINE_FLAG(bool, precompile_collect_closures, false,
"Collect all closure functions referenced from compiled code.");
DECLARE_FLAG(int, optimization_counter_threshold);
DECLARE_FLAG(bool, profile_vm);
DECLARE_FLAG(bool, warn_on_javascript_compatibility);
DECLARE_FLAG(bool, use_field_guards);
// Quick access to the locally defined zone() method.
#define Z (zone())
// TODO(srdjan): Allow compiler to add constants as they are encountered in
// the compilation.
const double kCommonDoubleConstants[] =
{-1.0, -0.5, -0.1, 0.0, 0.1, 0.5, 1.0, 2.0, 4.0, 5.0,
10.0, 20.0, 30.0, 64.0, 255.0, NAN,
// From dart:math
2.718281828459045, 2.302585092994046, 0.6931471805599453,
1.4426950408889634, 0.4342944819032518, 3.1415926535897932,
0.7071067811865476, 1.4142135623730951};
uword FlowGraphBuilder::FindDoubleConstant(double value) {
intptr_t len = sizeof(kCommonDoubleConstants) / sizeof(double); // NOLINT
for (intptr_t i = 0; i < len; i++) {
if (Utils::DoublesBitEqual(value, kCommonDoubleConstants[i])) {
return reinterpret_cast<uword>(&kCommonDoubleConstants[i]);
}
}
return 0;
}
#define RECOGNIZE_FACTORY(test_factory_symbol, cid, fp) \
{ Symbols::k##test_factory_symbol##Id, cid, \
fp, #test_factory_symbol ", " #cid },
static struct {
intptr_t symbold_id;
intptr_t cid;
intptr_t finger_print;
const char* name;
} factory_recognizer_list[] = {
RECOGNIZED_LIST_FACTORY_LIST(RECOGNIZE_FACTORY)
{ Symbols::kIllegal, -1, -1, NULL }
};
#undef RECOGNIZE_FACTORY
intptr_t FactoryRecognizer::ResultCid(const Function& factory) {
ASSERT(factory.IsFactory());
const Class& function_class = Class::Handle(factory.Owner());
const Library& lib = Library::Handle(function_class.library());
ASSERT((lib.raw() == Library::CoreLibrary()) ||
(lib.raw() == Library::TypedDataLibrary()));
const String& factory_name = String::Handle(factory.name());
for (intptr_t i = 0;
factory_recognizer_list[i].symbold_id != Symbols::kIllegal;
i++) {
if (String::EqualsIgnoringPrivateKey(
factory_name,
Symbols::Symbol(factory_recognizer_list[i].symbold_id))) {
return factory_recognizer_list[i].cid;
}
}
return kDynamicCid;
}
// Base class for a stack of enclosing statements of interest (e.g.,
// blocks (breakable) and loops (continuable)).
class NestedStatement : public ValueObject {
public:
FlowGraphBuilder* owner() const { return owner_; }
const SourceLabel* label() const { return label_; }
NestedStatement* outer() const { return outer_; }
JoinEntryInstr* break_target() const { return break_target_; }
virtual intptr_t ContextLevel() const;
virtual void AdjustContextLevel(intptr_t context_level);
virtual JoinEntryInstr* BreakTargetFor(SourceLabel* label);
virtual JoinEntryInstr* ContinueTargetFor(SourceLabel* label);
protected:
NestedStatement(FlowGraphBuilder* owner, const SourceLabel* label)
: owner_(owner),
label_(label),
outer_(owner->nesting_stack_),
break_target_(NULL),
try_index_(owner->try_index()) {
// Push on the owner's nesting stack.
owner->nesting_stack_ = this;
}
intptr_t try_index() const { return try_index_; }
virtual ~NestedStatement() {
// Pop from the owner's nesting stack.
ASSERT(owner_->nesting_stack_ == this);
owner_->nesting_stack_ = outer_;
}
private:
FlowGraphBuilder* owner_;
const SourceLabel* label_;
NestedStatement* outer_;
JoinEntryInstr* break_target_;
const intptr_t try_index_;
};
intptr_t NestedStatement::ContextLevel() const {
// Context level is determined by the innermost nested statement having one.
return (outer() == NULL) ? 0 : outer()->ContextLevel();
}
void NestedStatement::AdjustContextLevel(intptr_t context_level) {
// There must be a NestedContextAdjustment on the nesting stack.
ASSERT(outer() != NULL);
outer()->AdjustContextLevel(context_level);
}
intptr_t FlowGraphBuilder::context_level() const {
return (nesting_stack() == NULL) ? 0 : nesting_stack()->ContextLevel();
}
JoinEntryInstr* NestedStatement::BreakTargetFor(SourceLabel* label) {
if (label != label_) return NULL;
if (break_target_ == NULL) {
break_target_ =
new(owner()->zone()) JoinEntryInstr(owner()->AllocateBlockId(),
owner()->try_index());
}
return break_target_;
}
JoinEntryInstr* NestedStatement::ContinueTargetFor(SourceLabel* label) {
return NULL;
}
// A nested statement that has its own context level.
class NestedBlock : public NestedStatement {
public:
NestedBlock(FlowGraphBuilder* owner, SequenceNode* node)
: NestedStatement(owner, node->label()), scope_(node->scope()) {}
virtual intptr_t ContextLevel() const;
private:
LocalScope* scope_;
};
intptr_t NestedBlock::ContextLevel() const {
return ((scope_ == NULL) || (scope_->num_context_variables() == 0))
? NestedStatement::ContextLevel()
: scope_->context_level();
}
// A nested statement reflecting a context level adjustment.
class NestedContextAdjustment : public NestedStatement {
public:
NestedContextAdjustment(FlowGraphBuilder* owner, intptr_t context_level)
: NestedStatement(owner, NULL), context_level_(context_level) { }
virtual intptr_t ContextLevel() const { return context_level_; }
virtual void AdjustContextLevel(intptr_t context_level) {
ASSERT(context_level <= context_level_);
context_level_ = context_level;
}
private:
intptr_t context_level_;
};
// A nested statement that can be the target of a continue as well as a
// break.
class NestedLoop : public NestedStatement {
public:
NestedLoop(FlowGraphBuilder* owner, SourceLabel* label)
: NestedStatement(owner, label), continue_target_(NULL) {
owner->IncrementLoopDepth();
}
virtual ~NestedLoop() {
owner()->DecrementLoopDepth();
}
JoinEntryInstr* continue_target() const { return continue_target_; }
virtual JoinEntryInstr* ContinueTargetFor(SourceLabel* label);
private:
JoinEntryInstr* continue_target_;
};
JoinEntryInstr* NestedLoop::ContinueTargetFor(SourceLabel* label) {
if (label != this->label()) return NULL;
if (continue_target_ == NULL) {
continue_target_ =
new(owner()->zone()) JoinEntryInstr(owner()->AllocateBlockId(),
try_index());
}
return continue_target_;
}
// A nested switch which can be the target of a break if labeled, and whose
// cases can be the targets of continues.
class NestedSwitch : public NestedStatement {
public:
NestedSwitch(FlowGraphBuilder* owner, SwitchNode* node);
virtual JoinEntryInstr* ContinueTargetFor(SourceLabel* label);
private:
GrowableArray<SourceLabel*> case_labels_;
GrowableArray<JoinEntryInstr*> case_targets_;
};
NestedSwitch::NestedSwitch(FlowGraphBuilder* owner, SwitchNode* node)
: NestedStatement(owner, node->label()),
case_labels_(node->body()->length()),
case_targets_(node->body()->length()) {
SequenceNode* body = node->body();
for (intptr_t i = 0; i < body->length(); ++i) {
CaseNode* case_node = body->NodeAt(i)->AsCaseNode();
if (case_node != NULL) {
case_labels_.Add(case_node->label());
case_targets_.Add(NULL);
}
}
}
JoinEntryInstr* NestedSwitch::ContinueTargetFor(SourceLabel* label) {
// Allocate a join for a case clause that matches the label. This block
// is not necessarily targeted by a continue, but we always use a join in
// the graph anyway.
for (intptr_t i = 0; i < case_labels_.length(); ++i) {
if (label != case_labels_[i]) continue;
if (case_targets_[i] == NULL) {
case_targets_[i] =
new(owner()->zone()) JoinEntryInstr(owner()->AllocateBlockId(),
try_index());
}
return case_targets_[i];
}
return NULL;
}
FlowGraphBuilder::FlowGraphBuilder(
const ParsedFunction& parsed_function,
const ZoneGrowableArray<const ICData*>& ic_data_array,
InlineExitCollector* exit_collector,
intptr_t osr_id) :
parsed_function_(parsed_function),
ic_data_array_(ic_data_array),
num_copied_params_(parsed_function.num_copied_params()),
// All parameters are copied if any parameter is.
num_non_copied_params_((num_copied_params_ == 0)
? parsed_function.function().num_fixed_parameters()
: 0),
num_stack_locals_(parsed_function.num_stack_locals()),
exit_collector_(exit_collector),
last_used_block_id_(0), // 0 is used for the graph entry.
try_index_(CatchClauseNode::kInvalidTryIndex),
catch_try_index_(CatchClauseNode::kInvalidTryIndex),
loop_depth_(0),
graph_entry_(NULL),
temp_count_(0),
args_pushed_(0),
nesting_stack_(NULL),
osr_id_(osr_id),
jump_count_(0),
await_joins_(new(Z) ZoneGrowableArray<JoinEntryInstr*>()) { }
void FlowGraphBuilder::AddCatchEntry(CatchBlockEntryInstr* entry) {
graph_entry_->AddCatchEntry(entry);
}
void InlineExitCollector::PrepareGraphs(FlowGraph* callee_graph) {
ASSERT(callee_graph->graph_entry()->SuccessorCount() == 1);
ASSERT(callee_graph->max_block_id() > caller_graph_->max_block_id());
ASSERT(callee_graph->max_virtual_register_number() >
caller_graph_->max_virtual_register_number());
// Adjust the caller's maximum block id and current SSA temp index.
caller_graph_->set_max_block_id(callee_graph->max_block_id());
caller_graph_->set_current_ssa_temp_index(
callee_graph->max_virtual_register_number());
// Attach the outer environment on each instruction in the callee graph.
ASSERT(call_->env() != NULL);
// Scale the edge weights by the call count for the inlined function.
double scale_factor = static_cast<double>(call_->CallCount())
/ static_cast<double>(caller_graph_->graph_entry()->entry_count());
for (BlockIterator block_it = callee_graph->postorder_iterator();
!block_it.Done();
block_it.Advance()) {
BlockEntryInstr* block = block_it.Current();
if (block->IsTargetEntry()) {
block->AsTargetEntry()->adjust_edge_weight(scale_factor);
}
Instruction* instr = block;
if (block->env() != NULL) {
call_->env()->DeepCopyToOuter(callee_graph->zone(), block);
}
for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
instr = it.Current();
// TODO(zerny): Avoid creating unnecessary environments. Note that some
// optimizations need deoptimization info for non-deoptable instructions,
// eg, LICM on GOTOs.
if (instr->env() != NULL) {
call_->env()->DeepCopyToOuter(callee_graph->zone(), instr);
}
}
if (instr->IsGoto()) {
instr->AsGoto()->adjust_edge_weight(scale_factor);
}
}
}
void InlineExitCollector::AddExit(ReturnInstr* exit) {
Data data = { NULL, exit };
exits_.Add(data);
}
void InlineExitCollector::Union(const InlineExitCollector* other) {
// It doesn't make sense to combine different calls or calls from
// different graphs.
ASSERT(caller_graph_ == other->caller_graph_);
ASSERT(call_ == other->call_);
exits_.AddArray(other->exits_);
}
int InlineExitCollector::LowestBlockIdFirst(const Data* a, const Data* b) {
return (a->exit_block->block_id() - b->exit_block->block_id());
}
void InlineExitCollector::SortExits() {
// Assign block entries here because we did not necessarily know them when
// the return exit was added to the array.
for (int i = 0; i < exits_.length(); ++i) {
exits_[i].exit_block = exits_[i].exit_return->GetBlock();
}
exits_.Sort(LowestBlockIdFirst);
}
Definition* InlineExitCollector::JoinReturns(BlockEntryInstr** exit_block,
Instruction** last_instruction,
intptr_t try_index) {
// First sort the list of exits by block id (caching return instruction
// block entries as a side effect).
SortExits();
intptr_t num_exits = exits_.length();
if (num_exits == 1) {
ReturnAt(0)->UnuseAllInputs();
*exit_block = ExitBlockAt(0);
*last_instruction = LastInstructionAt(0);
return call_->HasUses() ? ValueAt(0)->definition() : NULL;
} else {
ASSERT(num_exits > 1);
// Create a join of the returns.
intptr_t join_id = caller_graph_->max_block_id() + 1;
caller_graph_->set_max_block_id(join_id);
JoinEntryInstr* join =
new(Z) JoinEntryInstr(join_id, try_index);
// The dominator set of the join is the intersection of the dominator
// sets of all the predecessors. If we keep the dominator sets ordered
// by height in the dominator tree, we can also get the immediate
// dominator of the join node from the intersection.
//
// block_dominators is the dominator set for each block, ordered from
// the immediate dominator to the root of the dominator tree. This is
// the order we collect them in (adding at the end).
//
// join_dominators is the join's dominators ordered from the root of the
// dominator tree to the immediate dominator. This order supports
// removing during intersection by truncating the list.
GrowableArray<BlockEntryInstr*> block_dominators;
GrowableArray<BlockEntryInstr*> join_dominators;
for (intptr_t i = 0; i < num_exits; ++i) {
// Add the control-flow edge.
GotoInstr* goto_instr = new(Z) GotoInstr(join);
goto_instr->InheritDeoptTarget(zone(), ReturnAt(i));
LastInstructionAt(i)->LinkTo(goto_instr);
ExitBlockAt(i)->set_last_instruction(LastInstructionAt(i)->next());
join->predecessors_.Add(ExitBlockAt(i));
// Collect the block's dominators.
block_dominators.Clear();
BlockEntryInstr* dominator = ExitBlockAt(i)->dominator();
while (dominator != NULL) {
block_dominators.Add(dominator);
dominator = dominator->dominator();
}
if (i == 0) {
// The initial dominator set is the first predecessor's dominator
// set. Reverse it.
for (intptr_t j = block_dominators.length() - 1; j >= 0; --j) {
join_dominators.Add(block_dominators[j]);
}
} else {
// Intersect the block's dominators with the join's dominators so far.
intptr_t last = block_dominators.length() - 1;
for (intptr_t j = 0; j < join_dominators.length(); ++j) {
intptr_t k = last - j; // Corresponding index in block_dominators.
if ((k < 0) || (join_dominators[j] != block_dominators[k])) {
// We either exhausted the dominators for this block before
// exhausting the current intersection, or else we found a block
// on the path from the root of the tree that is not in common.
// I.e., there cannot be an empty set of dominators.
ASSERT(j > 0);
join_dominators.TruncateTo(j);
break;
}
}
}
}
// The immediate dominator of the join is the last one in the ordered
// intersection.
join_dominators.Last()->AddDominatedBlock(join);
*exit_block = join;
*last_instruction = join;
// If the call has uses, create a phi of the returns.
if (call_->HasUses()) {
// Add a phi of the return values.
PhiInstr* phi = new(Z) PhiInstr(join, num_exits);
caller_graph_->AllocateSSAIndexes(phi);
phi->mark_alive();
for (intptr_t i = 0; i < num_exits; ++i) {
ReturnAt(i)->RemoveEnvironment();
phi->SetInputAt(i, ValueAt(i));
}
join->InsertPhi(phi);
join->InheritDeoptTargetAfter(caller_graph_, call_, phi);
return phi;
} else {
// In the case that the result is unused, remove the return value uses
// from their definition's use list.
for (intptr_t i = 0; i < num_exits; ++i) {
ReturnAt(i)->UnuseAllInputs();
}
join->InheritDeoptTargetAfter(caller_graph_, call_, NULL);
return NULL;
}
}
}
void InlineExitCollector::ReplaceCall(TargetEntryInstr* callee_entry) {
ASSERT(call_->previous() != NULL);
ASSERT(call_->next() != NULL);
BlockEntryInstr* call_block = call_->GetBlock();
// Insert the callee graph into the caller graph.
BlockEntryInstr* callee_exit = NULL;
Instruction* callee_last_instruction = NULL;
if (exits_.length() == 0) {
// Handle the case when there are no normal return exits from the callee
// (i.e. the callee unconditionally throws) by inserting an artificial
// branch (true === true).
// The true successor is the inlined body, the false successor
// goes to the rest of the caller graph. It is removed as unreachable code
// by the constant propagation.
TargetEntryInstr* false_block =
new(Z) TargetEntryInstr(caller_graph_->allocate_block_id(),
call_block->try_index());
false_block->InheritDeoptTargetAfter(caller_graph_, call_, NULL);
false_block->LinkTo(call_->next());
call_block->ReplaceAsPredecessorWith(false_block);
ConstantInstr* true_const = caller_graph_->GetConstant(Bool::True());
BranchInstr* branch =
new(Z) BranchInstr(
new(Z) StrictCompareInstr(call_block->start_pos(),
Token::kEQ_STRICT,
new(Z) Value(true_const),
new(Z) Value(true_const),
false)); // No number check.
branch->InheritDeoptTarget(zone(), call_);
*branch->true_successor_address() = callee_entry;
*branch->false_successor_address() = false_block;
call_->previous()->AppendInstruction(branch);
call_block->set_last_instruction(branch);
// Update dominator tree.
call_block->AddDominatedBlock(callee_entry);
call_block->AddDominatedBlock(false_block);
} else {
Definition* callee_result = JoinReturns(&callee_exit,
&callee_last_instruction,
call_block->try_index());
if (callee_result != NULL) {
call_->ReplaceUsesWith(callee_result);
}
if (callee_last_instruction == callee_entry) {
// There are no instructions in the inlined function (e.g., it might be
// a return of a parameter or a return of a constant defined in the
// initial definitions).
call_->previous()->LinkTo(call_->next());
} else {
call_->previous()->LinkTo(callee_entry->next());
callee_last_instruction->LinkTo(call_->next());
}
if (callee_exit != callee_entry) {
// In case of control flow, locally update the predecessors, phis and
// dominator tree.
//
// Pictorially, the graph structure is:
//
// Bc : call_block Bi : callee_entry
// before_call inlined_head
// call ... other blocks ...
// after_call Be : callee_exit
// inlined_foot
// And becomes:
//
// Bc : call_block
// before_call
// inlined_head
// ... other blocks ...
// Be : callee_exit
// inlined_foot
// after_call
//
// For successors of 'after_call', the call block (Bc) is replaced as a
// predecessor by the callee exit (Be).
call_block->ReplaceAsPredecessorWith(callee_exit);
// For successors of 'inlined_head', the callee entry (Bi) is replaced
// as a predecessor by the call block (Bc).
callee_entry->ReplaceAsPredecessorWith(call_block);
// The callee exit is now the immediate dominator of blocks whose
// immediate dominator was the call block.
ASSERT(callee_exit->dominated_blocks().is_empty());
for (intptr_t i = 0; i < call_block->dominated_blocks().length(); ++i) {
BlockEntryInstr* block = call_block->dominated_blocks()[i];
callee_exit->AddDominatedBlock(block);
}
// The call block is now the immediate dominator of blocks whose
// immediate dominator was the callee entry.
call_block->ClearDominatedBlocks();
for (intptr_t i = 0; i < callee_entry->dominated_blocks().length(); ++i) {
BlockEntryInstr* block = callee_entry->dominated_blocks()[i];
call_block->AddDominatedBlock(block);
}
}
// Callee entry in not in the graph anymore. Remove it from use lists.
callee_entry->UnuseAllInputs();
}
// Neither call nor the graph entry (if present) are in the
// graph at this point. Remove them from use lists.
if (callee_entry->PredecessorCount() > 0) {
callee_entry->PredecessorAt(0)->AsGraphEntry()->UnuseAllInputs();
}
call_->UnuseAllInputs();
}
void EffectGraphVisitor::Append(const EffectGraphVisitor& other_fragment) {
ASSERT(is_open());
if (other_fragment.is_empty()) return;
if (is_empty()) {
entry_ = other_fragment.entry();
} else {
exit()->LinkTo(other_fragment.entry());
}
exit_ = other_fragment.exit();
}
Value* EffectGraphVisitor::Bind(Definition* definition) {
ASSERT(is_open());
owner()->DeallocateTemps(definition->InputCount());
owner()->add_args_pushed(-definition->ArgumentCount());
definition->set_temp_index(owner()->AllocateTemp());
if (is_empty()) {
entry_ = definition;
} else {
exit()->LinkTo(definition);
}
exit_ = definition;
return new(Z) Value(definition);
}
void EffectGraphVisitor::Do(Definition* definition) {
ASSERT(is_open());
owner()->DeallocateTemps(definition->InputCount());
owner()->add_args_pushed(-definition->ArgumentCount());
if (is_empty()) {
entry_ = definition;
} else {
exit()->LinkTo(definition);
}
exit_ = definition;
}
void EffectGraphVisitor::AddInstruction(Instruction* instruction) {
ASSERT(is_open());
ASSERT(instruction->IsPushArgument() || !instruction->IsDefinition());
ASSERT(!instruction->IsBlockEntry());
owner()->DeallocateTemps(instruction->InputCount());
owner()->add_args_pushed(-instruction->ArgumentCount());
if (is_empty()) {
entry_ = exit_ = instruction;
} else {
exit()->LinkTo(instruction);
exit_ = instruction;
}
}
void EffectGraphVisitor::AddReturnExit(intptr_t token_pos, Value* value) {
ASSERT(is_open());
ReturnInstr* return_instr = new(Z) ReturnInstr(token_pos, value);
AddInstruction(return_instr);
InlineExitCollector* exit_collector = owner()->exit_collector();
if (exit_collector != NULL) {
exit_collector->AddExit(return_instr);
}
CloseFragment();
}
void EffectGraphVisitor::Goto(JoinEntryInstr* join) {
ASSERT(is_open());
if (is_empty()) {
entry_ = new(Z) GotoInstr(join);
} else {
exit()->Goto(join);
}
CloseFragment();
}
// Appends a graph fragment to a block entry instruction. Returns the entry
// instruction if the fragment was empty or else the exit of the fragment if
// it was non-empty (so NULL if the fragment is closed).
//
// Note that the fragment is no longer a valid fragment after calling this
// function -- the fragment is closed at its entry because the entry has a
// predecessor in the graph.
static Instruction* AppendFragment(BlockEntryInstr* entry,
const EffectGraphVisitor& fragment) {
if (fragment.is_empty()) return entry;
entry->LinkTo(fragment.entry());
return fragment.exit();
}
void EffectGraphVisitor::Join(const TestGraphVisitor& test_fragment,
const EffectGraphVisitor& true_fragment,
const EffectGraphVisitor& false_fragment) {
// We have: a test graph fragment with zero, one, or two available exits;
// and a pair of effect graph fragments with zero or one available exits.
// We want to append the branch and (if necessary) a join node to this
// graph fragment.
ASSERT(is_open());
// 1. Connect the test to this graph.
Append(test_fragment);
// 2. Connect the true and false bodies to the test and record their exits
// (if any).
BlockEntryInstr* true_entry = test_fragment.CreateTrueSuccessor();
Instruction* true_exit = AppendFragment(true_entry, true_fragment);
BlockEntryInstr* false_entry = test_fragment.CreateFalseSuccessor();
Instruction* false_exit = AppendFragment(false_entry, false_fragment);
// 3. Add a join or select one (or neither) of the arms as exit.
if (true_exit == NULL) {
exit_ = false_exit; // May be NULL.
} else if (false_exit == NULL) {
exit_ = true_exit;
} else {
JoinEntryInstr* join =
new(Z) JoinEntryInstr(owner()->AllocateBlockId(), owner()->try_index());
true_exit->Goto(join);
false_exit->Goto(join);
exit_ = join;
}
}
void EffectGraphVisitor::TieLoop(
intptr_t token_pos,
const TestGraphVisitor& test_fragment,
const EffectGraphVisitor& body_fragment,
const EffectGraphVisitor& test_preamble_fragment) {
// We have: a test graph fragment with zero, one, or two available exits;
// and an effect graph fragment with zero or one available exits. We want
// to append the 'while loop' consisting of the test graph fragment as
// condition and the effect graph fragment as body.
ASSERT(is_open());
// 1. Connect the body to the test if it is reachable, and if so record
// its exit (if any).
BlockEntryInstr* body_entry = test_fragment.CreateTrueSuccessor();
Instruction* body_exit = AppendFragment(body_entry, body_fragment);
// 2. Connect the test to this graph, including the body if reachable and
// using a fresh join node if the body is reachable and has an open exit.
if (body_exit == NULL) {
Append(test_preamble_fragment);
Append(test_fragment);
} else {
JoinEntryInstr* join =
new(Z) JoinEntryInstr(owner()->AllocateBlockId(), owner()->try_index());
CheckStackOverflowInstr* check =
new(Z) CheckStackOverflowInstr(token_pos, owner()->loop_depth());
join->LinkTo(check);
if (!test_preamble_fragment.is_empty()) {
check->LinkTo(test_preamble_fragment.entry());
test_preamble_fragment.exit()->LinkTo(test_fragment.entry());
} else {
check->LinkTo(test_fragment.entry());
}
Goto(join);
body_exit->Goto(join);
}
// 3. Set the exit to the graph to be the false successor of the test, a
// fresh target node
exit_ = test_fragment.CreateFalseSuccessor();
}
PushArgumentInstr* EffectGraphVisitor::PushArgument(Value* value) {
owner_->add_args_pushed(1);
PushArgumentInstr* result = new(Z) PushArgumentInstr(value);
AddInstruction(result);
return result;
}
Definition* EffectGraphVisitor::BuildStoreTemp(const LocalVariable& local,
Value* value) {
ASSERT(!local.is_captured());
return new(Z) StoreLocalInstr(local, value);
}
Definition* EffectGraphVisitor::BuildStoreExprTemp(Value* value) {
return BuildStoreTemp(*owner()->parsed_function().expression_temp_var(),
value);
}
Definition* EffectGraphVisitor::BuildLoadExprTemp() {
return BuildLoadLocal(*owner()->parsed_function().expression_temp_var());
}
Definition* EffectGraphVisitor::BuildStoreLocal(const LocalVariable& local,
Value* value) {
if (local.is_captured()) {
LocalVariable* tmp_var = EnterTempLocalScope(value);
intptr_t delta =
owner()->context_level() - local.owner()->context_level();
ASSERT(delta >= 0);
Value* context = Bind(BuildCurrentContext());
while (delta-- > 0) {
context = Bind(new(Z) LoadFieldInstr(
context, Context::parent_offset(), Type::ZoneHandle(Z, Type::null()),
Scanner::kNoSourcePos));
}
Value* tmp_val = Bind(new(Z) LoadLocalInstr(*tmp_var));
StoreInstanceFieldInstr* store =
new(Z) StoreInstanceFieldInstr(Context::variable_offset(local.index()),
context,
tmp_val,
kEmitStoreBarrier,
Scanner::kNoSourcePos);
Do(store);
return ExitTempLocalScope(tmp_var);
} else {
return new(Z) StoreLocalInstr(local, value);
}
}
Definition* EffectGraphVisitor::BuildLoadLocal(const LocalVariable& local) {
if (local.IsConst()) {
return new(Z) ConstantInstr(*local.ConstValue());
} else if (local.is_captured()) {
intptr_t delta =
owner()->context_level() - local.owner()->context_level();
ASSERT(delta >= 0);
Value* context = Bind(BuildCurrentContext());
while (delta-- > 0) {
context = Bind(new(Z) LoadFieldInstr(
context, Context::parent_offset(), Type::ZoneHandle(Z, Type::null()),
Scanner::kNoSourcePos));
}
return new(Z) LoadFieldInstr(context,
Context::variable_offset(local.index()),
local.type(),
Scanner::kNoSourcePos);
} else {
return new(Z) LoadLocalInstr(local);
}
}
// Stores current context into the 'variable'
void EffectGraphVisitor::BuildSaveContext(const LocalVariable& variable) {
Value* context = Bind(BuildCurrentContext());
Do(BuildStoreLocal(variable, context));
}
// Loads context saved in 'context_variable' into the current context.
void EffectGraphVisitor::BuildRestoreContext(const LocalVariable& variable) {
Value* load_saved_context = Bind(BuildLoadLocal(variable));
Do(BuildStoreContext(load_saved_context));
}
Definition* EffectGraphVisitor::BuildStoreContext(Value* value) {
return new(Z) StoreLocalInstr(
*owner()->parsed_function().current_context_var(), value);
}
Definition* EffectGraphVisitor::BuildCurrentContext() {
return new(Z) LoadLocalInstr(
*owner()->parsed_function().current_context_var());
}
void TestGraphVisitor::ConnectBranchesTo(
const GrowableArray<TargetEntryInstr**>& branches,
JoinEntryInstr* join) const {
ASSERT(!branches.is_empty());
for (intptr_t i = 0; i < branches.length(); i++) {
TargetEntryInstr* target =
new(Z) TargetEntryInstr(owner()->AllocateBlockId(),
owner()->try_index());
*(branches[i]) = target;
target->Goto(join);
}
}
void TestGraphVisitor::IfTrueGoto(JoinEntryInstr* join) const {
ConnectBranchesTo(true_successor_addresses_, join);
}
void TestGraphVisitor::IfFalseGoto(JoinEntryInstr* join) const {
ConnectBranchesTo(false_successor_addresses_, join);
}
BlockEntryInstr* TestGraphVisitor::CreateSuccessorFor(
const GrowableArray<TargetEntryInstr**>& branches) const {
ASSERT(!branches.is_empty());
if (branches.length() == 1) {
TargetEntryInstr* target =
new(Z) TargetEntryInstr(owner()->AllocateBlockId(),
owner()->try_index());
*(branches[0]) = target;
return target;
}
JoinEntryInstr* join =
new(Z) JoinEntryInstr(owner()->AllocateBlockId(), owner()->try_index());
ConnectBranchesTo(branches, join);
return join;
}
BlockEntryInstr* TestGraphVisitor::CreateTrueSuccessor() const {
return CreateSuccessorFor(true_successor_addresses_);
}
BlockEntryInstr* TestGraphVisitor::CreateFalseSuccessor() const {
return CreateSuccessorFor(false_successor_addresses_);
}
void TestGraphVisitor::ReturnValue(Value* value) {
Isolate* isolate = Isolate::Current();
if (isolate->flags().type_checks() ||
isolate->flags().asserts()) {
value = Bind(new(Z) AssertBooleanInstr(condition_token_pos(), value));
}
Value* constant_true = Bind(new(Z) ConstantInstr(Bool::True()));
StrictCompareInstr* comp =
new(Z) StrictCompareInstr(condition_token_pos(),
Token::kEQ_STRICT,
value,
constant_true,
false); // No number check.
BranchInstr* branch = new(Z) BranchInstr(comp);
AddInstruction(branch);
CloseFragment();
true_successor_addresses_.Add(branch->true_successor_address());
false_successor_addresses_.Add(branch->false_successor_address());
}
void TestGraphVisitor::MergeBranchWithComparison(ComparisonInstr* comp) {
BranchInstr* branch;
if (Token::IsStrictEqualityOperator(comp->kind())) {
ASSERT(comp->IsStrictCompare());
branch = new(Z) BranchInstr(comp);
} else if (Token::IsEqualityOperator(comp->kind()) &&
(comp->left()->BindsToConstantNull() ||
comp->right()->BindsToConstantNull())) {
branch = new(Z) BranchInstr(new(Z) StrictCompareInstr(
comp->token_pos(),
(comp->kind() == Token::kEQ) ? Token::kEQ_STRICT : Token::kNE_STRICT,
comp->left(),
comp->right(),
false)); // No number check.
} else {
branch = new(Z) BranchInstr(comp);
branch->set_is_checked(Isolate::Current()->flags().type_checks());
}
AddInstruction(branch);
CloseFragment();
true_successor_addresses_.Add(branch->true_successor_address());
false_successor_addresses_.Add(branch->false_successor_address());
}
void TestGraphVisitor::MergeBranchWithNegate(BooleanNegateInstr* neg) {
ASSERT(!Isolate::Current()->flags().type_checks());
Value* constant_true = Bind(new(Z) ConstantInstr(Bool::True()));
StrictCompareInstr* comp =
new(Z) StrictCompareInstr(condition_token_pos(),
Token::kNE_STRICT,
neg->value(),
constant_true,
false); // No number check.
BranchInstr* branch = new(Z) BranchInstr(comp);
AddInstruction(branch);
CloseFragment();
true_successor_addresses_.Add(branch->true_successor_address());
false_successor_addresses_.Add(branch->false_successor_address());
}
void TestGraphVisitor::ReturnDefinition(Definition* definition) {
ComparisonInstr* comp = definition->AsComparison();
if (comp != NULL) {
MergeBranchWithComparison(comp);
return;
}
if (!Isolate::Current()->flags().type_checks()) {
BooleanNegateInstr* neg = definition->AsBooleanNegate();
if (neg != NULL) {
MergeBranchWithNegate(neg);
return;
}
}
ReturnValue(Bind(definition));
}
// Special handling for AND/OR.
void TestGraphVisitor::VisitBinaryOpNode(BinaryOpNode* node) {
// Operators "&&" and "||" cannot be overloaded therefore do not call
// operator.
if ((node->kind() == Token::kAND) || (node->kind() == Token::kOR)) {
TestGraphVisitor for_left(owner(), node->left()->token_pos());
node->left()->Visit(&for_left);
TestGraphVisitor for_right(owner(), node->right()->token_pos());
node->right()->Visit(&for_right);
Append(for_left);
if (node->kind() == Token::kAND) {
AppendFragment(for_left.CreateTrueSuccessor(), for_right);
true_successor_addresses_.AddArray(for_right.true_successor_addresses_);
false_successor_addresses_.AddArray(for_left.false_successor_addresses_);
false_successor_addresses_.AddArray(for_right.false_successor_addresses_);
} else {
ASSERT(node->kind() == Token::kOR);
AppendFragment(for_left.CreateFalseSuccessor(), for_right);
false_successor_addresses_.AddArray(for_right.false_successor_addresses_);
true_successor_addresses_.AddArray(for_left.true_successor_addresses_);
true_successor_addresses_.AddArray(for_right.true_successor_addresses_);
}
CloseFragment();
return;
}
ValueGraphVisitor::VisitBinaryOpNode(node);
}
void EffectGraphVisitor::Bailout(const char* reason) const {
owner()->Bailout(reason);
}
void EffectGraphVisitor::InlineBailout(const char* reason) const {
owner()->function().set_is_inlinable(false);
if (owner()->IsInlining()) owner()->Bailout(reason);
}
// <Statement> ::= Return { value: <Expression>
// inlined_finally_list: <InlinedFinally>* }
void EffectGraphVisitor::VisitReturnNode(ReturnNode* node) {
ValueGraphVisitor for_value(owner());
node->value()->Visit(&for_value);
Append(for_value);
Value* return_value = for_value.value();
// Call to stub that checks whether the debugger is in single
// step mode. This call must happen before the contexts are
// unchained so that captured variables can be inspected.
// No debugger check is done in native functions or for return
// statements for which there is no associated source position.
const Function& function = owner()->function();
if (FLAG_support_debugger &&
(node->token_pos() != Scanner::kNoSourcePos) && !function.is_native()) {
AddInstruction(new(Z) DebugStepCheckInstr(node->token_pos(),
RawPcDescriptors::kRuntimeCall));
}
NestedContextAdjustment context_adjustment(owner(), owner()->context_level());
if (node->inlined_finally_list_length() > 0) {
LocalVariable* temp = owner()->parsed_function().finally_return_temp_var();
ASSERT(temp != NULL);
Do(BuildStoreLocal(*temp, return_value));
for (intptr_t i = 0; i < node->inlined_finally_list_length(); i++) {
InlineBailout("EffectGraphVisitor::VisitReturnNode (exception)");
EffectGraphVisitor for_effect(owner());
node->InlinedFinallyNodeAt(i)->Visit(&for_effect);
Append(for_effect);
if (!is_open()) {
return;
}
}
return_value = Bind(BuildLoadLocal(*temp));
}
if (Isolate::Current()->flags().type_checks()) {
const bool is_implicit_dynamic_getter =
(!function.is_static() &&
((function.kind() == RawFunction::kImplicitGetter) ||
(function.kind() == RawFunction::kImplicitStaticFinalGetter)));
// Implicit getters do not need a type check at return, unless they compute
// the initial value of a static field.
// The body of a constructor cannot modify the type of the
// constructed instance, which is passed in as an implicit parameter.
// However, factories may create an instance of the wrong type.
if (!is_implicit_dynamic_getter && !function.IsGenerativeConstructor()) {
const AbstractType& dst_type =
AbstractType::ZoneHandle(Z, function.result_type());
return_value = BuildAssignableValue(node->value()->token_pos(),
return_value,
dst_type,
Symbols::FunctionResult());
}
}
// Async functions contain two types of return statements:
// 1) Returns that should complete the completer once all finally blocks have
// been inlined (call: :async_completer.complete(return_value)). These
// returns end up returning null in the end.
// 2) "Continuation" returns that should not complete the completer but return
// the value.
//
// We distinguish those kinds of nodes via is_regular_return().
//
if (function.IsAsyncClosure() &&
(node->return_type() == ReturnNode::kRegular)) {
// Temporary store the computed return value.
Do(BuildStoreExprTemp(return_value));
LocalVariable* rcv_var =
node->scope()->LookupVariable(Symbols::AsyncCompleter(), false);
ASSERT(rcv_var != NULL && rcv_var->is_captured());
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(2);
Value* rcv_value = Bind(BuildLoadLocal(*rcv_var));
arguments->Add(PushArgument(rcv_value));
Value* returned_value = Bind(BuildLoadExprTemp());
arguments->Add(PushArgument(returned_value));
InstanceCallInstr* call = new(Z) InstanceCallInstr(
Scanner::kNoSourcePos,
Symbols::CompleterComplete(),
Token::kILLEGAL,
arguments,
Object::null_array(),
1,
owner()->ic_data_array());
Do(call);
// Rebind the return value for the actual return call to be null.
return_value = BuildNullValue();
}
intptr_t current_context_level = owner()->context_level();
ASSERT(current_context_level >= 0);
if (HasContextScope()) {
UnchainContexts(current_context_level);
}
AddReturnExit(node->token_pos(), return_value);
if ((function.IsAsyncClosure() ||
function.IsSyncGenClosure() ||
function.IsAsyncGenClosure()) &&
(node->return_type() == ReturnNode::kContinuationTarget)) {
JoinEntryInstr* const join = new(Z) JoinEntryInstr(
owner()->AllocateBlockId(), owner()->try_index());
owner()->await_joins()->Add(join);
exit_ = join;
}
}
// <Expression> ::= Literal { literal: Instance }
void EffectGraphVisitor::VisitLiteralNode(LiteralNode* node) {
ReturnDefinition(new(Z) ConstantInstr(node->literal()));
}
// Type nodes are used when a type is referenced as a literal. Type nodes
// can also be used for the right-hand side of instanceof comparisons,
// but they are handled specially in that context, not here.
void EffectGraphVisitor::VisitTypeNode(TypeNode* node) {
return;
}
void ValueGraphVisitor::VisitTypeNode(TypeNode* node) {
const AbstractType& type = node->type();
// Type may be malbounded, but not malformed.
ASSERT(type.IsFinalized() && !type.IsMalformed());
if (type.IsInstantiated()) {
ReturnDefinition(new(Z) ConstantInstr(type));
} else {
const Class& instantiator_class = Class::ZoneHandle(
Z, owner()->function().Owner());
Value* instantiator_value = BuildInstantiatorTypeArguments(
node->token_pos(), instantiator_class, NULL);
ReturnDefinition(new(Z) InstantiateTypeInstr(
node->token_pos(), type, instantiator_class, instantiator_value));
}
}
// Returns true if the type check can be skipped, for example, if the
// destination type is dynamic or if the compile type of the value is a subtype
// of the destination type.
bool EffectGraphVisitor::CanSkipTypeCheck(intptr_t token_pos,
Value* value,
const AbstractType& dst_type,
const String& dst_name) {
ASSERT(!dst_type.IsNull());
ASSERT(dst_type.IsFinalized());
// If the destination type is malformed or malbounded, a dynamic type error
// must be thrown at run time.
if (dst_type.IsMalformedOrMalbounded()) {
return false;
}
// Any type is more specific than the dynamic type and than the Object type.
if (dst_type.IsDynamicType() || dst_type.IsObjectType()) {
return true;
}
// Do not perform type check elimination if this optimization is turned off.
if (!FLAG_eliminate_type_checks) {
return false;
}
// If nothing is known about the value, as is the case for passed-in
// parameters, and since dst_type is not one of the tested cases above, then
// the type test cannot be eliminated.
if (value == NULL) {
return false;
}
const bool eliminated = value->Type()->IsAssignableTo(dst_type);
if (FLAG_trace_type_check_elimination) {
FlowGraphPrinter::PrintTypeCheck(owner()->parsed_function(),
token_pos,
value,
dst_type,
dst_name,
eliminated);
}
return eliminated;
}
// <Expression> :: Assignable { expr: <Expression>
// type: AbstractType
// dst_name: String }
void EffectGraphVisitor::VisitAssignableNode(AssignableNode* node) {
ValueGraphVisitor for_value(owner());
node->expr()->Visit(&for_value);
Append(for_value);
Definition* checked_value;
if (CanSkipTypeCheck(node->expr()->token_pos(),
for_value.value(),
node->type(),
node->dst_name())) {
// Drop the value and 0 additional temporaries.
checked_value = new(Z) DropTempsInstr(0, for_value.value());
} else {
checked_value = BuildAssertAssignable(node->expr()->token_pos(),
for_value.value(),
node->type(),
node->dst_name());
}
ReturnDefinition(checked_value);
}
void ValueGraphVisitor::VisitAssignableNode(AssignableNode* node) {
ValueGraphVisitor for_value(owner());
node->expr()->Visit(&for_value);
Append(for_value);
ReturnValue(BuildAssignableValue(node->expr()->token_pos(),
for_value.value(),
node->type(),
node->dst_name()));
}
// <Expression> :: BinaryOp { kind: Token::Kind
// left: <Expression>
// right: <Expression> }
void EffectGraphVisitor::VisitBinaryOpNode(BinaryOpNode* node) {
// Operators "&&" and "||" cannot be overloaded therefore do not call
// operator.
if ((node->kind() == Token::kAND) || (node->kind() == Token::kOR)) {
// See ValueGraphVisitor::VisitBinaryOpNode.
TestGraphVisitor for_left(owner(), node->left()->token_pos());
node->left()->Visit(&for_left);
EffectGraphVisitor empty(owner());
Isolate* isolate = Isolate::Current();
if (isolate->flags().type_checks() ||
isolate->flags().asserts()) {
ValueGraphVisitor for_right(owner());
node->right()->Visit(&for_right);
Value* right_value = for_right.value();
for_right.Do(new(Z) AssertBooleanInstr(node->right()->token_pos(),
right_value));
if (node->kind() == Token::kAND) {
Join(for_left, for_right, empty);
} else {
Join(for_left, empty, for_right);
}
} else {
EffectGraphVisitor for_right(owner());
node->right()->Visit(&for_right);
if (node->kind() == Token::kAND) {
Join(for_left, for_right, empty);
} else {
Join(for_left, empty, for_right);
}
}
return;
} else if (node->kind() == Token::kIFNULL) {
// left ?? right. This operation cannot be overloaded.
// temp = left; temp === null ? right : temp
ValueGraphVisitor for_left_value(owner());
node->left()->Visit(&for_left_value);
Append(for_left_value);
Do(BuildStoreExprTemp(for_left_value.value()));
LocalVariable* temp_var = owner()->parsed_function().expression_temp_var();
LoadLocalNode* load_temp =
new(Z) LoadLocalNode(Scanner::kNoSourcePos, temp_var);
LiteralNode* null_constant =
new(Z) LiteralNode(Scanner::kNoSourcePos, Object::null_instance());
ComparisonNode* check_is_null =
new(Z) ComparisonNode(Scanner::kNoSourcePos,
Token::kEQ_STRICT,
load_temp,
null_constant);
TestGraphVisitor for_test(owner(), Scanner::kNoSourcePos);
check_is_null->Visit(&for_test);
ValueGraphVisitor for_right_value(owner());
node->right()->Visit(&for_right_value);
for_right_value.Do(BuildStoreExprTemp(for_right_value.value()));
ValueGraphVisitor for_temp(owner());
// Nothing to do, left value is already loaded into temp.
Join(for_test, for_right_value, for_temp);
return;
}
ValueGraphVisitor for_left_value(owner());
node->left()->Visit(&for_left_value);
Append(for_left_value);
PushArgumentInstr* push_left = PushArgument(for_left_value.value());
ValueGraphVisitor for_right_value(owner());
node->right()->Visit(&for_right_value);
Append(for_right_value);
PushArgumentInstr* push_right = PushArgument(for_right_value.value());
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(2);
arguments->Add(push_left);
arguments->Add(push_right);
const String& name = String::ZoneHandle(Z, Symbols::New(node->TokenName()));
const intptr_t kNumArgsChecked = 2;
InstanceCallInstr* call = new(Z) InstanceCallInstr(node->token_pos(),
name,
node->kind(),
arguments,
Object::null_array(),
kNumArgsChecked,
owner()->ic_data_array());
ReturnDefinition(call);
}
// Special handling for AND/OR.
void ValueGraphVisitor::VisitBinaryOpNode(BinaryOpNode* node) {
// Operators "&&" and "||" cannot be overloaded therefore do not call
// operator.
if ((node->kind() == Token::kAND) || (node->kind() == Token::kOR)) {
// Implement short-circuit logic: do not evaluate right if evaluation
// of left is sufficient.
// AND: left ? right === true : false;
// OR: left ? true : right === true;
TestGraphVisitor for_test(owner(), node->left()->token_pos());
node->left()->Visit(&for_test);
ValueGraphVisitor for_right(owner());
node->right()->Visit(&for_right);
Value* right_value = for_right.value();
Isolate* isolate = Isolate::Current();
if (isolate->flags().type_checks() ||
isolate->flags().asserts()) {
right_value =
for_right.Bind(new(Z) AssertBooleanInstr(node->right()->token_pos(),
right_value));
}
Value* constant_true = for_right.Bind(new(Z) ConstantInstr(Bool::True()));
Value* compare =
for_right.Bind(new(Z) StrictCompareInstr(node->token_pos(),
Token::kEQ_STRICT,
right_value,
constant_true,
false)); // No number check.
for_right.Do(BuildStoreExprTemp(compare));
if (node->kind() == Token::kAND) {
ValueGraphVisitor for_false(owner());
Value* constant_false =
for_false.Bind(new(Z) ConstantInstr(Bool::False()));
for_false.Do(BuildStoreExprTemp(constant_false));
Join(for_test, for_right, for_false);
} else {
ASSERT(node->kind() == Token::kOR);
ValueGraphVisitor for_true(owner());
Value* constant_true = for_true.Bind(new(Z) ConstantInstr(Bool::True()));
for_true.Do(BuildStoreExprTemp(constant_true));
Join(for_test, for_true, for_right);
}
ReturnDefinition(BuildLoadExprTemp());
return;
} else if (node->kind() == Token::kIFNULL) {
// left ?? right. This operation cannot be overloaded.
// temp = left; temp === null ? right : temp
ValueGraphVisitor for_left_value(owner());
node->left()->Visit(&for_left_value);
Append(for_left_value);
Do(BuildStoreExprTemp(for_left_value.value()));
LocalVariable* temp_var = owner()->parsed_function().expression_temp_var();
LoadLocalNode* load_temp =
new(Z) LoadLocalNode(Scanner::kNoSourcePos, temp_var);
LiteralNode* null_constant =
new(Z) LiteralNode(Scanner::kNoSourcePos, Object::null_instance());
ComparisonNode* check_is_null =
new(Z) ComparisonNode(Scanner::kNoSourcePos,
Token::kEQ_STRICT,
load_temp,
null_constant);
TestGraphVisitor for_test(owner(), Scanner::kNoSourcePos);
check_is_null->Visit(&for_test);
ValueGraphVisitor for_right_value(owner());
node->right()->Visit(&for_right_value);
for_right_value.Do(BuildStoreExprTemp(for_right_value.value()));
ValueGraphVisitor for_temp(owner());
// Nothing to do, left value is already loaded into temp.
Join(for_test, for_right_value, for_temp);
ReturnDefinition(BuildLoadExprTemp());
return;
}
EffectGraphVisitor::VisitBinaryOpNode(node);
}
static const String& BinaryOpAndMaskName(BinaryOpNode* node) {
if (node->kind() == Token::kSHL) {
return Library::PrivateCoreLibName(Symbols::_leftShiftWithMask32());
}
UNIMPLEMENTED();
return String::ZoneHandle(Thread::Current()->zone(), String::null());
}
// <Expression> :: BinaryOp { kind: Token::Kind
// left: <Expression>
// right: <Expression>
// mask32: constant }
void EffectGraphVisitor::VisitBinaryOpWithMask32Node(
BinaryOpWithMask32Node* node) {
ASSERT((node->kind() != Token::kAND) && (node->kind() != Token::kOR));
ValueGraphVisitor for_left_value(owner());
node->left()->Visit(&for_left_value);
Append(for_left_value);
PushArgumentInstr* push_left = PushArgument(for_left_value.value());
ValueGraphVisitor for_right_value(owner());
node->right()->Visit(&for_right_value);
Append(for_right_value);
PushArgumentInstr* push_right = PushArgument(for_right_value.value());
Value* mask_value = Bind(new(Z) ConstantInstr(
Integer::ZoneHandle(Z, Integer::New(node->mask32(), Heap::kOld))));
PushArgumentInstr* push_mask = PushArgument(mask_value);
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(3);
arguments->Add(push_left);
arguments->Add(push_right);
// Call to special method 'BinaryOpAndMaskName(node)'.
arguments->Add(push_mask);
const intptr_t kNumArgsChecked = 2;
InstanceCallInstr* call = new(Z) InstanceCallInstr(node->token_pos(),
BinaryOpAndMaskName(node),
Token::kILLEGAL,
arguments,
Object::null_array(),
kNumArgsChecked,
owner()->ic_data_array());
ReturnDefinition(call);
}
void EffectGraphVisitor::BuildTypecheckPushArguments(
intptr_t token_pos,
PushArgumentInstr** push_instantiator_result,
PushArgumentInstr** push_instantiator_type_arguments_result) {
const Class& instantiator_class = Class::Handle(
Z, owner()->function().Owner());
// Since called only when type tested against is not instantiated.
ASSERT(instantiator_class.NumTypeParameters() > 0);
Value* instantiator_type_arguments = NULL;
Value* instantiator = BuildInstantiator(instantiator_class);
if (instantiator == NULL) {
// No instantiator when inside factory.
*push_instantiator_result = PushArgument(BuildNullValue());
instantiator_type_arguments =
BuildInstantiatorTypeArguments(token_pos, instantiator_class, NULL);
} else {
instantiator = Bind(BuildStoreExprTemp(instantiator));
*push_instantiator_result = PushArgument(instantiator);
Value* loaded = Bind(BuildLoadExprTemp());
instantiator_type_arguments =
BuildInstantiatorTypeArguments(token_pos, instantiator_class, loaded);
}
*push_instantiator_type_arguments_result =
PushArgument(instantiator_type_arguments);
}
void EffectGraphVisitor::BuildTypecheckArguments(
intptr_t token_pos,
Value** instantiator_result,
Value** instantiator_type_arguments_result) {
Value* instantiator = NULL;
Value* instantiator_type_arguments = NULL;
const Class& instantiator_class = Class::Handle(
Z, owner()->function().Owner());
// Since called only when type tested against is not instantiated.
ASSERT(instantiator_class.NumTypeParameters() > 0);
instantiator = BuildInstantiator(instantiator_class);
if (instantiator == NULL) {
// No instantiator when inside factory.
instantiator = BuildNullValue();
instantiator_type_arguments =
BuildInstantiatorTypeArguments(token_pos, instantiator_class, NULL);
} else {
// Preserve instantiator.
instantiator = Bind(BuildStoreExprTemp(instantiator));
Value* loaded = Bind(BuildLoadExprTemp());
instantiator_type_arguments =
BuildInstantiatorTypeArguments(token_pos, instantiator_class, loaded);
}
*instantiator_result = instantiator;
*instantiator_type_arguments_result = instantiator_type_arguments;
}
Value* EffectGraphVisitor::BuildNullValue() {
return Bind(new(Z) ConstantInstr(Object::ZoneHandle(Z, Object::null())));
}
// Used for testing incoming arguments.
AssertAssignableInstr* EffectGraphVisitor::BuildAssertAssignable(
intptr_t token_pos,
Value* value,
const AbstractType& dst_type,
const String& dst_name) {
// Build the type check computation.
Value* instantiator = NULL;
Value* instantiator_type_arguments = NULL;
if (dst_type.IsInstantiated()) {
instantiator = BuildNullValue();
instantiator_type_arguments = BuildNullValue();
} else {
BuildTypecheckArguments(token_pos,
&instantiator,
&instantiator_type_arguments);
}
const intptr_t deopt_id = Thread::Current()->GetNextDeoptId();
return new(Z) AssertAssignableInstr(token_pos,
value,
instantiator,
instantiator_type_arguments,
dst_type,
dst_name,
deopt_id);
}
// Used for type casts and to test assignments.
Value* EffectGraphVisitor::BuildAssignableValue(intptr_t token_pos,
Value* value,
const AbstractType& dst_type,
const String& dst_name) {
if (CanSkipTypeCheck(token_pos, value, dst_type, dst_name)) {
return value;
}
return Bind(BuildAssertAssignable(token_pos, value, dst_type, dst_name));
}
bool FlowGraphBuilder::WarnOnJSIntegralNumTypeTest(
AstNode* node, const AbstractType& type) const {
if (!(node->IsLiteralNode() && (type.IsIntType() || type.IsDoubleType()))) {
return false;
}
const Instance& instance = node->AsLiteralNode()->literal();
if (type.IsIntType()) {
if (instance.IsDouble()) {
const Double& double_instance = Double::Cast(instance);
double value = double_instance.value();
if (floor(value) == value) {
return true;
}
}
} else {
ASSERT(type.IsDoubleType());
if (instance.IsInteger()) {
return true;
}
}
return false;
}
void EffectGraphVisitor::BuildTypeTest(ComparisonNode* node) {
ASSERT(Token::IsTypeTestOperator(node->kind()));
const AbstractType& type = node->right()->AsTypeNode()->type();
ASSERT(type.IsFinalized() && !type.IsMalformedOrMalbounded());
const bool negate_result = (node->kind() == Token::kISNOT);
// All objects are instances of type T if Object type is a subtype of type T.
const Type& object_type = Type::Handle(Z, Type::ObjectType());
if (type.IsInstantiated() &&
object_type.IsSubtypeOf(type, NULL, Heap::kOld)) {
// Must evaluate left side.
EffectGraphVisitor for_left_value(owner());
node->left()->Visit(&for_left_value);
Append(for_left_value);
ReturnDefinition(new(Z) ConstantInstr(Bool::Get(!negate_result)));
return;
}
ValueGraphVisitor for_left_value(owner());
node->left()->Visit(&for_left_value);
Append(for_left_value);
if (!FLAG_warn_on_javascript_compatibility) {
if (type.IsNumberType() || type.IsIntType() || type.IsDoubleType() ||
type.IsSmiType() || type.IsStringType()) {
String& method_name = String::ZoneHandle(Z);
if (type.IsNumberType()) {
method_name = Symbols::_instanceOfNum().raw();
} else if (type.IsIntType()) {
method_name = Symbols::_instanceOfInt().raw();
} else if (type.IsDoubleType()) {
method_name = Symbols::_instanceOfDouble().raw();
} else if (type.IsSmiType()) {
method_name = Symbols::_instanceOfSmi().raw();
} else if (type.IsStringType()) {
method_name = Symbols::_instanceOfString().raw();
}
ASSERT(!method_name.IsNull());
PushArgumentInstr* push_left = PushArgument(for_left_value.value());
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(2);
arguments->Add(push_left);
const Bool& negate = Bool::Get(node->kind() == Token::kISNOT);
Value* negate_arg = Bind(new(Z) ConstantInstr(negate));
arguments->Add(PushArgument(negate_arg));
const intptr_t kNumArgsChecked = 1;
InstanceCallInstr* call = new(Z) InstanceCallInstr(
node->token_pos(),
Library::PrivateCoreLibName(method_name),
node->kind(),
arguments,
Object::null_array(), // No argument names.
kNumArgsChecked,
owner()->ic_data_array());
ReturnDefinition(call);
return;
}
}
PushArgumentInstr* push_left = PushArgument(for_left_value.value());
PushArgumentInstr* push_instantiator = NULL;
PushArgumentInstr* push_type_args = NULL;
if (type.IsInstantiated()) {
push_instantiator = PushArgument(BuildNullValue());
push_type_args = PushArgument(BuildNullValue());
} else {
BuildTypecheckPushArguments(node->token_pos(),
&push_instantiator,
&push_type_args);
}
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(5);
arguments->Add(push_left);
arguments->Add(push_instantiator);
arguments->Add(push_type_args);
ASSERT(!node->right()->AsTypeNode()->type().IsNull());
Value* type_const = Bind(new(Z) ConstantInstr(type));
arguments->Add(PushArgument(type_const));
const Bool& negate = Bool::Get(node->kind() == Token::kISNOT);
Value* negate_arg = Bind(new(Z) ConstantInstr(negate));
arguments->Add(PushArgument(negate_arg));
const intptr_t kNumArgsChecked = 1;
InstanceCallInstr* call = new(Z) InstanceCallInstr(
node->token_pos(),
Library::PrivateCoreLibName(Symbols::_instanceOf()),
node->kind(),
arguments,
Object::null_array(), // No argument names.
kNumArgsChecked,
owner()->ic_data_array());
ReturnDefinition(call);
}
void EffectGraphVisitor::BuildTypeCast(ComparisonNode* node) {
ASSERT(Token::IsTypeCastOperator(node->kind()));
ASSERT(!node->right()->AsTypeNode()->type().IsNull());
const AbstractType& type = node->right()->AsTypeNode()->type();
ASSERT(type.IsFinalized() && !type.IsMalformed() && !type.IsMalbounded());
ValueGraphVisitor for_value(owner());
node->left()->Visit(&for_value);
Append(for_value);
const String& dst_name = String::ZoneHandle(
Z, Symbols::New(Exceptions::kCastErrorDstName));
if (CanSkipTypeCheck(node->token_pos(),
for_value.value(),
type,
dst_name)) {
// Check for javascript compatibility.
// Do not skip type check if javascript compatibility warning is required.
if (!FLAG_warn_on_javascript_compatibility ||
!owner()->WarnOnJSIntegralNumTypeTest(node->left(), type)) {
ReturnValue(for_value.value());
return;
}
}
PushArgumentInstr* push_left = PushArgument(for_value.value());
PushArgumentInstr* push_instantiator = NULL;
PushArgumentInstr* push_type_args = NULL;
if (type.IsInstantiated()) {
push_instantiator = PushArgument(BuildNullValue());
push_type_args = PushArgument(BuildNullValue());
} else {
BuildTypecheckPushArguments(node->token_pos(),
&push_instantiator,
&push_type_args);
}
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(4);
arguments->Add(push_left);
arguments->Add(push_instantiator);
arguments->Add(push_type_args);
Value* type_arg = Bind(new(Z) ConstantInstr(type));
arguments->Add(PushArgument(type_arg));
const intptr_t kNumArgsChecked = 1;
InstanceCallInstr* call = new(Z) InstanceCallInstr(
node->token_pos(),
Library::PrivateCoreLibName(Symbols::_as()),
node->kind(),
arguments,
Object::null_array(), // No argument names.
kNumArgsChecked,
owner()->ic_data_array());
ReturnDefinition(call);
}
StrictCompareInstr* EffectGraphVisitor::BuildStrictCompare(AstNode* left,
AstNode* right,
Token::Kind kind,
intptr_t token_pos) {
ValueGraphVisitor for_left_value(owner());
left->Visit(&for_left_value);
Append(for_left_value);
ValueGraphVisitor for_right_value(owner());
right->Visit(&for_right_value);
Append(for_right_value);
StrictCompareInstr* comp = new(Z) StrictCompareInstr(token_pos,
kind,
for_left_value.value(),
for_right_value.value(),
true); // Number check.
return comp;
}
// <Expression> :: Comparison { kind: Token::Kind
// left: <Expression>
// right: <Expression> }
void EffectGraphVisitor::VisitComparisonNode(ComparisonNode* node) {
if (Token::IsTypeTestOperator(node->kind())) {
BuildTypeTest(node);
return;
}
if (Token::IsTypeCastOperator(node->kind())) {
BuildTypeCast(node);
return;
}
if ((node->kind() == Token::kEQ_STRICT) ||
(node->kind() == Token::kNE_STRICT)) {
ReturnDefinition(BuildStrictCompare(node->left(), node->right(),
node->kind(), node->token_pos()));
return;
}
if ((node->kind() == Token::kEQ) || (node->kind() == Token::kNE)) {
// Eagerly fold null-comparisons.
LiteralNode* left_lit = node->left()->AsLiteralNode();
LiteralNode* right_lit = node->right()->AsLiteralNode();
if (((left_lit != NULL) && left_lit->literal().IsNull()) ||
((right_lit != NULL) && right_lit->literal().IsNull())) {
Token::Kind kind =
(node->kind() == Token::kEQ) ? Token::kEQ_STRICT : Token::kNE_STRICT;
StrictCompareInstr* compare =
BuildStrictCompare(node->left(), node->right(),
kind, node->token_pos());
ReturnDefinition(compare);
return;
}
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(2);
ValueGraphVisitor for_left_value(owner());
node->left()->Visit(&for_left_value);
Append(for_left_value);
PushArgumentInstr* push_left = PushArgument(for_left_value.value());
arguments->Add(push_left);
ValueGraphVisitor for_right_value(owner());
node->right()->Visit(&for_right_value);
Append(for_right_value);
PushArgumentInstr* push_right = PushArgument(for_right_value.value());
arguments->Add(push_right);
const intptr_t kNumArgsChecked = 2;
Definition* result = new(Z) InstanceCallInstr(
node->token_pos(),
Symbols::EqualOperator(),
Token::kEQ, // Result is negated later for kNE.
arguments,
Object::null_array(),
kNumArgsChecked,
owner()->ic_data_array());
if (node->kind() == Token::kNE) {
Isolate* isolate = Isolate::Current();
if (isolate->flags().type_checks() ||
isolate->flags().asserts()) {
Value* value = Bind(result);
result = new(Z) AssertBooleanInstr(node->token_pos(), value);
}
Value* value = Bind(result);
result = new(Z) BooleanNegateInstr(value);
}
ReturnDefinition(result);
return;
}
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(2);
ValueGraphVisitor for_left_value(owner());
node->left()->Visit(&for_left_value);
Append(for_left_value);
PushArgumentInstr* push_left = PushArgument(for_left_value.value());
arguments->Add(push_left);
ValueGraphVisitor for_right_value(owner());
node->right()->Visit(&for_right_value);
Append(for_right_value);
PushArgumentInstr* push_right = PushArgument(for_right_value.value());
arguments->Add(push_right);
ASSERT(Token::IsRelationalOperator(node->kind()));
InstanceCallInstr* comp = new(Z) InstanceCallInstr(
node->token_pos(),
String::ZoneHandle(Z, Symbols::New(node->TokenName())),
node->kind(),
arguments,
Object::null_array(),
2,
owner()->ic_data_array());
ReturnDefinition(comp);
}
void EffectGraphVisitor::VisitUnaryOpNode(UnaryOpNode* node) {
// "!" cannot be overloaded, therefore do not call operator.
if (node->kind() == Token::kNOT) {
ValueGraphVisitor for_value(owner());
node->operand()->Visit(&for_value);
Append(for_value);
Value* value = for_value.value();
Isolate* isolate = Isolate::Current();
if (isolate->flags().type_checks() ||
isolate->flags().asserts()) {
value =
Bind(new(Z) AssertBooleanInstr(node->operand()->token_pos(), value));
}
BooleanNegateInstr* negate = new(Z) BooleanNegateInstr(value);
ReturnDefinition(negate);
return;
}
ValueGraphVisitor for_value(owner());
node->operand()->Visit(&for_value);
Append(for_value);
PushArgumentInstr* push_value = PushArgument(for_value.value());
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(1);
arguments->Add(push_value);
InstanceCallInstr* call = new(Z) InstanceCallInstr(
node->token_pos(),
String::ZoneHandle(Z, Symbols::New(node->TokenName())),
node->kind(),
arguments,
Object::null_array(),
1,
owner()->ic_data_array());
ReturnDefinition(call);
}
void EffectGraphVisitor::VisitConditionalExprNode(ConditionalExprNode* node) {
TestGraphVisitor for_test(owner(), node->condition()->token_pos());
node->condition()->Visit(&for_test);
// Translate the subexpressions for their effects.
EffectGraphVisitor for_true(owner());
node->true_expr()->Visit(&for_true);
EffectGraphVisitor for_false(owner());
node->false_expr()->Visit(&for_false);
Join(for_test, for_true, for_false);
}
void ValueGraphVisitor::VisitConditionalExprNode(ConditionalExprNode* node) {
TestGraphVisitor for_test(owner(), node->condition()->token_pos());
node->condition()->Visit(&for_test);
ValueGraphVisitor for_true(owner());
node->true_expr()->Visit(&for_true);
ASSERT(for_true.is_open());
for_true.Do(BuildStoreExprTemp(for_true.value()));
ValueGraphVisitor for_false(owner());
node->false_expr()->Visit(&for_false);
ASSERT(for_false.is_open());
for_false.Do(BuildStoreExprTemp(for_false.value()));
Join(for_test, for_true, for_false);
ReturnDefinition(BuildLoadExprTemp());
}
// <Statement> ::= If { condition: <Expression>
// true_branch: <Sequence>
// false_branch: <Sequence> }
void EffectGraphVisitor::VisitIfNode(IfNode* node) {
TestGraphVisitor for_test(owner(), node->condition()->token_pos());
node->condition()->Visit(&for_test);
EffectGraphVisitor for_true(owner());
EffectGraphVisitor for_false(owner());
node->true_branch()->Visit(&for_true);
// The for_false graph fragment will be empty (default graph fragment) if
// we do not call Visit.
if (node->false_branch() != NULL) node->false_branch()->Visit(&for_false);
Join(for_test, for_true, for_false);
}
void EffectGraphVisitor::VisitSwitchNode(SwitchNode* node) {
NestedSwitch nested_switch(owner(), node);
EffectGraphVisitor switch_body(owner());
node->body()->Visit(&switch_body);
Append(switch_body);
if (nested_switch.break_target() != NULL) {
if (is_open()) Goto(nested_switch.break_target());
exit_ = nested_switch.break_target();
}
}
// A case node contains zero or more case expressions, can contain default
// and a case statement body.
// Compose fragment as follows:
// - if no case expressions, must have default:
// a) target
// b) [ case-statements ]
//
// - if has 1 or more case statements
// a) target-0
// b) [ case-expression-0 ] -> (true-target-0, target-1)
// c) target-1
// d) [ case-expression-1 ] -> (true-target-1, exit-target)
// e) true-target-0 -> case-statements-join
// f) true-target-1 -> case-statements-join
// g) case-statements-join
// h) [ case-statements ] -> exit-join
// i) exit-target -> exit-join
// j) exit-join
//
// Note: The specification of switch/case is under discussion and may change
// drastically.
void EffectGraphVisitor::VisitCaseNode(CaseNode* node) {
const intptr_t len = node->case_expressions()->length();
// Create case statements instructions.
EffectGraphVisitor for_case_statements(owner());
// Compute the start of the statements fragment.
JoinEntryInstr* statement_start = NULL;
if (node->label() == NULL) {
statement_start = new(Z) JoinEntryInstr(owner()->AllocateBlockId(),
owner()->try_index());
} else {
// The case nodes are nested inside a SequenceNode that is the body of a
// SwitchNode. The SwitchNode on the nesting stack contains the
// continue labels for all the case clauses.
statement_start =
owner()->nesting_stack()->outer()->ContinueTargetFor(node->label());
}
ASSERT(statement_start != NULL);
node->statements()->Visit(&for_case_statements);
Instruction* statement_exit =
AppendFragment(statement_start, for_case_statements);
if (is_open() && (len == 0)) {
ASSERT(node->contains_default());
// Default only case node.
Goto(statement_start);
exit_ = statement_exit;
return;
}
// Generate instructions for all case expressions.
TargetEntryInstr* next_target = NULL;
for (intptr_t i = 0; i < len; i++) {
AstNode* case_expr = node->case_expressions()->NodeAt(i);
TestGraphVisitor for_case_expression(owner(), case_expr->token_pos());
case_expr->Visit(&for_case_expression);
if (i == 0) {
// Append only the first one, everything else is connected from it.
Append(for_case_expression);
} else {
ASSERT(next_target != NULL);
AppendFragment(next_target, for_case_expression);
}
for_case_expression.IfTrueGoto(statement_start);
next_target = for_case_expression.CreateFalseSuccessor()->AsTargetEntry();
}
// Once a test fragment has been added, this fragment is closed.
ASSERT(!is_open());
Instruction* exit_instruction = NULL;
// Handle last (or only) case: false goes to exit or to statement if this
// node contains default.
if (len > 0) {
ASSERT(next_target != NULL);
if (node->contains_default()) {
// True and false go to statement start.
next_target->Goto(statement_start);
exit_instruction = statement_exit;
} else {
if (statement_exit != NULL) {
JoinEntryInstr* join = new(Z) JoinEntryInstr(owner()->AllocateBlockId(),
owner()->try_index());
statement_exit->Goto(join);
next_target->Goto(join);
exit_instruction = join;
} else {
exit_instruction = next_target;
}
}
} else {
// A CaseNode without case expressions must contain default.
ASSERT(node->contains_default());
Goto(statement_start);
exit_instruction = statement_exit;
}
ASSERT(!is_open());
exit_ = exit_instruction;
}
// <Statement> ::= While { label: SourceLabel
// condition: <Expression>
// body: <Sequence> }
// The fragment is composed as follows:
// a) loop-join
// b) [ test_preamble ]?
// c) [ test ] -> (body-entry-target, loop-exit-target)
// d) body-entry-target
// e) [ body ] -> (continue-join)
// f) continue-join -> (loop-join)
// g) loop-exit-target
// h) break-join (optional)
void EffectGraphVisitor::VisitWhileNode(WhileNode* node) {
NestedLoop nested_loop(owner(), node->label());
EffectGraphVisitor for_preamble(owner());
if (node->condition_preamble() != NULL) {
node->condition_preamble()->Visit(&for_preamble);
}
TestGraphVisitor for_test(owner(), node->condition()->token_pos());
node->condition()->Visit(&for_test);
ASSERT(!for_test.is_empty()); // Language spec.
EffectGraphVisitor for_body(owner());
node->body()->Visit(&for_body);
// Labels are set after body traversal.
JoinEntryInstr* join = nested_loop.continue_target();
if (join != NULL) {
if (for_body.is_open()) for_body.Goto(join);
for_body.exit_ = join;
}
TieLoop(node->token_pos(), for_test, for_body, for_preamble);
join = nested_loop.break_target();
if (join != NULL) {
Goto(join);
exit_ = join;
}
}
// The fragment is composed as follows:
// a) body-entry-join
// b) [ body ]
// c) test-entry (continue-join or body-exit-target)
// d) [ test-entry ] -> (back-target, loop-exit-target)
// e) back-target -> (body-entry-join)
// f) loop-exit-target
// g) break-join
void EffectGraphVisitor::VisitDoWhileNode(DoWhileNode* node) {
NestedLoop nested_loop(owner(), node->label());
// Traverse the body first in order to generate continue and break labels.
EffectGraphVisitor for_body(owner());
node->body()->Visit(&for_body);
TestGraphVisitor for_test(owner(), node->condition()->token_pos());
node->condition()->Visit(&for_test);
ASSERT(is_open());
// Tie do-while loop (test is after the body).
JoinEntryInstr* body_entry_join =
new(Z) JoinEntryInstr(owner()->AllocateBlockId(),
owner()->try_index());
Goto(body_entry_join);
Instruction* body_exit = AppendFragment(body_entry_join, for_body);
JoinEntryInstr* join = nested_loop.continue_target();
if ((body_exit != NULL) || (join != NULL)) {
if (join == NULL) {
join = new(Z) JoinEntryInstr(owner()->AllocateBlockId(),
owner()->try_index());
}
CheckStackOverflowInstr* check = new(Z) CheckStackOverflowInstr(
node->token_pos(), owner()->loop_depth());
join->LinkTo(check);
check->LinkTo(for_test.entry());
if (body_exit != NULL) {
body_exit->Goto(join);
}
}
for_test.IfTrueGoto(body_entry_join);
join = nested_loop.break_target();
if (join == NULL) {
exit_ = for_test.CreateFalseSuccessor();
} else {
for_test.IfFalseGoto(join);
exit_ = join;
}
}
// A ForNode can contain break and continue jumps. 'break' joins to
// ForNode exit, 'continue' joins at increment entry. The fragment is composed
// as follows:
// a) [ initializer ]
// b) loop-join
// c) [ test ] -> (body-entry-target, loop-exit-target)
// d) body-entry-target
// e) [ body ]
// f) continue-join (optional)
// g) [ increment ] -> (loop-join)
// h) loop-exit-target
// i) break-join
void EffectGraphVisitor::VisitForNode(ForNode* node) {
EffectGraphVisitor for_initializer(owner());
node->initializer()->Visit(&for_initializer);
Append(for_initializer);
ASSERT(is_open());
NestedLoop nested_loop(owner(), node->label());
// Compose body to set any jump labels.
EffectGraphVisitor for_body(owner());
node->body()->Visit(&for_body);
EffectGraphVisitor for_increment(owner());
node->increment()->Visit(&for_increment);
// Join the loop body and increment and then tie the loop.
JoinEntryInstr* continue_join = nested_loop.continue_target();
if ((continue_join != NULL) || for_body.is_open()) {
JoinEntryInstr* loop_entry =
new(Z) JoinEntryInstr(owner()->AllocateBlockId(), owner()->try_index());
if (continue_join != NULL) {
if (for_body.is_open()) for_body.Goto(continue_join);
Instruction* current = AppendFragment(continue_join, for_increment);
current->Goto(loop_entry);
} else {
for_body.Append(for_increment);
for_body.Goto(loop_entry);
}
Goto(loop_entry);
exit_ = loop_entry;
AddInstruction(
new(Z) CheckStackOverflowInstr(node->token_pos(),
owner()->loop_depth()));
}
if (node->condition() == NULL) {
// Endless loop, no test.
Append(for_body);
exit_ = nested_loop.break_target(); // May be NULL.
} else {
EffectGraphVisitor for_test_preamble(owner());
if (node->condition_preamble() != NULL) {
node->condition_preamble()->Visit(&for_test_preamble);
Append(for_test_preamble);
}
TestGraphVisitor for_test(owner(), node->condition()->token_pos());
node->condition()->Visit(&for_test);
Append(for_test);
BlockEntryInstr* body_entry = for_test.CreateTrueSuccessor();
AppendFragment(body_entry, for_body);
if (nested_loop.break_target() == NULL) {
exit_ = for_test.CreateFalseSuccessor();
} else {
for_test.IfFalseGoto(nested_loop.break_target());
exit_ = nested_loop.break_target();
}
}
}
void EffectGraphVisitor::VisitJumpNode(JumpNode* node) {
NestedContextAdjustment context_adjustment(owner(), owner()->context_level());
for (intptr_t i = 0; i < node->inlined_finally_list_length(); i++) {
EffectGraphVisitor for_effect(owner());
node->InlinedFinallyNodeAt(i)->Visit(&for_effect);
Append(for_effect);
if (!is_open()) return;
}
// Unchain the context(s) up to the outer context level of the scope which
// contains the destination label.
SourceLabel* label = node->label();
ASSERT(label->owner() != NULL);
AdjustContextLevel(label->owner());
JoinEntryInstr* jump_target = NULL;
NestedStatement* current = owner()->nesting_stack();
while (current != NULL) {
jump_target = (node->kind() == Token::kBREAK)
? current->BreakTargetFor(node->label())
: current->ContinueTargetFor(node->label());
if (jump_target != NULL) break;
current = current->outer();
}
ASSERT(jump_target != NULL);
Goto(jump_target);
}
void EffectGraphVisitor::VisitArgumentListNode(ArgumentListNode* node) {
UNREACHABLE();
}
void EffectGraphVisitor::VisitAwaitNode(AwaitNode* node) {
// Await nodes are temporary during parsing.
UNREACHABLE();
}
void EffectGraphVisitor::VisitAwaitMarkerNode(AwaitMarkerNode* node) {
// We need to create a new await state which involves:
// * Increase the jump counter. Sanity check against the list of targets.
// * Save the current context for resuming.
ASSERT(node->async_scope() != NULL);
ASSERT(node->await_scope() != NULL);
LocalVariable* jump_var = node->async_scope()->LookupVariable(
Symbols::AwaitJumpVar(), false);
LocalVariable* ctx_var = node->async_scope()->LookupVariable(
Symbols::AwaitContextVar(), false);
ASSERT((jump_var != NULL) && jump_var->is_captured());
ASSERT((ctx_var != NULL) && ctx_var->is_captured());
const intptr_t jump_count = owner()->next_await_counter();
ASSERT(jump_count >= 0);
// Sanity check that we always add a JoinEntryInstr before adding a new
// state.
ASSERT(jump_count == owner()->await_joins()->length());
// Store the counter in :await_jump_var.
Value* jump_val = Bind(new(Z) ConstantInstr(
Smi::ZoneHandle(Z, Smi::New(jump_count))));
Do(BuildStoreLocal(*jump_var, jump_val));
// Save the current context for resuming.
BuildSaveContext(*ctx_var);
}
intptr_t EffectGraphVisitor::GetCurrentTempLocalIndex() const {
return kFirstLocalSlotFromFp
- owner()->num_stack_locals()
- owner()->num_copied_params()
- owner()->args_pushed()
- owner()->temp_count() + 1;
}
LocalVariable* EffectGraphVisitor::EnterTempLocalScope(Value* value) {
Do(new(Z) PushTempInstr(value));
owner()->AllocateTemp();
ASSERT(value->definition()->temp_index() == (owner()->temp_count() - 1));
intptr_t index = GetCurrentTempLocalIndex();
char name[64];
OS::SNPrint(name, 64, ":tmp_local%" Pd, index);
LocalVariable* var =
new(Z) LocalVariable(0,
String::ZoneHandle(Z, Symbols::New(name)),
*value->Type()->ToAbstractType());
var->set_index(index);
return var;
}
Definition* EffectGraphVisitor::ExitTempLocalScope(LocalVariable* var) {
Value* tmp = Bind(new(Z) LoadLocalInstr(*var));
owner()->DeallocateTemps(1);
ASSERT(GetCurrentTempLocalIndex() == var->index());
return new(Z) DropTempsInstr(1, tmp);
}
void EffectGraphVisitor::BuildLetTempExpressions(LetNode* node) {
intptr_t num_temps = node->num_temps();
for (intptr_t i = 0; i < num_temps; ++i) {
ValueGraphVisitor for_value(owner());
node->InitializerAt(i)->Visit(&for_value);
Append(for_value);
Value* temp_val = for_value.value();
ASSERT(!node->TempAt(i)->HasIndex() ||
(node->TempAt(i)->index() == GetCurrentTempLocalIndex()));
node->TempAt(i)->set_index(GetCurrentTempLocalIndex());
Do(new(Z) PushTempInstr(temp_val));
owner()->AllocateTemp();
}
}
void EffectGraphVisitor::VisitLetNode(LetNode* node) {
BuildLetTempExpressions(node);
// Visit body.
for (intptr_t i = 0; i < node->nodes().length(); ++i) {
EffectGraphVisitor for_effect(owner());
node->nodes()[i]->Visit(&for_effect);
Append(for_effect);
}
intptr_t num_temps = node->num_temps();
if (num_temps > 0) {
owner()->DeallocateTemps(num_temps);
Do(new(Z) DropTempsInstr(num_temps, NULL));
}
}
void ValueGraphVisitor::VisitLetNode(LetNode* node) {
BuildLetTempExpressions(node);
// Visit body.
for (intptr_t i = 0; i < node->nodes().length() - 1; ++i) {
EffectGraphVisitor for_effect(owner());
node->nodes()[i]->Visit(&for_effect);
Append(for_effect);
}
// Visit the last body expression for value.
ValueGraphVisitor for_value(owner());
node->nodes().Last()->Visit(&for_value);
Append(for_value);
Value* result_value = for_value.value();
intptr_t num_temps = node->num_temps();
if (num_temps > 0) {
owner()->DeallocateTemps(num_temps);
ReturnDefinition(new(Z) DropTempsInstr(num_temps, result_value));
} else {
ReturnValue(result_value);
}
}
void EffectGraphVisitor::VisitArrayNode(ArrayNode* node) {
const TypeArguments& type_args =
TypeArguments::ZoneHandle(Z, node->type().arguments());
Value* element_type = BuildInstantiatedTypeArguments(node->token_pos(),
type_args);
Value* num_elements =
Bind(new(Z) ConstantInstr(Smi::ZoneHandle(Z, Smi::New(node->length()))));
CreateArrayInstr* create = new(Z) CreateArrayInstr(node->token_pos(),
element_type,
num_elements);
Value* array_val = Bind(create);
{ LocalVariable* tmp_var = EnterTempLocalScope(array_val);
const intptr_t class_id = kArrayCid;
const intptr_t deopt_id = Thread::kNoDeoptId;
for (int i = 0; i < node->length(); ++i) {
Value* array = Bind(new(Z) LoadLocalInstr(*tmp_var));
Value* index =
Bind(new(Z) ConstantInstr(Smi::ZoneHandle(Z, Smi::New(i))));
ValueGraphVisitor for_value(owner());
node->ElementAt(i)->Visit(&for_value);
Append(for_value);
// No store barrier needed for constants.
const StoreBarrierType emit_store_barrier =
for_value.value()->BindsToConstant()
? kNoStoreBarrier
: kEmitStoreBarrier;
const intptr_t index_scale = Instance::ElementSizeFor(class_id);
StoreIndexedInstr* store = new(Z) StoreIndexedInstr(
array, index, for_value.value(), emit_store_barrier,
index_scale, class_id, deopt_id, node->token_pos());
Do(store);
}
ReturnDefinition(ExitTempLocalScope(tmp_var));
}
}
void EffectGraphVisitor::VisitStringInterpolateNode(
StringInterpolateNode* node) {
ValueGraphVisitor for_argument(owner());
ArrayNode* arguments = node->value();
if (arguments->length() == 1) {
ZoneGrowableArray<PushArgumentInstr*>* values =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(1);
arguments->ElementAt(0)->Visit(&for_argument);
Append(for_argument);
PushArgumentInstr* push_arg = PushArgument(for_argument.value());
values->Add(push_arg);
const int kNumberOfArguments = 1;
const Array& kNoArgumentNames = Object::null_array();
const Class& cls =
Class::Handle(Library::LookupCoreClass(Symbols::StringBase()));
ASSERT(!cls.IsNull());
const Function& function = Function::ZoneHandle(
Z,
Resolver::ResolveStatic(
cls,
Library::PrivateCoreLibName(Symbols::InterpolateSingle()),
kNumberOfArguments,
kNoArgumentNames));
StaticCallInstr* call =
new(Z) StaticCallInstr(node->token_pos(),
function,
kNoArgumentNames,
values,
owner()->ic_data_array());
ReturnDefinition(call);
return;
}
arguments->Visit(&for_argument);
Append(for_argument);
StringInterpolateInstr* instr =
new(Z) StringInterpolateInstr(for_argument.value(), node->token_pos());
ReturnDefinition(instr);
}
// TODO(rmacnak): De-dup closures in inlined-finally and track down other
// stragglers to use Class::closures instead.
static void CollectClosureFunction(const Function& function) {
if (function.HasCode()) return;
// Although this is only called when precompiling, this can happen before
// Dart_Precompile as part of loading code, so check for a non-null work
// list.
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
if (isolate->collected_closures() != GrowableObjectArray::null()) {
const GrowableObjectArray& functions =
GrowableObjectArray::Handle(thread->zone(),
isolate->collected_closures());
functions.Add(function);
}
}
void EffectGraphVisitor::VisitClosureNode(ClosureNode* node) {
const Function& function = node->function();
if (FLAG_precompile_collect_closures) {
CollectClosureFunction(function);
}
if (function.IsImplicitStaticClosureFunction()) {
const Instance& closure =
Instance::ZoneHandle(Z, function.ImplicitStaticClosure());
ReturnDefinition(new(Z) ConstantInstr(closure));
return;
}
const bool is_implicit = function.IsImplicitInstanceClosureFunction();
ASSERT(is_implicit || function.IsNonImplicitClosureFunction());
// The context scope may have already been set by the non-optimizing
// compiler. If it was not, set it here.
if (function.context_scope() == ContextScope::null()) {
ASSERT(!is_implicit);
const ContextScope& context_scope = ContextScope::ZoneHandle(
Z, node->scope()->PreserveOuterScope(owner()->context_level()));
ASSERT(!function.HasCode());
ASSERT(function.context_scope() == ContextScope::null());
function.set_context_scope(context_scope);
const Class& cls = Class::Handle(Z, owner()->function().Owner());
// The closure is now properly setup, add it to the lookup table.
// It is possible that the compiler creates more than one function
// object for the same closure, e.g. when inlining nodes from
// finally clauses. If we already have a function object for the
// same closure, do not add a second one. We compare the origin
// class, token position, and parent function to detect duplicates.
// Note that we can have two different closure object for the same
// source text representation of the closure: one with a non-closurized
// parent, and one with a closurized parent function.
const Function& found_func = Function::Handle(
Z, cls.LookupClosureFunction(function.token_pos()));
if (found_func.IsNull() ||
(found_func.token_pos() != function.token_pos()) ||
(found_func.script() != function.script()) ||
(found_func.parent_function() != function.parent_function())) {
cls.AddClosureFunction(function);
}
}
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(1);
ASSERT(function.context_scope() != ContextScope::null());
// The function type of a closure may have type arguments. In that case,
// pass the type arguments of the instantiator.
const Class& cls = Class::ZoneHandle(Z, function.signature_class());
ASSERT(!cls.IsNull());
const bool requires_type_arguments = cls.NumTypeArguments() > 0;
Value* type_arguments = NULL;
if (requires_type_arguments) {
ASSERT(cls.type_arguments_field_offset() ==
Closure::type_arguments_offset());
ASSERT(cls.instance_size() == Closure::InstanceSize());
const Class& instantiator_class = Class::Handle(
Z, owner()->function().Owner());
type_arguments = BuildInstantiatorTypeArguments(node->token_pos(),
instantiator_class,
NULL);
arguments->Add(PushArgument(type_arguments));
}
AllocateObjectInstr* alloc = new(Z) AllocateObjectInstr(node->token_pos(),
cls,
arguments);
alloc->set_closure_function(function);
Value* closure_val = Bind(alloc);
{ LocalVariable* closure_tmp_var = EnterTempLocalScope(closure_val);
// Store function.
Value* closure_tmp_val = Bind(new(Z) LoadLocalInstr(*closure_tmp_var));
Value* func_val =
Bind(new(Z) ConstantInstr(Function::ZoneHandle(Z, function.raw())));
Do(new(Z) StoreInstanceFieldInstr(Closure::function_offset(),
closure_tmp_val,
func_val,
kEmitStoreBarrier,
node->token_pos()));
if (is_implicit) {
// Create new context containing the receiver.
const intptr_t kNumContextVariables = 1; // The receiver.
Value* allocated_context =
Bind(new(Z) AllocateContextInstr(node->token_pos(),
kNumContextVariables));
{ LocalVariable* context_tmp_var = EnterTempLocalScope(allocated_context);
// Store receiver in context.
Value* context_tmp_val = Bind(new(Z) LoadLocalInstr(*context_tmp_var));
ValueGraphVisitor for_receiver(owner());
node->receiver()->Visit(&for_receiver);
Append(for_receiver);
Value* receiver = for_receiver.value();
Do(new(Z) StoreInstanceFieldInstr(Context::variable_offset(0),
context_tmp_val,
receiver,
kEmitStoreBarrier,
node->token_pos()));
// Store new context in closure.
closure_tmp_val = Bind(new(Z) LoadLocalInstr(*closure_tmp_var));
context_tmp_val = Bind(new(Z) LoadLocalInstr(*context_tmp_var));
Do(new(Z) StoreInstanceFieldInstr(Closure::context_offset(),
closure_tmp_val,
context_tmp_val,
kEmitStoreBarrier,
node->token_pos()));
Do(ExitTempLocalScope(context_tmp_var));
}
} else {
// Store current context in closure.
closure_tmp_val = Bind(new(Z) LoadLocalInstr(*closure_tmp_var));
Value* context = Bind(BuildCurrentContext());
Do(new(Z) StoreInstanceFieldInstr(Closure::context_offset(),
closure_tmp_val,
context,
kEmitStoreBarrier,
node->token_pos()));
}
ReturnDefinition(ExitTempLocalScope(closure_tmp_var));
}
}
void EffectGraphVisitor::BuildPushArguments(
const ArgumentListNode& node,
ZoneGrowableArray<PushArgumentInstr*>* values) {
for (intptr_t i = 0; i < node.length(); ++i) {
ValueGraphVisitor for_argument(owner());
node.NodeAt(i)->Visit(&for_argument);
Append(for_argument);
PushArgumentInstr* push_arg = PushArgument(for_argument.value());
values->Add(push_arg);
}
}
void EffectGraphVisitor::BuildInstanceCallConditional(InstanceCallNode* node) {
LocalVariable* temp_var = owner()->parsed_function().expression_temp_var();
LoadLocalNode* load_temp =
new(Z) LoadLocalNode(Scanner::kNoSourcePos, temp_var);
LiteralNode* null_constant =
new(Z) LiteralNode(Scanner::kNoSourcePos, Object::null_instance());
ComparisonNode* check_is_null =
new(Z) ComparisonNode(Scanner::kNoSourcePos,
Token::kEQ,
load_temp,
null_constant);
TestGraphVisitor for_test(owner(), Scanner::kNoSourcePos);
check_is_null->Visit(&for_test);
EffectGraphVisitor for_true(owner());
EffectGraphVisitor for_false(owner());
StoreLocalNode* store_null =
new(Z) StoreLocalNode(Scanner::kNoSourcePos, temp_var, null_constant);
store_null->Visit(&for_true);
InstanceCallNode* call =
new(Z) InstanceCallNode(node->token_pos(),
load_temp,
node->function_name(),
node->arguments());
StoreLocalNode* store_result =
new(Z) StoreLocalNode(Scanner::kNoSourcePos, temp_var, call);
store_result->Visit(&for_false);
Join(for_test, for_true, for_false);
}
void ValueGraphVisitor::VisitInstanceCallNode(InstanceCallNode* node) {
if (node->is_conditional()) {
ValueGraphVisitor for_receiver(owner());
node->receiver()->Visit(&for_receiver);
Append(for_receiver);
Do(BuildStoreExprTemp(for_receiver.value()));
BuildInstanceCallConditional(node);
ReturnDefinition(BuildLoadExprTemp());
} else {
EffectGraphVisitor::VisitInstanceCallNode(node);
}
}
void EffectGraphVisitor::VisitInstanceCallNode(InstanceCallNode* node) {
ValueGraphVisitor for_receiver(owner());
node->receiver()->Visit(&for_receiver);
Append(for_receiver);
if (node->is_conditional()) {
Do(BuildStoreExprTemp(for_receiver.value()));
BuildInstanceCallConditional(node);
} else {
PushArgumentInstr* push_receiver = PushArgument(for_receiver.value());
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(
node->arguments()->length() + 1);
arguments->Add(push_receiver);
BuildPushArguments(*node->arguments(), arguments);
InstanceCallInstr* call = new(Z) InstanceCallInstr(
node->token_pos(),
node->function_name(),
Token::kILLEGAL,
arguments,
node->arguments()->names(),
1,
owner()->ic_data_array());
ReturnDefinition(call);
}
}
static intptr_t GetResultCidOfNativeFactory(const Function& function) {
const Class& function_class = Class::Handle(function.Owner());
if (function_class.library() == Library::TypedDataLibrary()) {
const String& function_name = String::Handle(function.name());
if (!String::EqualsIgnoringPrivateKey(function_name, Symbols::_New())) {
return kDynamicCid;
}
switch (function_class.id()) {
case kTypedDataInt8ArrayCid:
case kTypedDataUint8ArrayCid:
case kTypedDataUint8ClampedArrayCid:
case kTypedDataInt16ArrayCid:
case kTypedDataUint16ArrayCid:
case kTypedDataInt32ArrayCid:
case kTypedDataUint32ArrayCid:
case kTypedDataInt64ArrayCid:
case kTypedDataUint64ArrayCid:
case kTypedDataFloat32ArrayCid:
case kTypedDataFloat64ArrayCid:
case kTypedDataFloat32x4ArrayCid:
case kTypedDataInt32x4ArrayCid:
return function_class.id();
default:
return kDynamicCid; // Unknown.
}
}
return kDynamicCid;
}
// <Expression> ::= StaticCall { function: Function
// arguments: <ArgumentList> }
void EffectGraphVisitor::VisitStaticCallNode(StaticCallNode* node) {
ZoneGrowableArray<PushArgumentInstr*>* arguments =
new(Z) ZoneGrowableArray<PushArgumentInstr*>(node->arguments()->length());
BuildPushArguments(*node->arguments(), arguments);
StaticCallInstr* call =
new(Z) StaticCallInstr(node->token_pos(),
node->function(),
node->arguments()->names(),
arguments,
owner()->ic_data_array());
if (node->function().is_native()) {