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