| // 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/compiler.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, |
| trace_type_check_elimination, |
| false, |
| "Trace type check elimination at compile time."); |
| |
| DECLARE_FLAG(bool, profile_vm); |
| DECLARE_FLAG(bool, support_externalizable_strings); |
| |
| // Quick access to the locally defined zone() method. |
| #define Z (zone()) |
| |
| // Quick access to the locally defined thread() method. |
| #define T (thread()) |
| |
| // Quick synthetic token position. |
| #define ST(token_pos) ((token_pos).ToSynthetic()) |
| |
| // 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(symbol, class_name, constructor_name, cid, fp) \ |
| {Symbols::k##symbol##Id, cid, fp, #symbol ", " #cid}, // NOLINT |
| |
| static struct { |
| intptr_t symbol_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].symbol_id != Symbols::kIllegal; i++) { |
| if (String::EqualsIgnoringPrivateKey( |
| factory_name, |
| Symbols::Symbol(factory_recognizer_list[i].symbol_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(), 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*>()), |
| await_token_positions_(new (Z) ZoneGrowableArray<TokenPosition>()) {} |
| |
| |
| 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); |
| } |
| } |
| |
| RemoveUnreachableExits(callee_graph); |
| } |
| |
| |
| 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::RemoveUnreachableExits(FlowGraph* callee_graph) { |
| const GrowableArray<BlockEntryInstr*>& postorder = callee_graph->postorder(); |
| int j = 0; |
| for (int i = 0; i < exits_.length(); ++i) { |
| BlockEntryInstr* block = exits_[i].exit_return->GetBlock(); |
| if ((block != NULL) && (0 <= block->postorder_number()) && |
| (block->postorder_number() < postorder.length()) && |
| (postorder[block->postorder_number()] == block)) { |
| if (i != j) { |
| exits_[j] = exits_[i]; |
| } |
| j++; |
| } |
| } |
| exits_.TruncateTo(j); |
| } |
| |
| |
| 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( |
| TokenPosition::kNoSource, 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); |
| |
| // Replace uses of the return value with null to maintain valid |
| // SSA form - even though the rest of the caller is unreachable. |
| call_->ReplaceUsesWith(caller_graph_->constant_null()); |
| |
| // 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(TokenPosition 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( |
| TokenPosition 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, |
| TokenPosition token_pos) { |
| ASSERT(!local.is_captured()); |
| ASSERT(!token_pos.IsClassifying()); |
| return new (Z) StoreLocalInstr(local, value, ST(token_pos)); |
| } |
| |
| |
| Definition* EffectGraphVisitor::BuildStoreExprTemp(Value* value, |
| TokenPosition token_pos) { |
| return BuildStoreTemp(*owner()->parsed_function().expression_temp_var(), |
| value, token_pos); |
| } |
| |
| |
| Definition* EffectGraphVisitor::BuildLoadExprTemp(TokenPosition token_pos) { |
| ASSERT(!token_pos.IsClassifying()); |
| return BuildLoadLocal(*owner()->parsed_function().expression_temp_var(), |
| token_pos); |
| } |
| |
| |
| Definition* EffectGraphVisitor::BuildStoreLocal(const LocalVariable& local, |
| Value* value, |
| TokenPosition token_pos) { |
| 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(token_pos)); |
| while (delta-- > 0) { |
| context = Bind(new (Z) LoadFieldInstr(context, Context::parent_offset(), |
| Type::ZoneHandle(Z, Type::null()), |
| token_pos)); |
| } |
| Value* tmp_val = Bind(new (Z) LoadLocalInstr(*tmp_var, token_pos)); |
| StoreInstanceFieldInstr* store = new (Z) |
| StoreInstanceFieldInstr(Context::variable_offset(local.index()), |
| context, tmp_val, kEmitStoreBarrier, token_pos); |
| Do(store); |
| return ExitTempLocalScope(value); |
| } else { |
| return new (Z) StoreLocalInstr(local, value, token_pos); |
| } |
| } |
| |
| |
| Definition* EffectGraphVisitor::BuildLoadLocal(const LocalVariable& local, |
| TokenPosition token_pos) { |
| if (local.IsConst()) { |
| return new (Z) ConstantInstr(*local.ConstValue(), token_pos); |
| } else if (local.is_captured()) { |
| intptr_t delta = owner()->context_level() - local.owner()->context_level(); |
| ASSERT(delta >= 0); |
| Value* context = Bind(BuildCurrentContext(token_pos)); |
| while (delta-- > 0) { |
| context = Bind(new (Z) LoadFieldInstr(context, Context::parent_offset(), |
| Type::ZoneHandle(Z, Type::null()), |
| token_pos)); |
| } |
| LoadFieldInstr* load = |
| new (Z) LoadFieldInstr(context, Context::variable_offset(local.index()), |
| local.type(), token_pos); |
| load->set_is_immutable(local.is_final()); |
| return load; |
| } else { |
| return new (Z) LoadLocalInstr(local, token_pos); |
| } |
| } |
| |
| |
| // Stores current context into the 'variable' |
| void EffectGraphVisitor::BuildSaveContext(const LocalVariable& variable, |
| TokenPosition token_pos) { |
| ASSERT(token_pos.IsSynthetic() || token_pos.IsNoSource()); |
| Value* context = Bind(BuildCurrentContext(token_pos)); |
| Do(BuildStoreLocal(variable, context, token_pos)); |
| } |
| |
| |
| // Loads context saved in 'context_variable' into the current context. |
| void EffectGraphVisitor::BuildRestoreContext(const LocalVariable& variable, |
| TokenPosition token_pos) { |
| Value* load_saved_context = Bind(BuildLoadLocal(variable, token_pos)); |
| Do(BuildStoreContext(load_saved_context, token_pos)); |
| } |
| |
| |
| Definition* EffectGraphVisitor::BuildStoreContext(Value* value, |
| TokenPosition token_pos) { |
| return new (Z) StoreLocalInstr( |
| *owner()->parsed_function().current_context_var(), value, token_pos); |
| } |
| |
| |
| Definition* EffectGraphVisitor::BuildCurrentContext(TokenPosition token_pos) { |
| return new (Z) LoadLocalInstr( |
| *owner()->parsed_function().current_context_var(), token_pos); |
| } |
| |
| |
| 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->type_checks() || isolate->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::MergeBranchWithStrictCompare(StrictCompareInstr* comp) { |
| 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::MergeBranchWithNegate(BooleanNegateInstr* neg) { |
| ASSERT(!Isolate::Current()->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) { |
| StrictCompareInstr* comp = definition->AsStrictCompare(); |
| if (comp != NULL) { |
| MergeBranchWithStrictCompare(comp); |
| return; |
| } |
| if (!Isolate::Current()->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().IsDebugPause() && |
| !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, node->token_pos())); |
| 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, node->token_pos())); |
| } |
| |
| if (Isolate::Current()->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()); |
| } |
| } |
| |
| if (FLAG_causal_async_stacks && |
| (function.IsAsyncClosure() || function.IsAsyncGenClosure())) { |
| // We are returning from an asynchronous closure. Before we do that, be |
| // sure to clear the thread's asynchronous stack trace. |
| const Function& async_clear_thread_stack_trace = Function::ZoneHandle( |
| Z, isolate()->object_store()->async_clear_thread_stack_trace()); |
| ZoneGrowableArray<PushArgumentInstr*>* no_arguments = |
| new (Z) ZoneGrowableArray<PushArgumentInstr*>(0); |
| StaticCallInstr* call_async_clear_thread_stack_trace = new (Z) |
| StaticCallInstr(node->token_pos().ToSynthetic(), |
| async_clear_thread_stack_trace, Object::null_array(), |
| no_arguments, owner()->ic_data_array()); |
| Do(call_async_clear_thread_stack_trace); |
| } |
| |
| // 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, node->token_pos())); |
| |
| 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, node->token_pos())); |
| arguments->Add(PushArgument(rcv_value)); |
| Value* returned_value = Bind(BuildLoadExprTemp(node->token_pos())); |
| arguments->Add(PushArgument(returned_value)); |
| // Call a helper function to complete the completer. The debugger |
| // uses the helper function to know when to step-out. |
| const Function& complete_on_async_return = Function::ZoneHandle( |
| Z, isolate()->object_store()->complete_on_async_return()); |
| ASSERT(!complete_on_async_return.IsNull()); |
| StaticCallInstr* call = new (Z) StaticCallInstr( |
| node->token_pos().ToSynthetic(), complete_on_async_return, |
| Object::null_array(), arguments, owner()->ic_data_array()); |
| Do(call); |
| |
| // Rebind the return value for the actual return call to be null. |
| return_value = BuildNullValue(node->token_pos()); |
| } |
| |
| 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(), node->token_pos())); |
| } |
| |
| |
| // 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)); |
| return; |
| } |
| const TokenPosition token_pos = node->token_pos(); |
| Value* instantiator_type_arguments = NULL; |
| if (type.IsInstantiated(kClass)) { |
| instantiator_type_arguments = BuildNullValue(token_pos); |
| } else { |
| instantiator_type_arguments = BuildInstantiatorTypeArguments(token_pos); |
| } |
| Value* function_type_arguments = NULL; |
| if (type.IsInstantiated(kCurrentFunction)) { |
| // TODO(regis): function_type_arguments = BuildNullValue((token_pos); |
| } else { |
| function_type_arguments = BuildFunctionTypeArguments(token_pos); |
| } |
| ReturnDefinition(new (Z) InstantiateTypeInstr( |
| token_pos, type, instantiator_type_arguments, function_type_arguments)); |
| } |
| |
| |
| // 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(TokenPosition 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); |
| if (CanSkipTypeCheck(node->expr()->token_pos(), for_value.value(), |
| node->type(), node->dst_name())) { |
| ReturnValue(for_value.value()); |
| } else { |
| ReturnDefinition(BuildAssertAssignable(node->expr()->token_pos(), |
| for_value.value(), node->type(), |
| node->dst_name())); |
| } |
| } |
| |
| |
| 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->type_checks() || isolate->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; |
| } |
| ASSERT(node->kind() != Token::kIFNULL); |
| 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 = Symbols::Token(node->kind()); |
| 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->type_checks() || isolate->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, node->token_pos())); |
| |
| 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, node->token_pos())); |
| 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, node->token_pos())); |
| Join(for_test, for_true, for_right); |
| } |
| ReturnDefinition(BuildLoadExprTemp(node->token_pos())); |
| return; |
| } |
| |
| EffectGraphVisitor::VisitBinaryOpNode(node); |
| } |
| |
| |
| PushArgumentInstr* EffectGraphVisitor::PushInstantiatorTypeArguments( |
| const AbstractType& type, |
| TokenPosition token_pos) { |
| if (type.IsInstantiated(kClass)) { |
| return PushArgument(BuildNullValue(token_pos)); |
| } else { |
| Value* instantiator_type_args = BuildInstantiatorTypeArguments(token_pos); |
| return PushArgument(instantiator_type_args); |
| } |
| } |
| |
| |
| PushArgumentInstr* EffectGraphVisitor::PushFunctionTypeArguments( |
| const AbstractType& type, |
| TokenPosition token_pos) { |
| if (type.IsInstantiated(kCurrentFunction)) { |
| return PushArgument(BuildNullValue(token_pos)); |
| } else { |
| Value* function_type_args = BuildFunctionTypeArguments(token_pos); |
| return PushArgument(function_type_args); |
| } |
| } |
| |
| |
| Value* EffectGraphVisitor::BuildNullValue(TokenPosition token_pos) { |
| return Bind( |
| new (Z) ConstantInstr(Object::ZoneHandle(Z, Object::null()), token_pos)); |
| } |
| |
| |
| // Used for testing incoming arguments. |
| AssertAssignableInstr* EffectGraphVisitor::BuildAssertAssignable( |
| TokenPosition token_pos, |
| Value* value, |
| const AbstractType& dst_type, |
| const String& dst_name) { |
| // Build the type check computation. |
| Value* instantiator_type_arguments = NULL; |
| Value* function_type_arguments = NULL; |
| if (dst_type.IsInstantiated(kClass)) { |
| instantiator_type_arguments = BuildNullValue(token_pos); |
| } else { |
| instantiator_type_arguments = BuildInstantiatorTypeArguments(token_pos); |
| } |
| if (dst_type.IsInstantiated(kCurrentFunction)) { |
| // TODO(regis): function_type_arguments = BuildNullValue(token_pos); |
| } else { |
| function_type_arguments = BuildFunctionTypeArguments(token_pos); |
| } |
| |
| const intptr_t deopt_id = Thread::Current()->GetNextDeoptId(); |
| return new (Z) AssertAssignableInstr( |
| token_pos, value, instantiator_type_arguments, function_type_arguments, |
| dst_type, dst_name, deopt_id); |
| } |
| |
| |
| // Used for type casts and to test assignments. |
| Value* EffectGraphVisitor::BuildAssignableValue(TokenPosition 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)); |
| } |
| |
| |
| static bool simpleInstanceOfType(const AbstractType& type) { |
| // Bail if the type is still uninstantiated at compile time. |
| if (!type.IsInstantiated()) return false; |
| |
| // Bail if the type is a function or a Dart Function type. |
| if (type.IsFunctionType() || type.IsDartFunctionType()) return false; |
| |
| ASSERT(type.HasResolvedTypeClass()); |
| const Class& type_class = Class::Handle(type.type_class()); |
| // Bail if the type has any type parameters. |
| if (type_class.IsGeneric()) return false; |
| |
| // Finally a simple class for instance of checking. |
| return true; |
| } |
| |
| |
| 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, 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); |
| |
| // We now know type is a real class (!num, !int, !smi, !string) |
| // and the type check could NOT be removed at compile time. |
| PushArgumentInstr* push_left = PushArgument(for_left_value.value()); |
| if (simpleInstanceOfType(type)) { |
| ZoneGrowableArray<PushArgumentInstr*>* arguments = |
| new (Z) ZoneGrowableArray<PushArgumentInstr*>(2); |
| arguments->Add(push_left); |
| Value* type_const = Bind(new (Z) ConstantInstr(type)); |
| arguments->Add(PushArgument(type_const)); |
| const intptr_t kNumArgsChecked = 2; |
| Definition* result = new (Z) InstanceCallInstr( |
| node->token_pos(), |
| Library::PrivateCoreLibName(Symbols::_simpleInstanceOf()), node->kind(), |
| arguments, |
| Object::null_array(), // No argument names. |
| kNumArgsChecked, owner()->ic_data_array()); |
| if (negate_result) { |
| result = new (Z) BooleanNegateInstr(Bind(result)); |
| } |
| ReturnDefinition(result); |
| return; |
| } |
| |
| PushArgumentInstr* push_instantiator_type_args = |
| PushInstantiatorTypeArguments(type, node->token_pos()); |
| // TODO(regis): PushArgumentInstr* push_function_type_args = |
| // PushFunctionTypeArguments(type, node->token_pos()); |
| ZoneGrowableArray<PushArgumentInstr*>* arguments = |
| new (Z) ZoneGrowableArray<PushArgumentInstr*>(3); |
| arguments->Add(push_left); |
| arguments->Add(push_instantiator_type_args); |
| // TODO(regis): arguments->Add(push_function_type_args); |
| Value* type_const = Bind(new (Z) ConstantInstr(type)); |
| arguments->Add(PushArgument(type_const)); |
| const intptr_t kNumArgsChecked = 1; |
| Definition* result = new (Z) InstanceCallInstr( |
| node->token_pos(), Library::PrivateCoreLibName(Symbols::_instanceOf()), |
| node->kind(), arguments, |
| Object::null_array(), // No argument names. |
| kNumArgsChecked, owner()->ic_data_array()); |
| if (negate_result) { |
| result = new (Z) BooleanNegateInstr(Bind(result)); |
| } |
| ReturnDefinition(result); |
| } |
| |
| |
| 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); |
| if (CanSkipTypeCheck(node->token_pos(), for_value.value(), type, |
| Symbols::InTypeCast())) { |
| ReturnValue(for_value.value()); |
| return; |
| } |
| PushArgumentInstr* push_left = PushArgument(for_value.value()); |
| PushArgumentInstr* push_instantiator_type_args = |
| PushInstantiatorTypeArguments(type, node->token_pos()); |
| // TODO(regis): PushArgumentInstr* push_function_type_args = |
| // PushFunctionTypeArguments(type, node->token_pos()); |
| ZoneGrowableArray<PushArgumentInstr*>* arguments = |
| new (Z) ZoneGrowableArray<PushArgumentInstr*>(3); |
| arguments->Add(push_left); |
| arguments->Add(push_instantiator_type_args); |
| // TODO(regis): arguments->Add(push_function_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, |
| TokenPosition 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->type_checks() || isolate->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(), Symbols::Token(node->kind()), 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->type_checks() || isolate->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(), Symbols::Token(node->kind()), 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(), node->true_expr()->token_pos())); |
| |
| ValueGraphVisitor for_false(owner()); |
| node->false_expr()->Visit(&for_false); |
| ASSERT(for_false.is_open()); |
| for_false.Do( |
| BuildStoreExprTemp(for_false.value(), node->false_expr()->token_pos())); |
| |
| Join(for_test, for_true, for_false); |
| ReturnDefinition(BuildLoadExprTemp(node->token_pos())); |
| } |
| |
| |
| // <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; |
| // Note: the stack overflow check happens on the back branch that jumps |
| // to the increment instruction. The token position for the overflow |
| // check must match the position of the increment expression, so that |
| // the context level (if any) matches the that of the increment |
| // expression. |
| AddInstruction(new (Z) CheckStackOverflowInstr( |
| node->increment()->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) { |
| if (FLAG_support_debugger && owner()->function().is_debuggable()) { |
| AddInstruction(new (Z) DebugStepCheckInstr(node->token_pos(), |
| RawPcDescriptors::kRuntimeCall)); |
| } |
| |
| 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->token_pos().IsSynthetic() || node->token_pos().IsNoSource()); |
| 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)), node->token_pos())); |
| Do(BuildStoreLocal(*jump_var, jump_val, node->token_pos())); |
| // Add a mapping from jump_count -> token_position. |
| owner()->AppendAwaitTokenPosition(node->token_pos()); |
| // Save the current context for resuming. |
| BuildSaveContext(*ctx_var, node->token_pos()); |
| } |
| |
| |
| 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) { |
| 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(TokenPosition::kNoSource, TokenPosition::kNoSource, |
| String::ZoneHandle(Z, Symbols::New(T, name)), |
| *value->Type()->ToAbstractType()); |
| var->set_index(index); |
| return var; |
| } |
| |
| |
| Definition* EffectGraphVisitor::ExitTempLocalScope(Value* value) { |
| return new (Z) DropTempsInstr(0, value); |
| } |
| |
| |
| 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); |
| ASSERT(!node->TempAt(i)->HasIndex() || |
| (node->TempAt(i)->index() == GetCurrentTempLocalIndex())); |
| node->TempAt(i)->set_index(GetCurrentTempLocalIndex()); |
| } |
| } |
| |
| |
| 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, node->token_pos())); |
| Value* index = Bind(new (Z) ConstantInstr(Smi::ZoneHandle(Z, Smi::New(i)), |
| node->token_pos())); |
| 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, kAlignedAccess, deopt_id, node->token_pos()); |
| Do(store); |
| } |
| ReturnDefinition(ExitTempLocalScope(array_val)); |
| } |
| } |
| |
| |
| 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); |
| } |
| |
| |
| void EffectGraphVisitor::VisitClosureNode(ClosureNode* node) { |
| const Function& function = node->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); |
| ASSERT(node->scope() != NULL); |
| 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); |
| |
| // 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 token position, |
| // and parent function to detect duplicates. |
| const Function& parent = Function::Handle(function.parent_function()); |
| const Function& found_func = Function::Handle( |
| Z, isolate()->LookupClosureFunction(parent, function.token_pos())); |
| if (found_func.IsNull()) { |
| isolate()->AddClosureFunction(function); |
| } |
| } |
| 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& closure_class = |
| Class::ZoneHandle(Z, isolate()->object_store()->closure_class()); |
| ZoneGrowableArray<PushArgumentInstr*>* no_arguments = |
| new (Z) ZoneGrowableArray<PushArgumentInstr*>(0); |
| AllocateObjectInstr* alloc = new (Z) |
| AllocateObjectInstr(node->token_pos(), closure_class, no_arguments); |
| alloc->set_closure_function(function); |
| |
| Value* closure_val = Bind(alloc); |
| { |
| LocalVariable* closure_tmp_var = EnterTempLocalScope(closure_val); |
| // Store instantiator type arguments if scope class is generic. |
| const Type& function_type = Type::ZoneHandle(Z, function.SignatureType()); |
| const Class& scope_cls = Class::ZoneHandle(Z, function_type.type_class()); |
| if (scope_cls.IsGeneric()) { |
| ASSERT(function.Owner() == scope_cls.raw()); |
| Value* closure_tmp_val = |
| Bind(new (Z) LoadLocalInstr(*closure_tmp_var, node->token_pos())); |
| Value* type_arguments = BuildInstantiatorTypeArguments(node->token_pos()); |
| Do(new (Z) StoreInstanceFieldInstr(Closure::instantiator_offset(), |
| closure_tmp_val, type_arguments, |
| kEmitStoreBarrier, node->token_pos())); |
| } |
| |
| // Store function. |
| Value* closure_tmp_val = |
| Bind(new (Z) LoadLocalInstr(*closure_tmp_var, node->token_pos())); |
| 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, node->token_pos())); |
| 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, node->token_pos())); |
| context_tmp_val = |
| Bind(new (Z) LoadLocalInstr(*context_tmp_var, node->token_pos())); |
| Do(new (Z) StoreInstanceFieldInstr( |
| Closure::context_offset(), closure_tmp_val, context_tmp_val, |
| kEmitStoreBarrier, node->token_pos())); |
| Do(ExitTempLocalScope(allocated_context)); |
| } |
| } else { |
| // Store current context in closure. |
| closure_tmp_val = |
| Bind(new (Z) LoadLocalInstr(*closure_tmp_var, node->token_pos())); |
| Value* context = Bind(BuildCurrentContext(node->token_pos())); |
| Do(new (Z) StoreInstanceFieldInstr(Closure::context_offset(), |
| closure_tmp_val, context, |
| kEmitStoreBarrier, node->token_pos())); |
| } |
| ReturnDefinition(ExitTempLocalScope(closure_val)); |
| } |
| } |
| |
| |
| 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) { |
| const TokenPosition token_pos = node->token_pos(); |
| LocalVariable* temp_var = owner()->parsed_function().expression_temp_var(); |
| LoadLocalNode* load_temp = new (Z) LoadLocalNode(token_pos, temp_var); |
| |
| LiteralNode* null_constant = |
| new (Z) LiteralNode(ST(token_pos), Object::null_instance()); |
| ComparisonNode* check_is_null = new (Z) |
| ComparisonNode(ST(token_pos), Token::kEQ, load_temp, null_constant); |
| TestGraphVisitor for_test(owner(), ST(token_pos)); |
| check_is_null->Visit(&for_test); |
| |
| EffectGraphVisitor for_true(owner()); |
| EffectGraphVisitor for_false(owner()); |
| |
| StoreLocalNode* store_null = |
| new (Z) StoreLocalNode(ST(token_pos), temp_var, null_constant); |
| store_null->Visit(&for_true); |
| |
| InstanceCallNode* call = new (Z) InstanceCallNode( |
| token_pos, load_temp, node->function_name(), node->arguments()); |
| StoreLocalNode* store_result = |
| new (Z) StoreLocalNode(ST(token_pos), 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(), node->token_pos())); |
| BuildInstanceCallConditional(node); |
| ReturnDefinition(BuildLoadExprTemp(node->token_pos())); |
| } 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(), node->token_pos())); |
| 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); |
| } |
| } |
| |
| |
| // <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().recognized_kind() != MethodRecognizer::kUnknown) { |
| call->set_result_cid(MethodRecognizer::ResultCid(node->function())); |
| } |
| ReturnDefinition(call); |
| } |
| |
| |
| void EffectGraphVisitor::BuildClosureCall(ClosureCallNode* node, |
| bool result_needed) { |
| ValueGraphVisitor for_closure(owner()); |
| node->closure()->Visit(&for_closure); |
| Append(for_closure); |
| |
| Value* closure_value = for_closure.value(); |
| LocalVariable* tmp_var = EnterTempLocalScope(closure_value); |
| |
| ZoneGrowableArray<PushArgumentInstr*>* arguments = new (Z) |
| ZoneGrowableArray<PushArgumentInstr*>(node->arguments()->length()); |
| Value* closure_val = |
| Bind(new (Z) LoadLocalInstr(*tmp_var, node->token_pos())); |
| PushArgumentInstr* push_closure = PushArgument(closure_val); |
| arguments->Add(push_closure); |
| BuildPushArguments(*node->arguments(), arguments); |
| |
| closure_val = Bind(new (Z) LoadLocalInstr(*tmp_var, node->token_pos())); |
| LoadFieldInstr* function_load = new (Z) LoadFieldInstr( |
| closure_val, Closure::function_offset(), |
| AbstractType::ZoneHandle(Z, AbstractType::null()), node->token_pos()); |
| function_load->set_is_immutable(true); |
| Value* function_val = Bind(function_load); |
| |
| Definition* closure_call = |
| new (Z) ClosureCallInstr(function_val, node, arguments); |
| if (result_needed) { |
| Value* result = Bind(closure_call); |
| Do(new (Z) StoreLocalInstr(*tmp_var, result, ST(node->token_pos()))); |
| } else { |
| Do(closure_call); |
| } |
| ReturnDefinition(ExitTempLocalScope(closure_value)); |
| } |
| |
| |
| void EffectGraphVisitor::VisitClosureCallNode(ClosureCallNode* node) { |
| BuildClosureCall(node, false); |
| } |
| |
| |
| void ValueGraphVisitor::VisitClosureCallNode(ClosureCallNode* node) { |
| BuildClosureCall(node, true); |
| } |
| |
| |
| void EffectGraphVisitor::VisitInitStaticFieldNode(InitStaticFieldNode* node) { |
| Value* field = Bind( |
| new (Z) ConstantInstr(Field::ZoneHandle(Z, node->field().Original()))); |
| AddInstruction(new (Z) InitStaticFieldInstr(field, node->field())); |
| } |
| |
| |
| void EffectGraphVisitor::VisitCloneContextNode(CloneContextNode* node) { |
| Value* context = Bind(BuildCurrentContext(node->token_pos())); |
| Value* clone = Bind(new (Z) CloneContextInstr(node->token_pos(), context)); |
| Do(BuildStoreContext(clone, node->token_pos())); |
| } |
| |
| |
| Value* EffectGraphVisitor::BuildObjectAllocation(ConstructorCallNode* node) { |
| const Class& cls = Class::ZoneHandle(Z, node->constructor().Owner()); |
| const bool cls_is_parameterized = cls.NumTypeArguments() > 0; |
| |
| ZoneGrowableArray<PushArgumentInstr*>* allocate_arguments = new (Z) |
| ZoneGrowableArray<PushArgumentInstr*>(cls_is_parameterized ? 1 : 0); |
| if (cls_is_parameterized) { |
| Value* type_args = BuildInstantiatedTypeArguments(node->token_pos(), |
| node->type_arguments()); |
| allocate_arguments->Add(PushArgument(type_args)); |
| } |
| |
| Definition* allocation = new (Z) AllocateObjectInstr( |
| node->token_pos(), Class::ZoneHandle(Z, node->constructor().Owner()), |
| allocate_arguments); |
| |
| return Bind(allocation); |
| } |
| |
| |
| void EffectGraphVisitor::BuildConstructorCall( |
| ConstructorCallNode* node, |
| PushArgumentInstr* push_alloc_value) { |
| ZoneGrowableArray<PushArgumentInstr*>* arguments = |
| new (Z) ZoneGrowableArray<PushArgumentInstr*>(2); |
| arguments->Add(push_alloc_value); |
| |
| BuildPushArguments(*node->arguments(), arguments); |
| Do(new (Z) StaticCallInstr(node->token_pos(), node->constructor(), |
| node->arguments()->names(), arguments, |
| owner()->ic_data_array())); |
| } |
| |
| |
| static intptr_t GetResultCidOfListFactory(ConstructorCallNode* node) { |
| const Function& function = node->constructor(); |
| const Class& function_class = Class::Handle(function.Owner()); |
| |
| if ((function_class.library() != Library::CoreLibrary()) && |
| (function_class.library() != Library::TypedDataLibrary())) { |
| return kDynamicCid; |
| } |
| |
| if (node->constructor().IsFactory()) { |
| if ((function_class.Name() == Symbols::List().raw()) && |
| (function.name() == Symbols::ListFactory().raw())) { |
| // Special recognition of 'new List()' vs 'new List(n)'. |
| if (node->arguments()->length() == 0) { |
| return kGrowableObjectArrayCid; |
| } |
| return kArrayCid; |
| } |
| return FactoryRecognizer::ResultCid(function); |
| } |
| return kDynamicCid; // Not a known list constructor. |
| } |
| |
| |
| void EffectGraphVisitor::VisitConstructorCallNode(ConstructorCallNode* node) { |
| if (node->constructor().IsFactory()) { |
| ZoneGrowableArray<PushArgumentInstr*>* arguments = |
| new (Z) ZoneGrowableArray<PushArgumentInstr*>(); |
| PushArgumentInstr* push_type_arguments = |
| PushArgument(BuildInstantiatedTypeArguments(node->token_pos(), |
| node->type_arguments())); |
| arguments->Add(push_type_arguments); |
| ASSERT(arguments->length() == 1); |
| BuildPushArguments(*node->arguments(), arguments); |
| StaticCallInstr* call = new (Z) StaticCallInstr( |
| node->token_pos(), node->constructor(), node->arguments()->names(), |
| arguments, owner()->ic_data_array()); |
| const intptr_t result_cid = GetResultCidOfListFactory(node); |
| if (result_cid != kDynamicCid) { |
| call->set_result_cid(result_cid); |
| call->set_is_known_list_constructor(true); |
| // Recognized fixed length array factory must have two arguments: |
| // (0) type-arguments, (1) length. |
| ASSERT(!LoadFieldInstr::IsFixedLengthArrayCid(result_cid) || |
| arguments->length() == 2); |
| } else if (node->constructor().recognized_kind() != |
| MethodRecognizer::kUnknown) { |
| call->set_result_cid(MethodRecognizer::ResultCid(node->constructor())); |
| } |
| ReturnDefinition(call); |
| return; |
| } |
| // t_n contains the allocated and initialized object. |
| // t_n <- AllocateObject(class) |
| // t_n+1 <- ctor-arg |
| // t_n+2... <- constructor arguments start here |
| // StaticCall(constructor, t_n+1, t_n+2, ...) |
| // No need to preserve allocated value (simpler than in ValueGraphVisitor). |
| Value* allocated_value = BuildObjectAllocation(node); |
| PushArgumentInstr* push_allocated_value = PushArgument(allocated_value); |
| BuildConstructorCall(node, push_allocated_value); |
| } |
| |
| |
| Value* EffectGraphVisitor::BuildInstantiator(TokenPosition token_pos) { |
| Function& outer_function = Function::Handle(Z, owner()->function().raw()); |
| while (outer_function.IsLocalFunction()) { |
| outer_function = outer_function.parent_function(); |
| } |
| if (outer_function.IsFactory()) { |
| return NULL; |
| } |
| |
| LocalVariable* instantiator = owner()->parsed_function().instantiator(); |
| ASSERT(instantiator != NULL); |
| Value* result = Bind(BuildLoadLocal(*instantiator, token_pos)); |
| return result; |
| } |
| |
| |
| Value* EffectGraphVisitor::BuildInstantiatorTypeArguments( |
| TokenPosition token_pos) { |
| const Class& instantiator_class = |
| Class::Handle(Z, owner()->function().Owner()); |
| if (!instantiator_class.IsGeneric()) { |
| // The type arguments are compile time constants. |
| TypeArguments& type_arguments = |
| TypeArguments::ZoneHandle(Z, TypeArguments::null()); |
| // Type is temporary. Only its type arguments are preserved. |
| Type& type = Type::Handle(Z, Type::New(instantiator_class, type_arguments, |
| token_pos, Heap::kNew)); |
| type ^= ClassFinalizer::FinalizeType(instantiator_class, type, |
| ClassFinalizer::kFinalize); |
| ASSERT(!type.IsMalformedOrMalbounded()); |
| type_arguments = type.arguments(); |
| type_arguments = type_arguments.Canonicalize(); |
| return Bind(new (Z) ConstantInstr(type_arguments)); |
| } |
| Function& outer_function = Function::Handle(Z, owner()->function().raw()); |
| while (outer_function.IsLocalFunction()) { |
| outer_function = outer_function.parent_function(); |
| } |
| if (outer_function.IsFactory()) { |
| // Note that in the factory case, the instantiator is the first parameter |
| // of the factory, i.e. already a TypeArguments object. |
| LocalVariable* instantiator_var = owner()->parsed_function().instantiator(); |
| ASSERT(instantiator_var != NULL); |
| return Bind(BuildLoadLocal(*instantiator_var, token_pos)); |
| } |
| // The instantiator is the receiver of the caller, which is not a factory. |
| // The receiver cannot be null; extract its TypeArguments object. |
| Value* instantiator = BuildInstantiator(token_pos); |
| intptr_t type_arguments_field_offset = |
| instantiator_class.type_arguments_field_offset(); |
| ASSERT(type_arguments_field_offset != Class::kNoTypeArguments); |
| |
| return Bind(new (Z) LoadFieldInstr( |
| instantiator, type_arguments_field_offset, |
| Type::ZoneHandle(Z, Type::null()), // Not an instance, no type. |
| token_pos)); |
| } |
| |
| |
| Value* EffectGraphVisitor::BuildFunctionTypeArguments(TokenPosition token_pos) { |
| UNIMPLEMENTED(); |
| return NULL; |
| } |
| |
| |
| Value* EffectGraphVisitor::BuildInstantiatedTypeArguments( |
| TokenPosition token_pos, |
| const TypeArguments& type_arguments) { |
| |