// Copyright (c) 2013, 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/intermediate_language.h" #include "vm/bigint_operations.h" #include "vm/bit_vector.h" #include "vm/dart_entry.h" #include "vm/flow_graph_allocator.h" #include "vm/flow_graph_builder.h" #include "vm/flow_graph_compiler.h" #include "vm/flow_graph_optimizer.h" #include "vm/locations.h" #include "vm/object.h" #include "vm/object_store.h" #include "vm/os.h" #include "vm/scopes.h" #include "vm/stub_code.h" #include "vm/symbols.h" namespace dart { DEFINE_FLAG(int, max_equality_polymorphic_checks, 32, "Maximum number of polymorphic checks in equality operator," " otherwise use megamorphic dispatch."); DEFINE_FLAG(bool, new_identity_spec, true, "Use new identity check rules for numbers."); DEFINE_FLAG(bool, propagate_ic_data, true, "Propagate IC data from unoptimized to optimized IC calls."); DECLARE_FLAG(bool, enable_type_checks); DECLARE_FLAG(bool, eliminate_type_checks); DECLARE_FLAG(int, max_polymorphic_checks); DECLARE_FLAG(bool, trace_optimization); DECLARE_FLAG(bool, trace_constant_propagation); DECLARE_FLAG(bool, throw_on_javascript_int_overflow); Definition::Definition() : range_(NULL), type_(NULL), temp_index_(-1), ssa_temp_index_(-1), input_use_list_(NULL), env_use_list_(NULL), use_kind_(kValue), // Phis and parameters rely on this default. constant_value_(Object::ZoneHandle(ConstantPropagator::Unknown())) { } ICData* Instruction::GetICData(const Array& ic_data_array) const { ICData& ic_data = ICData::ZoneHandle(); // The deopt_id can be outside the range of the IC data array for // computations added in the optimizing compiler. if (!ic_data_array.IsNull() && (deopt_id_ < ic_data_array.Length())) { ic_data ^= ic_data_array.At(deopt_id_); } return &ic_data; } intptr_t Instruction::Hashcode() const { intptr_t result = tag(); for (intptr_t i = 0; i < InputCount(); ++i) { Value* value = InputAt(i); intptr_t j = value->definition()->ssa_temp_index(); result = result * 31 + j; } return result; } bool Instruction::Equals(Instruction* other) const { if (tag() != other->tag()) return false; for (intptr_t i = 0; i < InputCount(); ++i) { if (!InputAt(i)->Equals(other->InputAt(i))) return false; } return AttributesEqual(other); } bool Value::Equals(Value* other) const { return definition() == other->definition(); } CheckClassInstr::CheckClassInstr(Value* value, intptr_t deopt_id, const ICData& unary_checks) : unary_checks_(unary_checks), null_check_(false) { ASSERT(unary_checks.IsZoneHandle()); // Expected useful check data. ASSERT(!unary_checks_.IsNull()); ASSERT(unary_checks_.NumberOfChecks() > 0); ASSERT(unary_checks_.num_args_tested() == 1); SetInputAt(0, value); deopt_id_ = deopt_id; // Otherwise use CheckSmiInstr. ASSERT((unary_checks_.NumberOfChecks() != 1) || (unary_checks_.GetReceiverClassIdAt(0) != kSmiCid)); } bool CheckClassInstr::AttributesEqual(Instruction* other) const { CheckClassInstr* other_check = other->AsCheckClass(); ASSERT(other_check != NULL); if (unary_checks().NumberOfChecks() != other_check->unary_checks().NumberOfChecks()) { return false; } for (intptr_t i = 0; i < unary_checks().NumberOfChecks(); ++i) { // TODO(fschneider): Make sure ic_data are sorted to hit more cases. if (unary_checks().GetReceiverClassIdAt(i) != other_check->unary_checks().GetReceiverClassIdAt(i)) { return false; } } return true; } EffectSet CheckClassInstr::Dependencies() const { // Externalization of strings via the API can change the class-id. const bool externalizable = unary_checks().HasReceiverClassId(kOneByteStringCid) || unary_checks().HasReceiverClassId(kTwoByteStringCid); return externalizable ? EffectSet::Externalization() : EffectSet::None(); } bool GuardFieldInstr::AttributesEqual(Instruction* other) const { return field().raw() == other->AsGuardField()->field().raw(); } bool AssertAssignableInstr::AttributesEqual(Instruction* other) const { AssertAssignableInstr* other_assert = other->AsAssertAssignable(); ASSERT(other_assert != NULL); // This predicate has to be commutative for DominatorBasedCSE to work. // TODO(fschneider): Eliminate more asserts with subtype relation. return dst_type().raw() == other_assert->dst_type().raw(); } bool StrictCompareInstr::AttributesEqual(Instruction* other) const { StrictCompareInstr* other_op = other->AsStrictCompare(); ASSERT(other_op != NULL); return kind() == other_op->kind(); } bool MathMinMaxInstr::AttributesEqual(Instruction* other) const { MathMinMaxInstr* other_op = other->AsMathMinMax(); ASSERT(other_op != NULL); return (op_kind() == other_op->op_kind()) && (result_cid() == other_op->result_cid()); } bool BinarySmiOpInstr::AttributesEqual(Instruction* other) const { BinarySmiOpInstr* other_op = other->AsBinarySmiOp(); ASSERT(other_op != NULL); return (op_kind() == other_op->op_kind()) && (overflow_ == other_op->overflow_) && (is_truncating_ == other_op->is_truncating_); } EffectSet LoadFieldInstr::Dependencies() const { return immutable_ ? EffectSet::None() : EffectSet::All(); } bool LoadFieldInstr::AttributesEqual(Instruction* other) const { LoadFieldInstr* other_load = other->AsLoadField(); ASSERT(other_load != NULL); if (field() != NULL) { return (other_load->field() != NULL) && (field()->raw() == other_load->field()->raw()); } return (other_load->field() == NULL) && (offset_in_bytes() == other_load->offset_in_bytes()); } EffectSet LoadStaticFieldInstr::Dependencies() const { return StaticField().is_final() ? EffectSet::None() : EffectSet::All(); } bool LoadStaticFieldInstr::AttributesEqual(Instruction* other) const { LoadStaticFieldInstr* other_load = other->AsLoadStaticField(); ASSERT(other_load != NULL); // Assert that the field is initialized. ASSERT(StaticField().value() != Object::sentinel().raw()); ASSERT(StaticField().value() != Object::transition_sentinel().raw()); return StaticField().raw() == other_load->StaticField().raw(); } const Field& LoadStaticFieldInstr::StaticField() const { Field& field = Field::Handle(); field ^= field_value()->BoundConstant().raw(); return field; } EffectSet LoadIndexedInstr::Dependencies() const { return EffectSet::All(); } bool LoadIndexedInstr::AttributesEqual(Instruction* other) const { LoadIndexedInstr* other_load = other->AsLoadIndexed(); ASSERT(other_load != NULL); return class_id() == other_load->class_id(); } ConstantInstr::ConstantInstr(const Object& value) : value_(value) { // Check that the value is not an incorrect Integer representation. ASSERT(!value.IsBigint() || !BigintOperations::FitsIntoSmi(Bigint::Cast(value))); ASSERT(!value.IsBigint() || !BigintOperations::FitsIntoInt64(Bigint::Cast(value))); ASSERT(!value.IsMint() || !Smi::IsValid64(Mint::Cast(value).AsInt64Value())); } bool ConstantInstr::AttributesEqual(Instruction* other) const { ConstantInstr* other_constant = other->AsConstant(); ASSERT(other_constant != NULL); return (value().raw() == other_constant->value().raw()); } // Returns true if the value represents a constant. bool Value::BindsToConstant() const { return definition()->IsConstant(); } // Returns true if the value represents constant null. bool Value::BindsToConstantNull() const { ConstantInstr* constant = definition()->AsConstant(); return (constant != NULL) && constant->value().IsNull(); } const Object& Value::BoundConstant() const { ASSERT(BindsToConstant()); ConstantInstr* constant = definition()->AsConstant(); ASSERT(constant != NULL); return constant->value(); } GraphEntryInstr::GraphEntryInstr(const ParsedFunction& parsed_function, TargetEntryInstr* normal_entry, intptr_t osr_id) : BlockEntryInstr(0, CatchClauseNode::kInvalidTryIndex), parsed_function_(parsed_function), normal_entry_(normal_entry), catch_entries_(), initial_definitions_(), osr_id_(osr_id), spill_slot_count_(0), fixed_slot_count_(0) { } ConstantInstr* GraphEntryInstr::constant_null() { ASSERT(initial_definitions_.length() > 0); for (intptr_t i = 0; i < initial_definitions_.length(); ++i) { ConstantInstr* defn = initial_definitions_[i]->AsConstant(); if (defn != NULL && defn->value().IsNull()) return defn; } UNREACHABLE(); return NULL; } CatchBlockEntryInstr* GraphEntryInstr::GetCatchEntry(intptr_t index) { // TODO(fschneider): Sort the catch entries by catch_try_index to avoid // searching. for (intptr_t i = 0; i < catch_entries_.length(); ++i) { if (catch_entries_[i]->catch_try_index() == index) return catch_entries_[i]; } return NULL; } static bool StartsWith(const String& name, const char* prefix, intptr_t n) { ASSERT(name.IsOneByteString()); if (name.Length() < n) { return false; } for (intptr_t i = 0; i < n; i++) { if (name.CharAt(i) != prefix[i]) { return false; } } return true; } static bool CompareNames(const Library& lib, const char* test_name, const String& name) { const char* kPrivateGetterPrefix = "get:_"; const char* kPrivateSetterPrefix = "set:_"; if (test_name[0] == '_') { if (name.CharAt(0) != '_') { return false; } } else if (strncmp(test_name, kPrivateGetterPrefix, strlen(kPrivateGetterPrefix)) == 0) { if (!StartsWith(name, kPrivateGetterPrefix, strlen(kPrivateGetterPrefix))) { return false; } } else if (strncmp(test_name, kPrivateSetterPrefix, strlen(kPrivateSetterPrefix)) == 0) { if (!StartsWith(name, kPrivateSetterPrefix, strlen(kPrivateSetterPrefix))) { return false; } } else { // Compare without mangling. return name.Equals(test_name); } // Both names are private. Mangle test_name before comparison. const String& test_name_symbol = String::Handle(Symbols::New(test_name)); return String::Handle(lib.PrivateName(test_name_symbol)).Equals(name); } static bool IsRecognizedLibrary(const Library& library) { // List of libraries where methods can be recognized. return (library.raw() == Library::CoreLibrary()) || (library.raw() == Library::MathLibrary()) || (library.raw() == Library::TypedDataLibrary()) || (library.raw() == Library::CollectionDevLibrary()); } MethodRecognizer::Kind MethodRecognizer::RecognizeKind( const Function& function) { const Class& function_class = Class::Handle(function.Owner()); const Library& lib = Library::Handle(function_class.library()); if (!IsRecognizedLibrary(lib)) { return kUnknown; } const String& function_name = String::Handle(function.name()); const String& class_name = String::Handle(function_class.Name()); #define RECOGNIZE_FUNCTION(test_class_name, test_function_name, enum_name, fp) \ if (CompareNames(lib, #test_function_name, function_name) && \ CompareNames(lib, #test_class_name, class_name)) { \ ASSERT(function.CheckSourceFingerprint(fp)); \ return k##enum_name; \ } RECOGNIZED_LIST(RECOGNIZE_FUNCTION) #undef RECOGNIZE_FUNCTION return kUnknown; } bool MethodRecognizer::AlwaysInline(const Function& function) { const Class& function_class = Class::Handle(function.Owner()); const Library& lib = Library::Handle(function_class.library()); if (!IsRecognizedLibrary(lib)) { return false; } const String& function_name = String::Handle(function.name()); const String& class_name = String::Handle(function_class.Name()); #define RECOGNIZE_FUNCTION(test_class_name, test_function_name, enum_name, fp) \ if (CompareNames(lib, #test_function_name, function_name) && \ CompareNames(lib, #test_class_name, class_name)) { \ ASSERT(function.CheckSourceFingerprint(fp)); \ return true; \ } INLINE_WHITE_LIST(RECOGNIZE_FUNCTION) #undef RECOGNIZE_FUNCTION return false; } const char* MethodRecognizer::KindToCString(Kind kind) { #define KIND_TO_STRING(class_name, function_name, enum_name, fp) \ if (kind == k##enum_name) return #enum_name; RECOGNIZED_LIST(KIND_TO_STRING) #undef KIND_TO_STRING return "?"; } // ==== Support for visiting flow graphs. #define DEFINE_ACCEPT(ShortName) \ void ShortName##Instr::Accept(FlowGraphVisitor* visitor) { \ visitor->Visit##ShortName(this); \ } FOR_EACH_INSTRUCTION(DEFINE_ACCEPT) #undef DEFINE_ACCEPT void Instruction::SetEnvironment(Environment* deopt_env) { intptr_t use_index = 0; for (Environment::DeepIterator it(deopt_env); !it.Done(); it.Advance()) { Value* use = it.CurrentValue(); use->set_instruction(this); use->set_use_index(use_index++); } env_ = deopt_env; } void Instruction::RemoveEnvironment() { for (Environment::DeepIterator it(env()); !it.Done(); it.Advance()) { it.CurrentValue()->RemoveFromUseList(); } env_ = NULL; } Instruction* Instruction::RemoveFromGraph(bool return_previous) { ASSERT(!IsBlockEntry()); ASSERT(!IsControl()); ASSERT(!IsThrow()); ASSERT(!IsReturn()); ASSERT(!IsReThrow()); ASSERT(!IsGoto()); ASSERT(previous() != NULL); // We cannot assert that the instruction, if it is a definition, has no // uses. This function is used to remove instructions from the graph and // reinsert them elsewhere (e.g., hoisting). Instruction* prev_instr = previous(); Instruction* next_instr = next(); ASSERT(next_instr != NULL); ASSERT(!next_instr->IsBlockEntry()); prev_instr->LinkTo(next_instr); UnuseAllInputs(); // Reset the successor and previous instruction to indicate that the // instruction is removed from the graph. set_previous(NULL); set_next(NULL); return return_previous ? prev_instr : next_instr; } void Instruction::InsertAfter(Instruction* prev) { ASSERT(previous_ == NULL); ASSERT(next_ == NULL); previous_ = prev; next_ = prev->next_; next_->previous_ = this; previous_->next_ = this; // Update def-use chains whenever instructions are added to the graph // after initial graph construction. for (intptr_t i = InputCount() - 1; i >= 0; --i) { Value* input = InputAt(i); input->definition()->AddInputUse(input); } } Instruction* Instruction::AppendInstruction(Instruction* tail) { LinkTo(tail); // Update def-use chains whenever instructions are added to the graph // after initial graph construction. for (intptr_t i = tail->InputCount() - 1; i >= 0; --i) { Value* input = tail->InputAt(i); input->definition()->AddInputUse(input); } return tail; } BlockEntryInstr* Instruction::GetBlock() const { // TODO(fschneider): Implement a faster way to get the block of an // instruction. ASSERT(previous() != NULL); Instruction* result = previous(); while (!result->IsBlockEntry()) result = result->previous(); return result->AsBlockEntry(); } void ForwardInstructionIterator::RemoveCurrentFromGraph() { current_ = current_->RemoveFromGraph(true); // Set current_ to previous. } // Default implementation of visiting basic blocks. Can be overridden. void FlowGraphVisitor::VisitBlocks() { ASSERT(current_iterator_ == NULL); for (intptr_t i = 0; i < block_order_.length(); ++i) { BlockEntryInstr* entry = block_order_[i]; entry->Accept(this); ForwardInstructionIterator it(entry); current_iterator_ = ⁢ for (; !it.Done(); it.Advance()) { it.Current()->Accept(this); } current_iterator_ = NULL; } } bool Value::NeedsStoreBuffer() { if (Type()->IsNull() || (Type()->ToNullableCid() == kSmiCid) || (Type()->ToNullableCid() == kBoolCid)) { return false; } return !BindsToConstant(); } void JoinEntryInstr::AddPredecessor(BlockEntryInstr* predecessor) { // Require the predecessors to be sorted by block_id to make managing // their corresponding phi inputs simpler. intptr_t pred_id = predecessor->block_id(); intptr_t index = 0; while ((index < predecessors_.length()) && (predecessors_[index]->block_id() < pred_id)) { ++index; } #if defined(DEBUG) for (intptr_t i = index; i < predecessors_.length(); ++i) { ASSERT(predecessors_[i]->block_id() != pred_id); } #endif predecessors_.InsertAt(index, predecessor); } intptr_t JoinEntryInstr::IndexOfPredecessor(BlockEntryInstr* pred) const { for (intptr_t i = 0; i < predecessors_.length(); ++i) { if (predecessors_[i] == pred) return i; } return -1; } void Value::AddToList(Value* value, Value** list) { Value* next = *list; *list = value; value->set_next_use(next); value->set_previous_use(NULL); if (next != NULL) next->set_previous_use(value); } void Value::RemoveFromUseList() { Definition* def = definition(); Value* next = next_use(); if (this == def->input_use_list()) { def->set_input_use_list(next); if (next != NULL) next->set_previous_use(NULL); } else if (this == def->env_use_list()) { def->set_env_use_list(next); if (next != NULL) next->set_previous_use(NULL); } else { Value* prev = previous_use(); prev->set_next_use(next); if (next != NULL) next->set_previous_use(prev); } set_previous_use(NULL); set_next_use(NULL); } // True if the definition has a single input use and is used only in // environments at the same instruction as that input use. bool Definition::HasOnlyUse(Value* use) const { if ((input_use_list() != use) || (use->next_use() != NULL)) return false; Instruction* target = use->instruction(); for (Value::Iterator it(env_use_list()); !it.Done(); it.Advance()) { if (it.Current()->instruction() != target) return false; } return true; } void Definition::ReplaceUsesWith(Definition* other) { ASSERT(other != NULL); ASSERT(this != other); Value* current = NULL; Value* next = input_use_list(); if (next != NULL) { // Change all the definitions. while (next != NULL) { current = next; current->set_definition(other); next = current->next_use(); } // Concatenate the lists. next = other->input_use_list(); current->set_next_use(next); if (next != NULL) next->set_previous_use(current); other->set_input_use_list(input_use_list()); set_input_use_list(NULL); } // Repeat for environment uses. current = NULL; next = env_use_list(); if (next != NULL) { while (next != NULL) { current = next; current->set_definition(other); next = current->next_use(); } next = other->env_use_list(); current->set_next_use(next); if (next != NULL) next->set_previous_use(current); other->set_env_use_list(env_use_list()); set_env_use_list(NULL); } } void Instruction::UnuseAllInputs() { for (intptr_t i = InputCount() - 1; i >= 0; --i) { InputAt(i)->RemoveFromUseList(); } for (Environment::DeepIterator it(env()); !it.Done(); it.Advance()) { it.CurrentValue()->RemoveFromUseList(); } } void Instruction::InheritDeoptTargetAfter(Instruction* other) { ASSERT(other->env() != NULL); deopt_id_ = Isolate::ToDeoptAfter(other->deopt_id_); other->env()->DeepCopyTo(this); env()->set_deopt_id(deopt_id_); } void Instruction::InheritDeoptTarget(Instruction* other) { ASSERT(other->env() != NULL); deopt_id_ = other->deopt_id_; other->env()->DeepCopyTo(this); env()->set_deopt_id(deopt_id_); } void BranchInstr::InheritDeoptTarget(Instruction* other) { Instruction::InheritDeoptTarget(other); comparison()->SetDeoptId(GetDeoptId()); } void Definition::ReplaceWith(Definition* other, ForwardInstructionIterator* iterator) { // Record other's input uses. for (intptr_t i = other->InputCount() - 1; i >= 0; --i) { Value* input = other->InputAt(i); input->definition()->AddInputUse(input); } // Take other's environment from this definition. ASSERT(other->env() == NULL); other->SetEnvironment(env()); env_ = NULL; // Replace all uses of this definition with other. ReplaceUsesWith(other); // Reuse this instruction's SSA name for other. ASSERT(!other->HasSSATemp()); if (HasSSATemp()) other->set_ssa_temp_index(ssa_temp_index()); // Finally insert the other definition in place of this one in the graph. previous()->LinkTo(other); if ((iterator != NULL) && (this == iterator->Current())) { // Remove through the iterator. other->LinkTo(this); iterator->RemoveCurrentFromGraph(); } else { other->LinkTo(next()); // Remove this definition's input uses. UnuseAllInputs(); } set_previous(NULL); set_next(NULL); } BranchInstr::BranchInstr(ComparisonInstr* comparison, bool is_checked) : comparison_(comparison), is_checked_(is_checked), constrained_type_(NULL), constant_target_(NULL) { for (intptr_t i = comparison->InputCount() - 1; i >= 0; --i) { comparison->InputAt(i)->set_instruction(this); } } void BranchInstr::RawSetInputAt(intptr_t i, Value* value) { comparison()->RawSetInputAt(i, value); } // A misleadingly named function for use in template functions that replace // both definitions with definitions and branch comparisons with // comparisons. In the branch case, leave the branch intact and replace its // comparison with a new comparison not currently in the graph. void BranchInstr::ReplaceWith(ComparisonInstr* other, ForwardInstructionIterator* ignored) { SetComparison(other); } void BranchInstr::SetComparison(ComparisonInstr* comp) { for (intptr_t i = comp->InputCount() - 1; i >= 0; --i) { Value* input = comp->InputAt(i); input->definition()->AddInputUse(input); input->set_instruction(this); } // There should be no need to copy or unuse an environment. ASSERT(comparison()->env() == NULL); // Remove the current comparison's input uses. comparison()->UnuseAllInputs(); ASSERT(!comp->HasUses()); comparison_ = comp; } // ==== Postorder graph traversal. static bool IsMarked(BlockEntryInstr* block, GrowableArray* preorder) { // Detect that a block has been visited as part of the current // DiscoverBlocks (we can call DiscoverBlocks multiple times). The block // will be 'marked' by (1) having a preorder number in the range of the // preorder array and (2) being in the preorder array at that index. intptr_t i = block->preorder_number(); return (i >= 0) && (i < preorder->length()) && ((*preorder)[i] == block); } // Base class implementation used for JoinEntry and TargetEntry. void BlockEntryInstr::DiscoverBlocks( BlockEntryInstr* predecessor, GrowableArray* preorder, GrowableArray* postorder, GrowableArray* parent, intptr_t variable_count, intptr_t fixed_parameter_count) { // If this block has a predecessor (i.e., is not the graph entry) we can // assume the preorder array is non-empty. ASSERT((predecessor == NULL) || !preorder->is_empty()); // Blocks with a single predecessor cannot have been reached before. ASSERT(IsJoinEntry() || !IsMarked(this, preorder)); // 1. If the block has already been reached, add current_block as a // basic-block predecessor and we are done. if (IsMarked(this, preorder)) { ASSERT(predecessor != NULL); AddPredecessor(predecessor); return; } // 2. Otherwise, clear the predecessors which might have been computed on // some earlier call to DiscoverBlocks and record this predecessor. ClearPredecessors(); if (predecessor != NULL) AddPredecessor(predecessor); // 3. The predecessor is the spanning-tree parent. The graph entry has no // parent, indicated by -1. intptr_t parent_number = (predecessor == NULL) ? -1 : predecessor->preorder_number(); parent->Add(parent_number); // 4. Assign the preorder number and add the block entry to the list. set_preorder_number(preorder->length()); preorder->Add(this); // The preorder and parent arrays are indexed by // preorder block number, so they should stay in lockstep. ASSERT(preorder->length() == parent->length()); // 5. Iterate straight-line successors to record assigned variables and // find the last instruction in the block. The graph entry block consists // of only the entry instruction, so that is the last instruction in the // block. Instruction* last = this; for (ForwardInstructionIterator it(this); !it.Done(); it.Advance()) { last = it.Current(); } set_last_instruction(last); // Visit the block's successors in reverse so that they appear forwards // the reverse postorder block ordering. for (intptr_t i = last->SuccessorCount() - 1; i >= 0; --i) { last->SuccessorAt(i)->DiscoverBlocks(this, preorder, postorder, parent, variable_count, fixed_parameter_count); } // 6. Assign postorder number and add the block entry to the list. set_postorder_number(postorder->length()); postorder->Add(this); } bool BlockEntryInstr::PruneUnreachable(FlowGraphBuilder* builder, GraphEntryInstr* graph_entry, intptr_t osr_id, BitVector* block_marks) { // Search for the instruction with the OSR id. Use a depth first search // because basic blocks have not been discovered yet. Prune unreachable // blocks by replacing the normal entry with a jump to the block // containing the OSR entry point. // Do not visit blocks more than once. if (block_marks->Contains(block_id())) return false; block_marks->Add(block_id()); // Search this block for the OSR id. Instruction* instr = this; for (ForwardInstructionIterator it(this); !it.Done(); it.Advance()) { instr = it.Current(); if (instr->GetDeoptId() == osr_id) { // Sanity check that we found a stack check instruction. ASSERT(instr->IsCheckStackOverflow()); // Loop stack check checks are always in join blocks so that they can // be the target of a goto. ASSERT(IsJoinEntry()); // The instruction should be the first instruction in the block so // we can simply jump to the beginning of the block. ASSERT(instr->previous() == this); GotoInstr* goto_join = new GotoInstr(AsJoinEntry()); goto_join->deopt_id_ = deopt_id_; graph_entry->normal_entry()->LinkTo(goto_join); return true; } } // Recursively search the successors. for (intptr_t i = instr->SuccessorCount() - 1; i >= 0; --i) { if (instr->SuccessorAt(i)->PruneUnreachable(builder, graph_entry, osr_id, block_marks)) { return true; } } return false; } bool BlockEntryInstr::Dominates(BlockEntryInstr* other) const { // TODO(fschneider): Make this faster by e.g. storing dominators for each // block while computing the dominator tree. ASSERT(other != NULL); BlockEntryInstr* current = other; while (current != NULL && current != this) { current = current->dominator(); } return current == this; } // Helper to mutate the graph during inlining. This block should be // replaced with new_block as a predecessor of all of this block's // successors. For each successor, the predecessors will be reordered // to preserve block-order sorting of the predecessors as well as the // phis if the successor is a join. void BlockEntryInstr::ReplaceAsPredecessorWith(BlockEntryInstr* new_block) { // Set the last instruction of the new block to that of the old block. Instruction* last = last_instruction(); new_block->set_last_instruction(last); // For each successor, update the predecessors. for (intptr_t sidx = 0; sidx < last->SuccessorCount(); ++sidx) { // If the successor is a target, update its predecessor. TargetEntryInstr* target = last->SuccessorAt(sidx)->AsTargetEntry(); if (target != NULL) { target->predecessor_ = new_block; continue; } // If the successor is a join, update each predecessor and the phis. JoinEntryInstr* join = last->SuccessorAt(sidx)->AsJoinEntry(); ASSERT(join != NULL); // Find the old predecessor index. intptr_t old_index = join->IndexOfPredecessor(this); intptr_t pred_count = join->PredecessorCount(); ASSERT(old_index >= 0); ASSERT(old_index < pred_count); // Find the new predecessor index while reordering the predecessors. intptr_t new_id = new_block->block_id(); intptr_t new_index = old_index; if (block_id() < new_id) { // Search upwards, bubbling down intermediate predecessors. for (; new_index < pred_count - 1; ++new_index) { if (join->predecessors_[new_index + 1]->block_id() > new_id) break; join->predecessors_[new_index] = join->predecessors_[new_index + 1]; } } else { // Search downwards, bubbling up intermediate predecessors. for (; new_index > 0; --new_index) { if (join->predecessors_[new_index - 1]->block_id() < new_id) break; join->predecessors_[new_index] = join->predecessors_[new_index - 1]; } } join->predecessors_[new_index] = new_block; // If the new and old predecessor index match there is nothing to update. if ((join->phis() == NULL) || (old_index == new_index)) return; // Otherwise, reorder the predecessor uses in each phi. for (PhiIterator it(join); !it.Done(); it.Advance()) { PhiInstr* phi = it.Current(); ASSERT(phi != NULL); ASSERT(pred_count == phi->InputCount()); // Save the predecessor use. Value* pred_use = phi->InputAt(old_index); // Move uses between old and new. intptr_t step = (old_index < new_index) ? 1 : -1; for (intptr_t use_idx = old_index; use_idx != new_index; use_idx += step) { phi->SetInputAt(use_idx, phi->InputAt(use_idx + step)); } // Write the predecessor use. phi->SetInputAt(new_index, pred_use); } } } void JoinEntryInstr::InsertPhi(intptr_t var_index, intptr_t var_count) { // Lazily initialize the array of phis. // Currently, phis are stored in a sparse array that holds the phi // for variable with index i at position i. // TODO(fschneider): Store phis in a more compact way. if (phis_ == NULL) { phis_ = new ZoneGrowableArray(var_count); for (intptr_t i = 0; i < var_count; i++) { phis_->Add(NULL); } } ASSERT((*phis_)[var_index] == NULL); (*phis_)[var_index] = new PhiInstr(this, PredecessorCount()); } void JoinEntryInstr::InsertPhi(PhiInstr* phi) { // Lazily initialize the array of phis. if (phis_ == NULL) { phis_ = new ZoneGrowableArray(1); } phis_->Add(phi); } void JoinEntryInstr::RemovePhi(PhiInstr* phi) { ASSERT(phis_ != NULL); for (intptr_t index = 0; index < phis_->length(); ++index) { if (phi == (*phis_)[index]) { (*phis_)[index] = phis_->Last(); phis_->RemoveLast(); return; } } } void JoinEntryInstr::RemoveDeadPhis(Definition* replacement) { if (phis_ == NULL) return; intptr_t to_index = 0; for (intptr_t from_index = 0; from_index < phis_->length(); ++from_index) { PhiInstr* phi = (*phis_)[from_index]; if (phi != NULL) { if (phi->is_alive()) { (*phis_)[to_index++] = phi; for (intptr_t i = phi->InputCount() - 1; i >= 0; --i) { Value* input = phi->InputAt(i); input->definition()->AddInputUse(input); } } else { phi->ReplaceUsesWith(replacement); } } } if (to_index == 0) { phis_ = NULL; } else { phis_->TruncateTo(to_index); } } intptr_t Instruction::SuccessorCount() const { return 0; } BlockEntryInstr* Instruction::SuccessorAt(intptr_t index) const { // Called only if index is in range. Only control-transfer instructions // can have non-zero successor counts and they override this function. UNREACHABLE(); return NULL; } intptr_t GraphEntryInstr::SuccessorCount() const { return 1 + catch_entries_.length(); } BlockEntryInstr* GraphEntryInstr::SuccessorAt(intptr_t index) const { if (index == 0) return normal_entry_; return catch_entries_[index - 1]; } intptr_t ControlInstruction::SuccessorCount() const { return 2; } BlockEntryInstr* ControlInstruction::SuccessorAt(intptr_t index) const { if (index == 0) return true_successor_; if (index == 1) return false_successor_; UNREACHABLE(); return NULL; } intptr_t GotoInstr::SuccessorCount() const { return 1; } BlockEntryInstr* GotoInstr::SuccessorAt(intptr_t index) const { ASSERT(index == 0); return successor(); } void Instruction::Goto(JoinEntryInstr* entry) { LinkTo(new GotoInstr(entry)); } bool EqualityCompareInstr::IsPolymorphic() const { return HasICData() && (ic_data()->NumberOfChecks() > 0) && (ic_data()->NumberOfChecks() <= FLAG_max_polymorphic_checks); } bool EqualityCompareInstr::IsCheckedStrictEqual() const { if (!HasICData()) return false; return ic_data()->AllTargetsHaveSameOwner(kInstanceCid) && (unary_ic_data_->NumberOfChecks() <= FLAG_max_equality_polymorphic_checks); } bool BinarySmiOpInstr::CanDeoptimize() const { if (FLAG_throw_on_javascript_int_overflow && (Smi::kBits > 32)) { // If Smi's are bigger than 32-bits, then the instruction could deoptimize // if the result is too big. return true; } switch (op_kind()) { case Token::kBIT_AND: case Token::kBIT_OR: case Token::kBIT_XOR: return false; case Token::kSHR: { // Can't deopt if shift-count is known positive. Range* right_range = this->right()->definition()->range(); return (right_range == NULL) || !right_range->IsWithin(0, RangeBoundary::kPlusInfinity); } case Token::kSHL: { Range* right_range = this->right()->definition()->range(); if ((right_range != NULL) && is_truncating()) { // Can deoptimize if right can be negative. return !right_range->IsWithin(0, RangeBoundary::kPlusInfinity); } return true; } default: return overflow_; } } bool BinarySmiOpInstr::RightIsPowerOfTwoConstant() const { if (!right()->definition()->IsConstant()) return false; const Object& constant = right()->definition()->AsConstant()->value(); if (!constant.IsSmi()) return false; const intptr_t int_value = Smi::Cast(constant).Value(); if (int_value == 0) return false; return Utils::IsPowerOfTwo(Utils::Abs(int_value)); } static bool ToIntegerConstant(Value* value, intptr_t* result) { if (!value->BindsToConstant()) { if (value->definition()->IsUnboxDouble()) { return ToIntegerConstant(value->definition()->AsUnboxDouble()->value(), result); } return false; } const Object& constant = value->BoundConstant(); if (constant.IsDouble()) { const Double& double_constant = Double::Cast(constant); *result = static_cast(double_constant.value()); return (static_cast(*result) == double_constant.value()); } else if (constant.IsSmi()) { *result = Smi::Cast(constant).Value(); return true; } return false; } static Definition* CanonicalizeCommutativeArithmetic(Token::Kind op, intptr_t cid, Value* left, Value* right) { ASSERT((cid == kSmiCid) || (cid == kDoubleCid) || (cid == kMintCid)); intptr_t left_value; if (!ToIntegerConstant(left, &left_value)) { return NULL; } switch (op) { case Token::kMUL: if (left_value == 1) { if ((cid == kDoubleCid) && (right->definition()->representation() != kUnboxedDouble)) { // Can't yet apply the equivalence because representation selection // did not run yet. We need it to guarantee that right value is // correctly coerced to double. The second canonicalization pass // will apply this equivalence. return NULL; } else { return right->definition(); } } else if ((left_value == 0) && (cid != kDoubleCid)) { // Can't apply this equivalence to double operation because // 0.0 * NaN is NaN not 0.0. return left->definition(); } break; case Token::kADD: if ((left_value == 0) && (cid != kDoubleCid)) { // Can't apply this equivalence to double operations because // 0.0 + (-0.0) is 0.0 not -0.0. return right->definition(); } break; case Token::kBIT_AND: ASSERT(cid != kDoubleCid); if (left_value == 0) { return left->definition(); } else if (left_value == -1) { return right->definition(); } break; case Token::kBIT_OR: ASSERT(cid != kDoubleCid); if (left_value == 0) { return right->definition(); } else if (left_value == -1) { return left->definition(); } break; case Token::kBIT_XOR: ASSERT(cid != kDoubleCid); if (left_value == 0) { return right->definition(); } break; default: break; } return NULL; } Definition* BinaryDoubleOpInstr::Canonicalize(FlowGraph* flow_graph) { Definition* result = NULL; result = CanonicalizeCommutativeArithmetic(op_kind(), kDoubleCid, left(), right()); if (result != NULL) { return result; } result = CanonicalizeCommutativeArithmetic(op_kind(), kDoubleCid, right(), left()); if (result != NULL) { return result; } return this; } Definition* BinarySmiOpInstr::Canonicalize(FlowGraph* flow_graph) { Definition* result = NULL; result = CanonicalizeCommutativeArithmetic(op_kind(), kSmiCid, left(), right()); if (result != NULL) { return result; } result = CanonicalizeCommutativeArithmetic(op_kind(), kSmiCid, right(), left()); if (result != NULL) { return result; } return this; } Definition* BinaryMintOpInstr::Canonicalize(FlowGraph* flow_graph) { Definition* result = NULL; result = CanonicalizeCommutativeArithmetic(op_kind(), kMintCid, left(), right()); if (result != NULL) { return result; } result = CanonicalizeCommutativeArithmetic(op_kind(), kMintCid, right(), left()); if (result != NULL) { return result; } return this; } // Optimizations that eliminate or simplify individual instructions. Instruction* Instruction::Canonicalize(FlowGraph* flow_graph) { return this; } Definition* Definition::Canonicalize(FlowGraph* flow_graph) { return this; } bool LoadFieldInstr::IsImmutableLengthLoad() const { switch (recognized_kind()) { case MethodRecognizer::kObjectArrayLength: case MethodRecognizer::kImmutableArrayLength: case MethodRecognizer::kTypedDataLength: case MethodRecognizer::kStringBaseLength: return true; default: return false; } } MethodRecognizer::Kind LoadFieldInstr::RecognizedKindFromArrayCid( intptr_t cid) { if (RawObject::IsTypedDataClassId(cid) || RawObject::IsExternalTypedDataClassId(cid)) { return MethodRecognizer::kTypedDataLength; } switch (cid) { case kArrayCid: return MethodRecognizer::kObjectArrayLength; case kImmutableArrayCid: return MethodRecognizer::kImmutableArrayLength; case kGrowableObjectArrayCid: return MethodRecognizer::kGrowableArrayLength; default: UNREACHABLE(); return MethodRecognizer::kUnknown; } } bool LoadFieldInstr::IsFixedLengthArrayCid(intptr_t cid) { if (RawObject::IsTypedDataClassId(cid) || RawObject::IsExternalTypedDataClassId(cid)) { return true; } switch (cid) { case kArrayCid: case kImmutableArrayCid: return true; default: return false; } } Definition* ConstantInstr::Canonicalize(FlowGraph* flow_graph) { return HasUses() ? this : NULL; } Definition* LoadFieldInstr::Canonicalize(FlowGraph* flow_graph) { if (!IsImmutableLengthLoad()) return this; // For fixed length arrays if the array is the result of a known constructor // call we can replace the length load with the length argument passed to // the constructor. StaticCallInstr* call = instance()->definition()->AsStaticCall(); if ((call != NULL) && call->is_known_list_constructor() && IsFixedLengthArrayCid(call->Type()->ToCid())) { return call->ArgumentAt(1); } return this; } Definition* AssertBooleanInstr::Canonicalize(FlowGraph* flow_graph) { if (FLAG_eliminate_type_checks && (value()->Type()->ToCid() == kBoolCid)) { return value()->definition(); } return this; } Definition* AssertAssignableInstr::Canonicalize(FlowGraph* flow_graph) { if (FLAG_eliminate_type_checks && value()->Type()->IsAssignableTo(dst_type())) { return value()->definition(); } // For uninstantiated target types: If the instantiator type arguments // are constant, instantiate the target type here. if (dst_type().IsInstantiated()) return this; ConstantInstr* constant_type_args = instantiator_type_arguments()->definition()->AsConstant(); if (constant_type_args != NULL && !constant_type_args->value().IsNull() && constant_type_args->value().IsTypeArguments()) { const TypeArguments& instantiator_type_args = TypeArguments::Cast(constant_type_args->value()); const AbstractType& new_dst_type = AbstractType::Handle( dst_type().InstantiateFrom(instantiator_type_args, NULL)); set_dst_type(AbstractType::ZoneHandle(new_dst_type.Canonicalize())); ConstantInstr* null_constant = flow_graph->constant_null(); instantiator_type_arguments()->BindTo(null_constant); } return this; } Definition* BoxDoubleInstr::Canonicalize(FlowGraph* flow_graph) { if (input_use_list() == NULL) { // Environments can accomodate any representation. No need to box. return value()->definition(); } // Fold away BoxDouble(UnboxDouble(v)) if value is known to be double. UnboxDoubleInstr* defn = value()->definition()->AsUnboxDouble(); if ((defn != NULL) && (defn->value()->Type()->ToCid() == kDoubleCid)) { return defn->value()->definition(); } return this; } Definition* UnboxDoubleInstr::Canonicalize(FlowGraph* flow_graph) { // Fold away UnboxDouble(BoxDouble(v)). BoxDoubleInstr* defn = value()->definition()->AsBoxDouble(); return (defn != NULL) ? defn->value()->definition() : this; } Instruction* BranchInstr::Canonicalize(FlowGraph* flow_graph) { // Only handle strict-compares. if (comparison()->IsStrictCompare()) { Definition* replacement = comparison()->Canonicalize(flow_graph); if ((replacement == comparison()) || (replacement == NULL)) { return this; } ComparisonInstr* comp = replacement->AsComparison(); if ((comp == NULL) || comp->CanDeoptimize()) { return this; } // Check that comparison is not serving as a pending deoptimization target // for conversions. for (intptr_t i = 0; i < comp->InputCount(); i++) { if (comp->RequiredInputRepresentation(i) != comp->InputAt(i)->definition()->representation()) { return this; } } // Replace the comparison if the replacement is used at this branch, // and has exactly one use. Value* use = comp->input_use_list(); if ((use->instruction() == this) && comp->HasOnlyUse(use)) { RemoveEnvironment(); flow_graph->CopyDeoptTarget(this, comp); comp->RemoveFromGraph(); SetComparison(comp); if (FLAG_trace_optimization) { OS::Print("Merging comparison v%"Pd"\n", comp->ssa_temp_index()); } // Clear the comparison's temp index and ssa temp index since the // value of the comparison is not used outside the branch anymore. ASSERT(comp->input_use_list() == NULL); comp->ClearSSATempIndex(); comp->ClearTempIndex(); } } return this; } Definition* StrictCompareInstr::Canonicalize(FlowGraph* flow_graph) { if (!right()->BindsToConstant()) { return this; } const Object& right_constant = right()->BoundConstant(); Definition* left_defn = left()->definition(); // TODO(fschneider): Handle other cases: e === false and e !== true/false. // Handles e === true. if ((kind() == Token::kEQ_STRICT) && (right_constant.raw() == Bool::True().raw()) && (left()->Type()->ToCid() == kBoolCid)) { // Return left subexpression as the replacement for this instruction. return left_defn; } // x = (a === b); y = x !== true; -> y = a !== b. // In order to merge two strict comares, 'left_strict' must have only one use. // Do not check left's cid as it is required to be a strict compare. StrictCompareInstr* left_strict = left_defn->AsStrictCompare(); if ((kind() == Token::kNE_STRICT) && (right_constant.raw() == Bool::True().raw()) && (left_strict != NULL) && (left_strict->HasOnlyUse(left()))) { left_strict->set_kind(Token::NegateComparison(left_strict->kind())); return left_strict; } return this; } Instruction* CheckClassInstr::Canonicalize(FlowGraph* flow_graph) { // TODO(vegorov): Replace class checks with null checks when ToNullableCid // matches. const intptr_t value_cid = value()->Type()->ToCid(); if (value_cid == kDynamicCid) { return this; } return unary_checks().HasReceiverClassId(value_cid) ? NULL : this; } Instruction* GuardFieldInstr::Canonicalize(FlowGraph* flow_graph) { if (field().guarded_cid() == kDynamicCid) { return NULL; // Nothing to guard. } if (field().is_nullable() && value()->Type()->IsNull()) { return NULL; } const intptr_t cid = field().is_nullable() ? value()->Type()->ToNullableCid() : value()->Type()->ToCid(); if (field().guarded_cid() == cid) { return NULL; // Value is guaranteed to have this cid. } return this; } Instruction* CheckSmiInstr::Canonicalize(FlowGraph* flow_graph) { return (value()->Type()->ToCid() == kSmiCid) ? NULL : this; } Instruction* CheckEitherNonSmiInstr::Canonicalize(FlowGraph* flow_graph) { if ((left()->Type()->ToCid() == kDoubleCid) || (right()->Type()->ToCid() == kDoubleCid)) { return NULL; // Remove from the graph. } return this; } // Shared code generation methods (EmitNativeCode and // MakeLocationSummary). Only assembly code that can be shared across all // architectures can be used. Machine specific register allocation and code // generation is located in intermediate_language_.cc #define __ compiler->assembler()-> LocationSummary* GraphEntryInstr::MakeLocationSummary() const { UNREACHABLE(); return NULL; } void GraphEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { // Nothing to do. } LocationSummary* JoinEntryInstr::MakeLocationSummary() const { UNREACHABLE(); return NULL; } void JoinEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ Bind(compiler->GetJumpLabel(this)); if (!compiler->is_optimizing()) { compiler->AddCurrentDescriptor(PcDescriptors::kDeopt, deopt_id_, Scanner::kDummyTokenIndex); } if (HasParallelMove()) { compiler->parallel_move_resolver()->EmitNativeCode(parallel_move()); } } LocationSummary* TargetEntryInstr::MakeLocationSummary() const { UNREACHABLE(); return NULL; } void TargetEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ Bind(compiler->GetJumpLabel(this)); if (!compiler->is_optimizing()) { compiler->AddCurrentDescriptor(PcDescriptors::kDeopt, deopt_id_, Scanner::kDummyTokenIndex); } if (HasParallelMove()) { compiler->parallel_move_resolver()->EmitNativeCode(parallel_move()); } } LocationSummary* CatchBlockEntryInstr::MakeLocationSummary() const { UNREACHABLE(); return NULL; } void CatchBlockEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ Bind(compiler->GetJumpLabel(this)); compiler->AddExceptionHandler(catch_try_index(), try_index(), compiler->assembler()->CodeSize(), catch_handler_types_); if (HasParallelMove()) { compiler->parallel_move_resolver()->EmitNativeCode(parallel_move()); } } LocationSummary* PhiInstr::MakeLocationSummary() const { UNREACHABLE(); return NULL; } void PhiInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* RedefinitionInstr::MakeLocationSummary() const { UNREACHABLE(); return NULL; } void RedefinitionInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* ParameterInstr::MakeLocationSummary() const { UNREACHABLE(); return NULL; } void ParameterInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* ParallelMoveInstr::MakeLocationSummary() const { return NULL; } void ParallelMoveInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* ConstraintInstr::MakeLocationSummary() const { UNREACHABLE(); return NULL; } void ConstraintInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* MaterializeObjectInstr::MakeLocationSummary() const { UNREACHABLE(); return NULL; } void MaterializeObjectInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } LocationSummary* StoreContextInstr::MakeLocationSummary() const { const intptr_t kNumInputs = 1; const intptr_t kNumTemps = 0; LocationSummary* summary = new LocationSummary(kNumInputs, kNumTemps, LocationSummary::kNoCall); summary->set_in(0, Location::RegisterLocation(CTX)); return summary; } LocationSummary* PushTempInstr::MakeLocationSummary() const { return LocationSummary::Make(1, Location::NoLocation(), LocationSummary::kNoCall); } void PushTempInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(!compiler->is_optimizing()); // Nothing to do. } LocationSummary* DropTempsInstr::MakeLocationSummary() const { return LocationSummary::Make(1, Location::SameAsFirstInput(), LocationSummary::kNoCall); } void DropTempsInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ASSERT(!compiler->is_optimizing()); Register value = locs()->in(0).reg(); Register result = locs()->out().reg(); ASSERT(result == value); // Assert that register assignment is correct. __ Drop(num_temps()); } void StoreContextInstr::EmitNativeCode(FlowGraphCompiler* compiler) { // Nothing to do. Context register was loaded by the register allocator. ASSERT(locs()->in(0).reg() == CTX); } StrictCompareInstr::StrictCompareInstr(intptr_t token_pos, Token::Kind kind, Value* left, Value* right) : ComparisonInstr(token_pos, kind, left, right), needs_number_check_(FLAG_new_identity_spec) { ASSERT((kind == Token::kEQ_STRICT) || (kind == Token::kNE_STRICT)); } LocationSummary* InstanceCallInstr::MakeLocationSummary() const { return MakeCallSummary(); } void InstanceCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { ICData& call_ic_data = ICData::ZoneHandle(ic_data()->raw()); if (!FLAG_propagate_ic_data || !compiler->is_optimizing()) { const Array& arguments_descriptor = Array::Handle(ArgumentsDescriptor::New(ArgumentCount(), argument_names())); call_ic_data = ICData::New(compiler->parsed_function().function(), function_name(), arguments_descriptor, deopt_id(), checked_argument_count()); } if (compiler->is_optimizing()) { ASSERT(HasICData()); if (ic_data()->NumberOfChecks() > 0) { const ICData& unary_ic_data = ICData::ZoneHandle(ic_data()->AsUnaryClassChecks()); compiler->GenerateInstanceCall(deopt_id(), token_pos(), ArgumentCount(), argument_names(), locs(), unary_ic_data); } else { // Call was not visited yet, use original ICData in order to populate it. compiler->GenerateInstanceCall(deopt_id(), token_pos(), ArgumentCount(), argument_names(), locs(), call_ic_data); } } else { // Unoptimized code. ASSERT(!HasICData()); compiler->AddCurrentDescriptor(PcDescriptors::kDeopt, deopt_id(), token_pos()); compiler->GenerateInstanceCall(deopt_id(), token_pos(), ArgumentCount(), argument_names(), locs(), call_ic_data); } } LocationSummary* StaticCallInstr::MakeLocationSummary() const { return MakeCallSummary(); } void StaticCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { Label skip_call; if (!compiler->is_optimizing()) { // Some static calls can be optimized by the optimizing compiler (e.g. sqrt) // and therefore need a deoptimization descriptor. compiler->AddCurrentDescriptor(PcDescriptors::kDeopt, deopt_id(), token_pos()); } if (function().name() == Symbols::EqualOperator().raw()) { compiler->EmitSuperEqualityCallPrologue(locs()->out().reg(), &skip_call); } compiler->GenerateStaticCall(deopt_id(), token_pos(), function(), ArgumentCount(), argument_names(), locs()); __ Bind(&skip_call); } void AssertAssignableInstr::EmitNativeCode(FlowGraphCompiler* compiler) { compiler->GenerateAssertAssignable(token_pos(), deopt_id(), dst_type(), dst_name(), locs()); ASSERT(locs()->in(0).reg() == locs()->out().reg()); } Environment* Environment::From(const GrowableArray& definitions, intptr_t fixed_parameter_count, const Function& function) { Environment* env = new Environment(definitions.length(), fixed_parameter_count, Isolate::kNoDeoptId, function, NULL); for (intptr_t i = 0; i < definitions.length(); ++i) { env->values_.Add(new Value(definitions[i])); } return env; } Environment* Environment::DeepCopy() const { return (this == NULL) ? NULL : DeepCopy(Length()); } Environment* Environment::DeepCopy(intptr_t length) const { ASSERT(length <= values_.length()); if (this == NULL) return NULL; Environment* copy = new Environment(length, fixed_parameter_count_, deopt_id_, function_, outer_->DeepCopy()); for (intptr_t i = 0; i < length; ++i) { copy->values_.Add(values_[i]->Copy()); } return copy; } // Copies the environment and updates the environment use lists. void Environment::DeepCopyTo(Instruction* instr) const { for (Environment::DeepIterator it(instr->env()); !it.Done(); it.Advance()) { it.CurrentValue()->RemoveFromUseList(); } Environment* copy = DeepCopy(); instr->SetEnvironment(copy); for (Environment::DeepIterator it(copy); !it.Done(); it.Advance()) { Value* value = it.CurrentValue(); value->definition()->AddEnvUse(value); } } // Copies the environment as outer on an inlined instruction and updates the // environment use lists. void Environment::DeepCopyToOuter(Instruction* instr) const { // Create a deep copy removing caller arguments from the environment. ASSERT(this != NULL); ASSERT(instr->env()->outer() == NULL); intptr_t argument_count = instr->env()->fixed_parameter_count(); Environment* copy = DeepCopy(values_.length() - argument_count); instr->env()->outer_ = copy; intptr_t use_index = instr->env()->Length(); // Start index after inner. for (Environment::DeepIterator it(copy); !it.Done(); it.Advance()) { Value* value = it.CurrentValue(); value->set_instruction(instr); value->set_use_index(use_index++); value->definition()->AddEnvUse(value); } } RangeBoundary RangeBoundary::FromDefinition(Definition* defn, intptr_t offs) { if (defn->IsConstant() && defn->AsConstant()->value().IsSmi()) { return FromConstant(Smi::Cast(defn->AsConstant()->value()).Value() + offs); } return RangeBoundary(kSymbol, reinterpret_cast(defn), offs); } RangeBoundary RangeBoundary::LowerBound() const { if (IsConstant()) return *this; return Add(Range::ConstantMin(symbol()->range()), RangeBoundary::FromConstant(offset_), OverflowedMinSmi()); } RangeBoundary RangeBoundary::UpperBound() const { if (IsConstant()) return *this; return Add(Range::ConstantMax(symbol()->range()), RangeBoundary::FromConstant(offset_), OverflowedMaxSmi()); } static Definition* UnwrapConstraint(Definition* defn) { while (defn->IsConstraint()) { defn = defn->AsConstraint()->value()->definition(); } return defn; } static bool AreEqualDefinitions(Definition* a, Definition* b) { a = UnwrapConstraint(a); b = UnwrapConstraint(b); return (a == b) || (a->AllowsCSE() && a->Dependencies().IsNone() && b->AllowsCSE() && b->Dependencies().IsNone() && a->Equals(b)); } // Returns true if two range boundaries refer to the same symbol. static bool DependOnSameSymbol(const RangeBoundary& a, const RangeBoundary& b) { return a.IsSymbol() && b.IsSymbol() && AreEqualDefinitions(a.symbol(), b.symbol()); } // Returns true if range has a least specific minimum value. static bool IsMinSmi(Range* range) { return (range == NULL) || (range->min().IsConstant() && (range->min().value() <= Smi::kMinValue)); } // Returns true if range has a least specific maximium value. static bool IsMaxSmi(Range* range) { return (range == NULL) || (range->max().IsConstant() && (range->max().value() >= Smi::kMaxValue)); } // Returns true if two range boundaries can be proven to be equal. static bool IsEqual(const RangeBoundary& a, const RangeBoundary& b) { if (a.IsConstant() && b.IsConstant()) { return a.value() == b.value(); } else if (a.IsSymbol() && b.IsSymbol()) { return (a.offset() == b.offset()) && DependOnSameSymbol(a, b); } else { return false; } } static RangeBoundary CanonicalizeBoundary(const RangeBoundary& a, const RangeBoundary& overflow) { if (a.IsConstant()) return a; intptr_t offset = a.offset(); Definition* symbol = a.symbol(); bool changed; do { changed = false; if (symbol->IsConstraint()) { symbol = symbol->AsConstraint()->value()->definition(); changed = true; } else if (symbol->IsBinarySmiOp()) { BinarySmiOpInstr* op = symbol->AsBinarySmiOp(); Definition* left = op->left()->definition(); Definition* right = op->right()->definition(); switch (op->op_kind()) { case Token::kADD: if (right->IsConstant()) { offset += Smi::Cast(right->AsConstant()->value()).Value(); symbol = left; changed = true; } else if (left->IsConstant()) { offset += Smi::Cast(left->AsConstant()->value()).Value(); symbol = right; changed = true; } break; case Token::kSUB: if (right->IsConstant()) { offset -= Smi::Cast(right->AsConstant()->value()).Value(); symbol = left; changed = true; } break; default: break; } } if (!Smi::IsValid(offset)) return overflow; } while (changed); return RangeBoundary::FromDefinition(symbol, offset); } static bool CanonicalizeMaxBoundary(RangeBoundary* a) { if (!a->IsSymbol()) return false; Range* range = a->symbol()->range(); if ((range == NULL) || !range->max().IsSymbol()) return false; const intptr_t offset = range->max().offset() + a->offset(); if (!Smi::IsValid(offset)) { *a = RangeBoundary::OverflowedMaxSmi(); return true; } *a = CanonicalizeBoundary( RangeBoundary::FromDefinition(range->max().symbol(), offset), RangeBoundary::OverflowedMaxSmi()); return true; } static bool CanonicalizeMinBoundary(RangeBoundary* a) { if (!a->IsSymbol()) return false; Range* range = a->symbol()->range(); if ((range == NULL) || !range->min().IsSymbol()) return false; const intptr_t offset = range->min().offset() + a->offset(); if (!Smi::IsValid(offset)) { *a = RangeBoundary::OverflowedMinSmi(); return true; } *a = CanonicalizeBoundary( RangeBoundary::FromDefinition(range->min().symbol(), offset), RangeBoundary::OverflowedMinSmi()); return true; } RangeBoundary RangeBoundary::Min(RangeBoundary a, RangeBoundary b) { if (DependOnSameSymbol(a, b)) { return (a.offset() <= b.offset()) ? a : b; } const intptr_t min_a = a.LowerBound().Clamp().value(); const intptr_t min_b = b.LowerBound().Clamp().value(); return RangeBoundary::FromConstant(Utils::Minimum(min_a, min_b)); } RangeBoundary RangeBoundary::Max(RangeBoundary a, RangeBoundary b) { if (DependOnSameSymbol(a, b)) { return (a.offset() >= b.offset()) ? a : b; } const intptr_t max_a = a.UpperBound().Clamp().value(); const intptr_t max_b = b.UpperBound().Clamp().value(); return RangeBoundary::FromConstant(Utils::Maximum(max_a, max_b)); } void Definition::InferRange() { ASSERT(Type()->ToCid() == kSmiCid); // Has meaning only for smis. if (range_ == NULL) { range_ = Range::Unknown(); } } void ConstantInstr::InferRange() { ASSERT(value_.IsSmi()); if (range_ == NULL) { intptr_t value = Smi::Cast(value_).Value(); range_ = new Range(RangeBoundary::FromConstant(value), RangeBoundary::FromConstant(value)); } } void ConstraintInstr::InferRange() { Range* value_range = value()->definition()->range(); RangeBoundary min; RangeBoundary max; if (IsMinSmi(value_range) && !IsMinSmi(constraint())) { min = constraint()->min(); } else if (IsMinSmi(constraint()) && !IsMinSmi(value_range)) { min = value_range->min(); } else if ((value_range != NULL) && IsEqual(constraint()->min(), value_range->min())) { min = constraint()->min(); } else { if (value_range != NULL) { RangeBoundary canonical_a = CanonicalizeBoundary(constraint()->min(), RangeBoundary::OverflowedMinSmi()); RangeBoundary canonical_b = CanonicalizeBoundary(value_range->min(), RangeBoundary::OverflowedMinSmi()); do { if (DependOnSameSymbol(canonical_a, canonical_b)) { min = (canonical_a.offset() <= canonical_b.offset()) ? canonical_b : canonical_a; } } while (CanonicalizeMinBoundary(&canonical_a) || CanonicalizeMinBoundary(&canonical_b)); } if (min.IsUnknown()) { min = RangeBoundary::Max(Range::ConstantMin(value_range), Range::ConstantMin(constraint())); } } if (IsMaxSmi(value_range) && !IsMaxSmi(constraint())) { max = constraint()->max(); } else if (IsMaxSmi(constraint()) && !IsMaxSmi(value_range)) { max = value_range->max(); } else if ((value_range != NULL) && IsEqual(constraint()->max(), value_range->max())) { max = constraint()->max(); } else { if (value_range != NULL) { RangeBoundary canonical_b = CanonicalizeBoundary(value_range->max(), RangeBoundary::OverflowedMaxSmi()); RangeBoundary canonical_a = CanonicalizeBoundary(constraint()->max(), RangeBoundary::OverflowedMaxSmi()); do { if (DependOnSameSymbol(canonical_a, canonical_b)) { max = (canonical_a.offset() <= canonical_b.offset()) ? canonical_a : canonical_b; break; } } while (CanonicalizeMaxBoundary(&canonical_a) || CanonicalizeMaxBoundary(&canonical_b)); } if (max.IsUnknown()) { max = RangeBoundary::Min(Range::ConstantMax(value_range), Range::ConstantMax(constraint())); } } range_ = new Range(min, max); // Mark branches that generate unsatisfiable constraints as constant. if (target() != NULL && range_->IsUnsatisfiable()) { BranchInstr* branch = target()->PredecessorAt(0)->last_instruction()->AsBranch(); if (target() == branch->true_successor()) { // True unreachable. if (FLAG_trace_constant_propagation) { OS::Print("Range analysis: True unreachable (B%"Pd")\n", branch->true_successor()->block_id()); } branch->set_constant_target(branch->false_successor()); } else { ASSERT(target() == branch->false_successor()); // False unreachable. if (FLAG_trace_constant_propagation) { OS::Print("Range analysis: False unreachable (B%"Pd")\n", branch->false_successor()->block_id()); } branch->set_constant_target(branch->true_successor()); } } } void LoadFieldInstr::InferRange() { if ((range_ == NULL) && ((recognized_kind() == MethodRecognizer::kObjectArrayLength) || (recognized_kind() == MethodRecognizer::kImmutableArrayLength))) { range_ = new Range(RangeBoundary::FromConstant(0), RangeBoundary::FromConstant(Array::kMaxElements)); return; } if ((range_ == NULL) && (recognized_kind() == MethodRecognizer::kTypedDataLength)) { range_ = new Range(RangeBoundary::FromConstant(0), RangeBoundary::MaxSmi()); return; } if ((range_ == NULL) && (recognized_kind() == MethodRecognizer::kStringBaseLength)) { range_ = new Range(RangeBoundary::FromConstant(0), RangeBoundary::FromConstant(String::kMaxElements)); return; } Definition::InferRange(); } void LoadIndexedInstr::InferRange() { switch (class_id()) { case kTypedDataInt8ArrayCid: range_ = new Range(RangeBoundary::FromConstant(-128), RangeBoundary::FromConstant(127)); break; case kTypedDataUint8ArrayCid: case kTypedDataUint8ClampedArrayCid: case kExternalTypedDataUint8ArrayCid: case kExternalTypedDataUint8ClampedArrayCid: range_ = new Range(RangeBoundary::FromConstant(0), RangeBoundary::FromConstant(255)); break; case kTypedDataInt16ArrayCid: range_ = new Range(RangeBoundary::FromConstant(-32768), RangeBoundary::FromConstant(32767)); break; case kTypedDataUint16ArrayCid: range_ = new Range(RangeBoundary::FromConstant(0), RangeBoundary::FromConstant(65535)); break; case kOneByteStringCid: range_ = new Range(RangeBoundary::FromConstant(0), RangeBoundary::FromConstant(0xFF)); break; case kTwoByteStringCid: range_ = new Range(RangeBoundary::FromConstant(0), RangeBoundary::FromConstant(0xFFFF)); break; default: Definition::InferRange(); break; } } void IfThenElseInstr::InferRange() { const intptr_t min = Utils::Minimum(if_true_, if_false_); const intptr_t max = Utils::Maximum(if_true_, if_false_); range_ = new Range(RangeBoundary::FromConstant(min), RangeBoundary::FromConstant(max)); } void PhiInstr::InferRange() { RangeBoundary new_min; RangeBoundary new_max; for (intptr_t i = 0; i < InputCount(); i++) { Range* input_range = InputAt(i)->definition()->range(); if (input_range == NULL) { range_ = Range::Unknown(); return; } if (new_min.IsUnknown()) { new_min = Range::ConstantMin(input_range); } else { new_min = RangeBoundary::Min(new_min, Range::ConstantMin(input_range)); } if (new_max.IsUnknown()) { new_max = Range::ConstantMax(input_range); } else { new_max = RangeBoundary::Max(new_max, Range::ConstantMax(input_range)); } } ASSERT(new_min.IsUnknown() == new_max.IsUnknown()); if (new_min.IsUnknown()) { range_ = Range::Unknown(); return; } range_ = new Range(new_min, new_max); } static bool SymbolicSub(const RangeBoundary& a, const RangeBoundary& b, RangeBoundary* result) { if (a.IsSymbol() && b.IsConstant() && !b.Overflowed()) { const intptr_t offset = a.offset() - b.value(); if (!Smi::IsValid(offset)) return false; *result = RangeBoundary::FromDefinition(a.symbol(), offset); return true; } return false; } static bool SymbolicAdd(const RangeBoundary& a, const RangeBoundary& b, RangeBoundary* result) { if (a.IsSymbol() && b.IsConstant() && !b.Overflowed()) { const intptr_t offset = a.offset() + b.value(); if (!Smi::IsValid(offset)) return false; *result = RangeBoundary::FromDefinition(a.symbol(), offset); return true; } else if (b.IsSymbol() && a.IsConstant() && !a.Overflowed()) { const intptr_t offset = b.offset() + a.value(); if (!Smi::IsValid(offset)) return false; *result = RangeBoundary::FromDefinition(b.symbol(), offset); return true; } return false; } static bool IsArrayLength(Definition* defn) { LoadFieldInstr* load = defn->AsLoadField(); return (load != NULL) && load->IsImmutableLengthLoad(); } void BinarySmiOpInstr::InferRange() { // TODO(vegorov): canonicalize BinarySmiOp to always have constant on the // right and a non-constant on the left. Definition* left_defn = left()->definition(); Range* left_range = left_defn->range(); Range* right_range = right()->definition()->range(); if ((left_range == NULL) || (right_range == NULL)) { range_ = new Range(RangeBoundary::MinSmi(), RangeBoundary::MaxSmi()); return; } RangeBoundary left_min = IsArrayLength(left_defn) ? RangeBoundary::FromDefinition(left_defn) : left_range->min(); RangeBoundary left_max = IsArrayLength(left_defn) ? RangeBoundary::FromDefinition(left_defn) : left_range->max(); RangeBoundary min; RangeBoundary max; switch (op_kind()) { case Token::kADD: if (!SymbolicAdd(left_min, right_range->min(), &min)) { min = RangeBoundary::Add(Range::ConstantMin(left_range), Range::ConstantMin(right_range), RangeBoundary::OverflowedMinSmi()); } if (!SymbolicAdd(left_max, right_range->max(), &max)) { max = RangeBoundary::Add(Range::ConstantMax(right_range), Range::ConstantMax(left_range), RangeBoundary::OverflowedMaxSmi()); } break; case Token::kSUB: if (!SymbolicSub(left_min, right_range->max(), &min)) { min = RangeBoundary::Sub(Range::ConstantMin(left_range), Range::ConstantMax(right_range), RangeBoundary::OverflowedMinSmi()); } if (!SymbolicSub(left_max, right_range->min(), &max)) { max = RangeBoundary::Sub(Range::ConstantMax(left_range), Range::ConstantMin(right_range), RangeBoundary::OverflowedMaxSmi()); } break; case Token::kBIT_AND: if (Range::ConstantMin(right_range).value() >= 0) { min = RangeBoundary::FromConstant(0); max = Range::ConstantMax(right_range); break; } if (Range::ConstantMin(left_range).value() >= 0) { min = RangeBoundary::FromConstant(0); max = Range::ConstantMax(left_range); break; } if (range_ == NULL) { range_ = Range::Unknown(); } return; default: if (range_ == NULL) { range_ = Range::Unknown(); } return; } ASSERT(!min.IsUnknown() && !max.IsUnknown()); set_overflow(min.LowerBound().Overflowed() || max.UpperBound().Overflowed()); if (min.IsConstant()) min.Clamp(); if (max.IsConstant()) max.Clamp(); range_ = new Range(min, max); } // Inclusive. bool Range::IsWithin(intptr_t min_int, intptr_t max_int) const { if (min().LowerBound().value() < min_int) return false; if (max().UpperBound().value() > max_int) return false; return true; } bool Range::IsUnsatisfiable() const { // Constant case: For example [0, -1]. if (Range::ConstantMin(this).value() > Range::ConstantMax(this).value()) { return true; } // Symbol case: For example [v+1, v]. if (DependOnSameSymbol(min(), max()) && min().offset() > max().offset()) { return true; } return false; } bool CheckArrayBoundInstr::IsFixedLengthArrayType(intptr_t cid) { return LoadFieldInstr::IsFixedLengthArrayCid(cid); } bool CheckArrayBoundInstr::IsRedundant(RangeBoundary length) { Range* index_range = index()->definition()->range(); // Range of the index is unknown can't decide if the check is redundant. if (index_range == NULL) { return false; } // Range of the index is not positive. Check can't be redundant. if (Range::ConstantMin(index_range).value() < 0) { return false; } RangeBoundary max = CanonicalizeBoundary(index_range->max(), RangeBoundary::OverflowedMaxSmi()); if (max.Overflowed()) { return false; } // Try to compare constant boundaries. if (max.UpperBound().value() < length.LowerBound().value()) { return true; } length = CanonicalizeBoundary(length, RangeBoundary::OverflowedMaxSmi()); if (length.Overflowed()) { return false; } // Try symbolic comparison. do { if (DependOnSameSymbol(max, length)) return max.offset() < length.offset(); } while (CanonicalizeMaxBoundary(&max) || CanonicalizeMinBoundary(&length)); // Failed to prove that maximum is bounded with array length. return false; } Instruction* CheckArrayBoundInstr::Canonicalize(FlowGraph* flow_graph) { return IsRedundant(RangeBoundary::FromDefinition(length()->definition())) ? NULL : this; } intptr_t CheckArrayBoundInstr::LengthOffsetFor(intptr_t class_id) { if (RawObject::IsExternalTypedDataClassId(class_id)) { return ExternalTypedData::length_offset(); } if (RawObject::IsTypedDataClassId(class_id)) { return TypedData::length_offset(); } switch (class_id) { case kGrowableObjectArrayCid: return GrowableObjectArray::length_offset(); case kOneByteStringCid: case kTwoByteStringCid: return String::length_offset(); case kArrayCid: case kImmutableArrayCid: return Array::length_offset(); default: UNREACHABLE(); return -1; } } InvokeMathCFunctionInstr::InvokeMathCFunctionInstr( ZoneGrowableArray* inputs, InstanceCallInstr* instance_call, MethodRecognizer::Kind recognized_kind) : inputs_(inputs), locs_(NULL), recognized_kind_(recognized_kind) { ASSERT(inputs_->length() == ArgumentCountFor(recognized_kind_)); for (intptr_t i = 0; i < inputs_->length(); ++i) { ASSERT((*inputs)[i] != NULL); (*inputs)[i]->set_instruction(this); (*inputs)[i]->set_use_index(i); } deopt_id_ = instance_call->deopt_id(); } intptr_t InvokeMathCFunctionInstr::ArgumentCountFor( MethodRecognizer::Kind kind) { switch (kind) { case MethodRecognizer::kDoubleTruncate: case MethodRecognizer::kDoubleFloor: case MethodRecognizer::kDoubleCeil: { ASSERT(!CPUFeatures::double_truncate_round_supported()); return 1; } case MethodRecognizer::kDoubleRound: return 1; case MethodRecognizer::kDoubleMod: case MethodRecognizer::kDoublePow: return 2; default: UNREACHABLE(); } return 0; } // Use expected function signatures to help MSVC compiler resolve overloading. typedef double (*UnaryMathCFunction) (double x); typedef double (*BinaryMathCFunction) (double x, double y); extern const RuntimeEntry kPowRuntimeEntry( "libc_pow", reinterpret_cast( static_cast(&pow)), 2, true, true); extern const RuntimeEntry kModRuntimeEntry( "DartModulo", reinterpret_cast( static_cast(&DartModulo)), 2, true, true); extern const RuntimeEntry kFloorRuntimeEntry( "libc_floor", reinterpret_cast( static_cast(&floor)), 1, true, true); extern const RuntimeEntry kCeilRuntimeEntry( "libc_ceil", reinterpret_cast( static_cast(&ceil)), 1, true, true); extern const RuntimeEntry kTruncRuntimeEntry( "libc_trunc", reinterpret_cast( static_cast(&trunc)), 1, true, true); extern const RuntimeEntry kRoundRuntimeEntry( "libc_round", reinterpret_cast( static_cast(&round)), 1, true, true); const RuntimeEntry& InvokeMathCFunctionInstr::TargetFunction() const { switch (recognized_kind_) { case MethodRecognizer::kDoubleTruncate: return kTruncRuntimeEntry; case MethodRecognizer::kDoubleRound: return kRoundRuntimeEntry; case MethodRecognizer::kDoubleFloor: return kFloorRuntimeEntry; case MethodRecognizer::kDoubleCeil: return kCeilRuntimeEntry; case MethodRecognizer::kDoublePow: return kPowRuntimeEntry; case MethodRecognizer::kDoubleMod: return kModRuntimeEntry; default: UNREACHABLE(); } return kPowRuntimeEntry; } #undef __ } // namespace dart