| // 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. |
| |
| #ifndef RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_COMPILER_H_ |
| #define RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_COMPILER_H_ |
| |
| #if defined(DART_PRECOMPILED_RUNTIME) |
| #error "AOT runtime should not use compiler sources (including header files)" |
| #endif // defined(DART_PRECOMPILED_RUNTIME) |
| |
| #include <functional> |
| |
| #include "vm/allocation.h" |
| #include "vm/code_descriptors.h" |
| #include "vm/compiler/assembler/assembler.h" |
| #include "vm/compiler/backend/code_statistics.h" |
| #include "vm/compiler/backend/il.h" |
| #include "vm/compiler/backend/locations.h" |
| #include "vm/runtime_entry.h" |
| |
| namespace dart { |
| |
| // Forward declarations. |
| class CatchEntryMovesMapBuilder; |
| class Code; |
| class DeoptInfoBuilder; |
| class FlowGraph; |
| class FlowGraphCompiler; |
| class Function; |
| template <typename T> |
| class GrowableArray; |
| class ParsedFunction; |
| class SpeculativeInliningPolicy; |
| |
| namespace compiler { |
| struct TableSelector; |
| } |
| |
| // Used in methods which need conditional access to a temporary register. |
| // May only be used to allocate a single temporary register. |
| class TemporaryRegisterAllocator : public ValueObject { |
| public: |
| virtual ~TemporaryRegisterAllocator() {} |
| virtual Register AllocateTemporary() = 0; |
| virtual void ReleaseTemporary() = 0; |
| }; |
| |
| class ConstantTemporaryAllocator : public TemporaryRegisterAllocator { |
| public: |
| explicit ConstantTemporaryAllocator(Register tmp) : tmp_(tmp) {} |
| |
| Register AllocateTemporary() override { return tmp_; } |
| void ReleaseTemporary() override {} |
| |
| private: |
| Register const tmp_; |
| }; |
| |
| class NoTemporaryAllocator : public TemporaryRegisterAllocator { |
| public: |
| Register AllocateTemporary() override { UNREACHABLE(); } |
| void ReleaseTemporary() override { UNREACHABLE(); } |
| }; |
| |
| class ParallelMoveResolver : public ValueObject { |
| public: |
| explicit ParallelMoveResolver(FlowGraphCompiler* compiler); |
| |
| // Resolve a set of parallel moves, emitting assembler instructions. |
| void EmitNativeCode(ParallelMoveInstr* parallel_move); |
| |
| private: |
| class ScratchFpuRegisterScope : public ValueObject { |
| public: |
| ScratchFpuRegisterScope(ParallelMoveResolver* resolver, |
| FpuRegister blocked); |
| ~ScratchFpuRegisterScope(); |
| |
| FpuRegister reg() const { return reg_; } |
| |
| private: |
| ParallelMoveResolver* resolver_; |
| FpuRegister reg_; |
| bool spilled_; |
| }; |
| |
| class TemporaryAllocator : public TemporaryRegisterAllocator { |
| public: |
| TemporaryAllocator(ParallelMoveResolver* resolver, Register blocked); |
| |
| Register AllocateTemporary() override; |
| void ReleaseTemporary() override; |
| DEBUG_ONLY(bool DidAllocateTemporary() { return allocated_; }) |
| |
| virtual ~TemporaryAllocator() { ASSERT(reg_ == kNoRegister); } |
| |
| private: |
| ParallelMoveResolver* const resolver_; |
| const Register blocked_; |
| Register reg_; |
| bool spilled_; |
| DEBUG_ONLY(bool allocated_ = false); |
| }; |
| |
| class ScratchRegisterScope : public ValueObject { |
| public: |
| ScratchRegisterScope(ParallelMoveResolver* resolver, Register blocked); |
| ~ScratchRegisterScope(); |
| |
| Register reg() const { return reg_; } |
| |
| private: |
| TemporaryAllocator allocator_; |
| Register reg_; |
| }; |
| |
| bool IsScratchLocation(Location loc); |
| intptr_t AllocateScratchRegister(Location::Kind kind, |
| uword blocked_mask, |
| intptr_t first_free_register, |
| intptr_t last_free_register, |
| bool* spilled); |
| |
| void SpillScratch(Register reg); |
| void RestoreScratch(Register reg); |
| void SpillFpuScratch(FpuRegister reg); |
| void RestoreFpuScratch(FpuRegister reg); |
| |
| // friend class ScratchXmmRegisterScope; |
| |
| // Build the initial list of moves. |
| void BuildInitialMoveList(ParallelMoveInstr* parallel_move); |
| |
| // Perform the move at the moves_ index in question (possibly requiring |
| // other moves to satisfy dependencies). |
| void PerformMove(const InstructionSource& source, int index); |
| |
| // Emit a move and remove it from the move graph. |
| void EmitMove(int index); |
| |
| // Execute a move by emitting a swap of two operands. The move from |
| // source to destination is removed from the move graph. |
| void EmitSwap(int index); |
| |
| // Verify the move list before performing moves. |
| void Verify(); |
| |
| // Helpers for non-trivial source-destination combinations that cannot |
| // be handled by a single instruction. |
| void MoveMemoryToMemory(const compiler::Address& dst, |
| const compiler::Address& src); |
| void Exchange(Register reg, const compiler::Address& mem); |
| void Exchange(const compiler::Address& mem1, const compiler::Address& mem2); |
| void Exchange(Register reg, Register base_reg, intptr_t stack_offset); |
| void Exchange(Register base_reg1, |
| intptr_t stack_offset1, |
| Register base_reg2, |
| intptr_t stack_offset2); |
| |
| FlowGraphCompiler* compiler_; |
| |
| // List of moves not yet resolved. |
| GrowableArray<MoveOperands*> moves_; |
| }; |
| |
| // Used for describing a deoptimization point after call (lazy deoptimization). |
| // For deoptimization before instruction use class CompilerDeoptInfoWithStub. |
| class CompilerDeoptInfo : public ZoneAllocated { |
| public: |
| CompilerDeoptInfo(intptr_t deopt_id, |
| ICData::DeoptReasonId reason, |
| uint32_t flags, |
| Environment* deopt_env) |
| : pc_offset_(-1), |
| deopt_id_(deopt_id), |
| reason_(reason), |
| flags_(flags), |
| deopt_env_(deopt_env) { |
| ASSERT(deopt_env != NULL); |
| } |
| virtual ~CompilerDeoptInfo() {} |
| |
| TypedDataPtr CreateDeoptInfo(FlowGraphCompiler* compiler, |
| DeoptInfoBuilder* builder, |
| const Array& deopt_table); |
| |
| // No code needs to be generated. |
| virtual void GenerateCode(FlowGraphCompiler* compiler, intptr_t stub_ix) {} |
| |
| intptr_t pc_offset() const { return pc_offset_; } |
| void set_pc_offset(intptr_t offset) { pc_offset_ = offset; } |
| |
| intptr_t deopt_id() const { return deopt_id_; } |
| ICData::DeoptReasonId reason() const { return reason_; } |
| uint32_t flags() const { return flags_; } |
| const Environment* deopt_env() const { return deopt_env_; } |
| |
| private: |
| void EmitMaterializations(Environment* env, DeoptInfoBuilder* builder); |
| |
| void AllocateIncomingParametersRecursive(Environment* env, |
| intptr_t* stack_height); |
| |
| intptr_t pc_offset_; |
| const intptr_t deopt_id_; |
| const ICData::DeoptReasonId reason_; |
| const uint32_t flags_; |
| Environment* deopt_env_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CompilerDeoptInfo); |
| }; |
| |
| class CompilerDeoptInfoWithStub : public CompilerDeoptInfo { |
| public: |
| CompilerDeoptInfoWithStub(intptr_t deopt_id, |
| ICData::DeoptReasonId reason, |
| uint32_t flags, |
| Environment* deopt_env) |
| : CompilerDeoptInfo(deopt_id, reason, flags, deopt_env), entry_label_() { |
| ASSERT(reason != ICData::kDeoptAtCall); |
| } |
| |
| compiler::Label* entry_label() { return &entry_label_; } |
| |
| // Implementation is in architecture specific file. |
| virtual void GenerateCode(FlowGraphCompiler* compiler, intptr_t stub_ix); |
| |
| const char* Name() const { |
| const char* kFormat = "Deopt stub for id %d, reason: %s"; |
| const intptr_t len = Utils::SNPrint(NULL, 0, kFormat, deopt_id(), |
| DeoptReasonToCString(reason())) + |
| 1; |
| char* chars = Thread::Current()->zone()->Alloc<char>(len); |
| Utils::SNPrint(chars, len, kFormat, deopt_id(), |
| DeoptReasonToCString(reason())); |
| return chars; |
| } |
| |
| private: |
| compiler::Label entry_label_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CompilerDeoptInfoWithStub); |
| }; |
| |
| class SlowPathCode : public ZoneAllocated { |
| public: |
| explicit SlowPathCode(Instruction* instruction) |
| : instruction_(instruction), entry_label_(), exit_label_() {} |
| virtual ~SlowPathCode() {} |
| |
| Instruction* instruction() const { return instruction_; } |
| compiler::Label* entry_label() { return &entry_label_; } |
| compiler::Label* exit_label() { return &exit_label_; } |
| |
| void GenerateCode(FlowGraphCompiler* compiler) { |
| EmitNativeCode(compiler); |
| ASSERT(entry_label_.IsBound()); |
| } |
| |
| private: |
| virtual void EmitNativeCode(FlowGraphCompiler* compiler) = 0; |
| |
| Instruction* instruction_; |
| compiler::Label entry_label_; |
| compiler::Label exit_label_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SlowPathCode); |
| }; |
| |
| template <typename T> |
| class TemplateSlowPathCode : public SlowPathCode { |
| public: |
| explicit TemplateSlowPathCode(T* instruction) : SlowPathCode(instruction) {} |
| |
| T* instruction() const { |
| return static_cast<T*>(SlowPathCode::instruction()); |
| } |
| }; |
| |
| // Slow path code which calls runtime entry to throw an exception. |
| class ThrowErrorSlowPathCode : public TemplateSlowPathCode<Instruction> { |
| public: |
| ThrowErrorSlowPathCode(Instruction* instruction, |
| const RuntimeEntry& runtime_entry, |
| intptr_t try_index) |
| : TemplateSlowPathCode(instruction), |
| runtime_entry_(runtime_entry), |
| try_index_(try_index) {} |
| |
| // This name appears in disassembly. |
| virtual const char* name() = 0; |
| |
| // Subclasses can override these methods to customize slow path code. |
| virtual void EmitCodeAtSlowPathEntry(FlowGraphCompiler* compiler) {} |
| virtual void AddMetadataForRuntimeCall(FlowGraphCompiler* compiler) {} |
| virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler) {} |
| |
| // Returns number of arguments for runtime call (if shared stub is not used). |
| virtual intptr_t GetNumberOfArgumentsForRuntimeCall() { return 0; } |
| |
| virtual void EmitSharedStubCall(FlowGraphCompiler* compiler, |
| bool save_fpu_registers) { |
| UNREACHABLE(); |
| } |
| |
| virtual void EmitNativeCode(FlowGraphCompiler* compiler); |
| |
| private: |
| const RuntimeEntry& runtime_entry_; |
| const intptr_t try_index_; |
| }; |
| |
| class NullErrorSlowPath : public ThrowErrorSlowPathCode { |
| public: |
| NullErrorSlowPath(CheckNullInstr* instruction, intptr_t try_index) |
| : ThrowErrorSlowPathCode(instruction, |
| GetRuntimeEntry(instruction->exception_type()), |
| try_index) {} |
| |
| CheckNullInstr::ExceptionType exception_type() const { |
| return instruction()->AsCheckNull()->exception_type(); |
| } |
| |
| const char* name() override; |
| |
| void EmitSharedStubCall(FlowGraphCompiler* compiler, |
| bool save_fpu_registers) override; |
| |
| void AddMetadataForRuntimeCall(FlowGraphCompiler* compiler) override { |
| CheckNullInstr::AddMetadataForRuntimeCall(instruction()->AsCheckNull(), |
| compiler); |
| } |
| |
| static CodePtr GetStub(FlowGraphCompiler* compiler, |
| CheckNullInstr::ExceptionType exception_type, |
| bool save_fpu_registers); |
| |
| private: |
| static const RuntimeEntry& GetRuntimeEntry( |
| CheckNullInstr::ExceptionType exception_type); |
| }; |
| |
| class RangeErrorSlowPath : public ThrowErrorSlowPathCode { |
| public: |
| RangeErrorSlowPath(GenericCheckBoundInstr* instruction, intptr_t try_index) |
| : ThrowErrorSlowPathCode(instruction, |
| kRangeErrorRuntimeEntry, |
| try_index) {} |
| virtual const char* name() { return "check bound"; } |
| |
| virtual intptr_t GetNumberOfArgumentsForRuntimeCall() { |
| return 2; // length and index |
| } |
| |
| virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler); |
| |
| virtual void EmitSharedStubCall(FlowGraphCompiler* compiler, |
| bool save_fpu_registers); |
| }; |
| |
| class LateInitializationErrorSlowPath : public ThrowErrorSlowPathCode { |
| public: |
| LateInitializationErrorSlowPath(LoadFieldInstr* instruction, |
| intptr_t try_index) |
| : ThrowErrorSlowPathCode(instruction, |
| kLateFieldNotInitializedErrorRuntimeEntry, |
| try_index) {} |
| virtual const char* name() { return "late initialization error"; } |
| |
| virtual intptr_t GetNumberOfArgumentsForRuntimeCall() { |
| return 1; // field |
| } |
| |
| virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler); |
| |
| virtual void EmitSharedStubCall(FlowGraphCompiler* compiler, |
| bool save_fpu_registers); |
| }; |
| |
| class FlowGraphCompiler : public ValueObject { |
| private: |
| class BlockInfo : public ZoneAllocated { |
| public: |
| BlockInfo() |
| : block_label_(), |
| jump_label_(&block_label_), |
| next_nonempty_label_(NULL), |
| is_marked_(false) {} |
| |
| // The label to jump to when control is transferred to this block. For |
| // nonempty blocks it is the label of the block itself. For empty |
| // blocks it is the label of the first nonempty successor block. |
| compiler::Label* jump_label() const { return jump_label_; } |
| void set_jump_label(compiler::Label* label) { jump_label_ = label; } |
| |
| // The label of the first nonempty block after this one in the block |
| // order, or NULL if there is no nonempty block following this one. |
| compiler::Label* next_nonempty_label() const { |
| return next_nonempty_label_; |
| } |
| void set_next_nonempty_label(compiler::Label* label) { |
| next_nonempty_label_ = label; |
| } |
| |
| bool WasCompacted() const { return jump_label_ != &block_label_; } |
| |
| // Block compaction is recursive. Block info for already-compacted |
| // blocks is marked so as to avoid cycles in the graph. |
| bool is_marked() const { return is_marked_; } |
| void mark() { is_marked_ = true; } |
| |
| private: |
| compiler::Label block_label_; |
| |
| compiler::Label* jump_label_; |
| compiler::Label* next_nonempty_label_; |
| |
| bool is_marked_; |
| }; |
| |
| public: |
| FlowGraphCompiler(compiler::Assembler* assembler, |
| FlowGraph* flow_graph, |
| const ParsedFunction& parsed_function, |
| bool is_optimizing, |
| SpeculativeInliningPolicy* speculative_policy, |
| const GrowableArray<const Function*>& inline_id_to_function, |
| const GrowableArray<TokenPosition>& inline_id_to_token_pos, |
| const GrowableArray<intptr_t>& caller_inline_id, |
| ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data, |
| CodeStatistics* stats = NULL); |
| |
| void ArchSpecificInitialization(); |
| |
| ~FlowGraphCompiler(); |
| |
| static bool SupportsUnboxedDoubles(); |
| static bool SupportsUnboxedInt64(); |
| static bool SupportsUnboxedSimd128(); |
| static bool SupportsHardwareDivision(); |
| static bool CanConvertInt64ToDouble(); |
| |
| static bool IsUnboxedField(const Field& field); |
| static bool IsPotentialUnboxedField(const Field& field); |
| |
| // Accessors. |
| compiler::Assembler* assembler() const { return assembler_; } |
| const ParsedFunction& parsed_function() const { return parsed_function_; } |
| const Function& function() const { return parsed_function_.function(); } |
| const GrowableArray<BlockEntryInstr*>& block_order() const { |
| return block_order_; |
| } |
| const GrowableArray<const compiler::TableSelector*>& |
| dispatch_table_call_targets() const { |
| return dispatch_table_call_targets_; |
| } |
| |
| // If 'ForcedOptimization()' returns 'true', we are compiling in optimized |
| // mode for a function which cannot deoptimize. Certain optimizations, e.g. |
| // speculative optimizations and call patching are disabled. |
| bool ForcedOptimization() const { return function().ForceOptimize(); } |
| |
| const FlowGraph& flow_graph() const { |
| return intrinsic_mode() ? *intrinsic_flow_graph_ : flow_graph_; |
| } |
| |
| BlockEntryInstr* current_block() const { return current_block_; } |
| void set_current_block(BlockEntryInstr* value) { current_block_ = value; } |
| static bool CanOptimize(); |
| bool CanOptimizeFunction() const; |
| bool CanOSRFunction() const; |
| bool is_optimizing() const { return is_optimizing_; } |
| |
| void InsertBSSRelocation(BSS::Relocation reloc); |
| void LoadBSSEntry(BSS::Relocation relocation, Register dst, Register tmp); |
| |
| // The function was fully intrinsified, so the body is unreachable. |
| // |
| // We still need to compile the body in unoptimized mode because the |
| // 'ICData's are added to the function's 'ic_data_array_' when instance |
| // calls are compiled. |
| bool skip_body_compilation() const { |
| return fully_intrinsified_ && is_optimizing(); |
| } |
| |
| void EnterIntrinsicMode(); |
| void ExitIntrinsicMode(); |
| bool intrinsic_mode() const { return intrinsic_mode_; } |
| |
| void set_intrinsic_flow_graph(const FlowGraph& flow_graph) { |
| intrinsic_flow_graph_ = &flow_graph; |
| } |
| |
| void set_intrinsic_slow_path_label(compiler::Label* label) { |
| ASSERT(intrinsic_slow_path_label_ == nullptr || label == nullptr); |
| intrinsic_slow_path_label_ = label; |
| } |
| compiler::Label* intrinsic_slow_path_label() const { |
| ASSERT(intrinsic_slow_path_label_ != nullptr); |
| return intrinsic_slow_path_label_; |
| } |
| |
| bool ForceSlowPathForStackOverflow() const; |
| |
| const GrowableArray<BlockInfo*>& block_info() const { return block_info_; } |
| ParallelMoveResolver* parallel_move_resolver() { |
| return ¶llel_move_resolver_; |
| } |
| |
| void StatsBegin(Instruction* instr) { |
| if (stats_ != NULL) stats_->Begin(instr); |
| } |
| |
| void StatsEnd(Instruction* instr) { |
| if (stats_ != NULL) stats_->End(instr); |
| } |
| |
| void SpecialStatsBegin(intptr_t tag) { |
| if (stats_ != NULL) stats_->SpecialBegin(tag); |
| } |
| |
| void SpecialStatsEnd(intptr_t tag) { |
| if (stats_ != NULL) stats_->SpecialEnd(tag); |
| } |
| |
| GrowableArray<const Field*>& used_static_fields() { |
| return used_static_fields_; |
| } |
| |
| // Constructor is lighweight, major initialization work should occur here. |
| // This makes it easier to measure time spent in the compiler. |
| void InitCompiler(); |
| |
| void CompileGraph(); |
| |
| void EmitPrologue(); |
| |
| void VisitBlocks(); |
| |
| // Bail out of the flow graph compiler. Does not return to the caller. |
| void Bailout(const char* reason); |
| |
| // Returns 'true' if regular code generation should be skipped. |
| bool TryIntrinsify(); |
| |
| // Emits code for a generic move from a location 'src' to a location 'dst'. |
| // |
| // Note that Location does not include a size (that can only be deduced from |
| // a Representation), so these moves might overapproximate the size needed |
| // to move. The maximal overapproximation is moving 8 bytes instead of 4 on |
| // 64 bit architectures. This overapproximation is not a problem, because |
| // the Dart calling convention only uses word-sized stack slots. |
| // |
| // TODO(dartbug.com/40400): Express this in terms of EmitMove(NativeLocation |
| // NativeLocation) to remove code duplication. |
| void EmitMove(Location dst, Location src, TemporaryRegisterAllocator* temp); |
| |
| // Emits code for a move from a location `src` to a location `dst`. |
| // |
| // Takes into account the payload and container representations of `dst` and |
| // `src` to do the smallest move possible, and sign (or zero) extend or |
| // truncate if needed. |
| // |
| // Makes use of TMP, FpuTMP, and `temp`. |
| void EmitNativeMove(const compiler::ffi::NativeLocation& dst, |
| const compiler::ffi::NativeLocation& src, |
| TemporaryRegisterAllocator* temp); |
| |
| // Helper method to move from a Location to a NativeLocation. |
| void EmitMoveToNative(const compiler::ffi::NativeLocation& dst, |
| Location src_loc, |
| Representation src_type, |
| TemporaryRegisterAllocator* temp); |
| |
| // Helper method to move from a NativeLocation to a Location. |
| void EmitMoveFromNative(Location dst_loc, |
| Representation dst_type, |
| const compiler::ffi::NativeLocation& src, |
| TemporaryRegisterAllocator* temp); |
| |
| // Emits a Dart const to a native location. |
| void EmitMoveConst(const compiler::ffi::NativeLocation& dst, |
| Location src, |
| Representation src_type, |
| TemporaryRegisterAllocator* temp); |
| |
| bool CheckAssertAssignableTypeTestingABILocations( |
| const LocationSummary& locs); |
| |
| void GenerateAssertAssignable(CompileType* receiver_type, |
| const InstructionSource& source, |
| intptr_t deopt_id, |
| const String& dst_name, |
| LocationSummary* locs, |
| bool was_licm_hoisted); |
| |
| #if !defined(TARGET_ARCH_IA32) |
| void GenerateCallerChecksForAssertAssignable(CompileType* receiver_type, |
| const AbstractType& dst_type, |
| compiler::Label* done); |
| |
| void GenerateTTSCall(const InstructionSource& source, |
| intptr_t deopt_id, |
| Register reg_with_type, |
| const AbstractType& dst_type, |
| const String& dst_name, |
| LocationSummary* locs, |
| bool was_licm_hoisted); |
| |
| static void GenerateIndirectTTSCall(compiler::Assembler* assembler, |
| Register reg_with_type, |
| intptr_t sub_type_cache_index); |
| #endif |
| |
| void GenerateRuntimeCall(const InstructionSource& source, |
| intptr_t deopt_id, |
| const RuntimeEntry& entry, |
| intptr_t argument_count, |
| LocationSummary* locs); |
| |
| void GenerateStubCall(const InstructionSource& source, |
| const Code& stub, |
| UntaggedPcDescriptors::Kind kind, |
| LocationSummary* locs, |
| intptr_t deopt_id = DeoptId::kNone, |
| Environment* env = nullptr); |
| |
| void GeneratePatchableCall(const InstructionSource& source, |
| const Code& stub, |
| UntaggedPcDescriptors::Kind kind, |
| LocationSummary* locs); |
| |
| void GenerateDartCall(intptr_t deopt_id, |
| const InstructionSource& source, |
| const Code& stub, |
| UntaggedPcDescriptors::Kind kind, |
| LocationSummary* locs, |
| Code::EntryKind entry_kind = Code::EntryKind::kNormal); |
| |
| void GenerateStaticDartCall( |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| UntaggedPcDescriptors::Kind kind, |
| LocationSummary* locs, |
| const Function& target, |
| Code::EntryKind entry_kind = Code::EntryKind::kNormal); |
| |
| void GenerateInstanceOf(const InstructionSource& source, |
| intptr_t deopt_id, |
| const AbstractType& type, |
| LocationSummary* locs); |
| |
| void GenerateInstanceCall(intptr_t deopt_id, |
| const InstructionSource& source, |
| LocationSummary* locs, |
| const ICData& ic_data, |
| Code::EntryKind entry_kind, |
| bool receiver_can_be_smi); |
| |
| void GenerateStaticCall( |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| const Function& function, |
| ArgumentsInfo args_info, |
| LocationSummary* locs, |
| const ICData& ic_data_in, |
| ICData::RebindRule rebind_rule, |
| Code::EntryKind entry_kind = Code::EntryKind::kNormal); |
| |
| void GenerateNumberTypeCheck(Register kClassIdReg, |
| const AbstractType& type, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_lbl); |
| void GenerateStringTypeCheck(Register kClassIdReg, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_lbl); |
| void GenerateListTypeCheck(Register kClassIdReg, |
| compiler::Label* is_instance_lbl); |
| |
| // Returns true if no further checks are necessary but the code coming after |
| // the emitted code here is still required do a runtime call (for the negative |
| // case of throwing an exception). |
| bool GenerateSubtypeRangeCheck(Register class_id_reg, |
| const Class& type_class, |
| compiler::Label* is_subtype_lbl); |
| |
| // We test up to 4 different cid ranges, if we would need to test more in |
| // order to get a definite answer we fall back to the old mechanism (namely |
| // of going into the subtyping cache) |
| static const intptr_t kMaxNumberOfCidRangesToTest = 4; |
| |
| // If [fall_through_if_inside] is `true`, then [outside_range_lbl] must be |
| // supplied, since it will be jumped to in the last case if the cid is outside |
| // the range. |
| static void GenerateCidRangesCheck(compiler::Assembler* assembler, |
| Register class_id_reg, |
| const CidRangeVector& cid_ranges, |
| compiler::Label* inside_range_lbl, |
| compiler::Label* outside_range_lbl = NULL, |
| bool fall_through_if_inside = false); |
| |
| void EmitOptimizedInstanceCall( |
| const Code& stub, |
| const ICData& ic_data, |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| LocationSummary* locs, |
| Code::EntryKind entry_kind = Code::EntryKind::kNormal); |
| |
| void EmitInstanceCallJIT(const Code& stub, |
| const ICData& ic_data, |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| LocationSummary* locs, |
| Code::EntryKind entry_kind); |
| |
| void EmitPolymorphicInstanceCall(const PolymorphicInstanceCallInstr* call, |
| const CallTargets& targets, |
| ArgumentsInfo args_info, |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| LocationSummary* locs, |
| bool complete, |
| intptr_t total_call_count, |
| bool receiver_can_be_smi = true); |
| |
| void EmitMegamorphicInstanceCall(const ICData& icdata, |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| LocationSummary* locs, |
| intptr_t try_index, |
| intptr_t slow_path_argument_count = 0) { |
| const String& name = String::Handle(icdata.target_name()); |
| const Array& arguments_descriptor = |
| Array::Handle(icdata.arguments_descriptor()); |
| EmitMegamorphicInstanceCall(name, arguments_descriptor, deopt_id, source, |
| locs, try_index); |
| } |
| |
| // Pass a value for try-index where block is not available (e.g. slow path). |
| void EmitMegamorphicInstanceCall(const String& function_name, |
| const Array& arguments_descriptor, |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| LocationSummary* locs, |
| intptr_t try_index, |
| intptr_t slow_path_argument_count = 0); |
| |
| void EmitInstanceCallAOT( |
| const ICData& ic_data, |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| LocationSummary* locs, |
| Code::EntryKind entry_kind = Code::EntryKind::kNormal, |
| bool receiver_can_be_smi = true); |
| |
| void EmitTestAndCall(const CallTargets& targets, |
| const String& function_name, |
| ArgumentsInfo args_info, |
| compiler::Label* failed, |
| compiler::Label* match_found, |
| intptr_t deopt_id, |
| const InstructionSource& source_index, |
| LocationSummary* locs, |
| bool complete, |
| intptr_t total_ic_calls, |
| Code::EntryKind entry_kind = Code::EntryKind::kNormal); |
| |
| void EmitDispatchTableCall(Register cid_reg, |
| int32_t selector_offset, |
| const Array& arguments_descriptor); |
| |
| Condition EmitEqualityRegConstCompare(Register reg, |
| const Object& obj, |
| bool needs_number_check, |
| const InstructionSource& source, |
| intptr_t deopt_id); |
| Condition EmitEqualityRegRegCompare(Register left, |
| Register right, |
| bool needs_number_check, |
| const InstructionSource& source, |
| intptr_t deopt_id); |
| Condition EmitBoolTest(Register value, BranchLabels labels, bool invert); |
| |
| bool NeedsEdgeCounter(BlockEntryInstr* block); |
| |
| void EmitEdgeCounter(intptr_t edge_id); |
| |
| void RecordCatchEntryMoves(Environment* env = NULL, |
| intptr_t try_index = kInvalidTryIndex); |
| |
| void EmitCallToStub(const Code& stub); |
| void EmitTailCallToStub(const Code& stub); |
| |
| // Emits the following metadata for the current PC: |
| // |
| // * Attaches current try index |
| // * Attaches stackmaps |
| // * Attaches catch entry moves (in AOT) |
| // * Deoptimization information (in JIT) |
| // |
| // If [env] is not `nullptr` it will be used instead of the |
| // `pending_deoptimization_env`. |
| void EmitCallsiteMetadata(const InstructionSource& source, |
| intptr_t deopt_id, |
| UntaggedPcDescriptors::Kind kind, |
| LocationSummary* locs, |
| Environment* env = nullptr); |
| |
| void EmitYieldPositionMetadata(const InstructionSource& source, |
| intptr_t yield_index); |
| |
| void EmitComment(Instruction* instr); |
| |
| // Returns stack size (number of variables on stack for unoptimized |
| // code, or number of spill slots for optimized code). |
| intptr_t StackSize() const; |
| |
| // Returns the number of extra stack slots used during an Osr entry |
| // (values for all [ParameterInstr]s, representing local variables |
| // and expression stack values, are already on the stack). |
| intptr_t ExtraStackSlotsOnOsrEntry() const; |
| |
| // Returns assembler label associated with the given block entry. |
| compiler::Label* GetJumpLabel(BlockEntryInstr* block_entry) const; |
| bool WasCompacted(BlockEntryInstr* block_entry) const; |
| |
| // Returns the label of the fall-through of the current block. |
| compiler::Label* NextNonEmptyLabel() const; |
| |
| // Returns true if there is a next block after the current one in |
| // the block order and if it is the given block. |
| bool CanFallThroughTo(BlockEntryInstr* block_entry) const; |
| |
| // Return true-, false- and fall-through label for a branch instruction. |
| BranchLabels CreateBranchLabels(BranchInstr* branch) const; |
| |
| void AddExceptionHandler(intptr_t try_index, |
| intptr_t outer_try_index, |
| intptr_t pc_offset, |
| bool is_generated, |
| const Array& handler_types, |
| bool needs_stacktrace); |
| void SetNeedsStackTrace(intptr_t try_index); |
| void AddCurrentDescriptor(UntaggedPcDescriptors::Kind kind, |
| intptr_t deopt_id, |
| const InstructionSource& source); |
| void AddDescriptor( |
| UntaggedPcDescriptors::Kind kind, |
| intptr_t pc_offset, |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| intptr_t try_index, |
| intptr_t yield_index = UntaggedPcDescriptors::kInvalidYieldIndex); |
| |
| // Add NullCheck information for the current PC. |
| void AddNullCheck(const InstructionSource& source, const String& name); |
| |
| void RecordSafepoint(LocationSummary* locs, |
| intptr_t slow_path_argument_count = 0); |
| |
| compiler::Label* AddDeoptStub(intptr_t deopt_id, |
| ICData::DeoptReasonId reason, |
| uint32_t flags = 0); |
| |
| CompilerDeoptInfo* AddDeoptIndexAtCall(intptr_t deopt_id, |
| Environment* env = nullptr); |
| CompilerDeoptInfo* AddSlowPathDeoptInfo(intptr_t deopt_id, Environment* env); |
| |
| void AddSlowPathCode(SlowPathCode* slow_path); |
| |
| void FinalizeExceptionHandlers(const Code& code); |
| void FinalizePcDescriptors(const Code& code); |
| ArrayPtr CreateDeoptInfo(compiler::Assembler* assembler); |
| void FinalizeStackMaps(const Code& code); |
| void FinalizeVarDescriptors(const Code& code); |
| void FinalizeCatchEntryMovesMap(const Code& code); |
| void FinalizeStaticCallTargetsTable(const Code& code); |
| void FinalizeCodeSourceMap(const Code& code); |
| |
| const Class& double_class() const { return double_class_; } |
| const Class& mint_class() const { return mint_class_; } |
| const Class& float32x4_class() const { return float32x4_class_; } |
| const Class& float64x2_class() const { return float64x2_class_; } |
| const Class& int32x4_class() const { return int32x4_class_; } |
| |
| const Class& BoxClassFor(Representation rep); |
| |
| void SaveLiveRegisters(LocationSummary* locs); |
| void RestoreLiveRegisters(LocationSummary* locs); |
| #if defined(DEBUG) |
| void ClobberDeadTempRegisters(LocationSummary* locs); |
| #endif |
| |
| // Returns a new environment based on [env] which accounts for the new |
| // locations of values in the slow path call. |
| Environment* SlowPathEnvironmentFor(Instruction* inst, |
| intptr_t num_slow_path_args) { |
| if (inst->env() == nullptr && is_optimizing()) { |
| if (pending_deoptimization_env_ == nullptr) { |
| return nullptr; |
| } |
| return SlowPathEnvironmentFor(pending_deoptimization_env_, inst->locs(), |
| num_slow_path_args); |
| } |
| return SlowPathEnvironmentFor(inst->env(), inst->locs(), |
| num_slow_path_args); |
| } |
| |
| Environment* SlowPathEnvironmentFor(Environment* env, |
| LocationSummary* locs, |
| intptr_t num_slow_path_args); |
| |
| intptr_t CurrentTryIndex() const { |
| if (current_block_ == NULL) { |
| return kInvalidTryIndex; |
| } |
| return current_block_->try_index(); |
| } |
| |
| bool may_reoptimize() const { return may_reoptimize_; } |
| |
| // Use in unoptimized compilation to preserve/reuse ICData. |
| // |
| // If [binary_smi_target] is non-null and we have to create the ICData, the |
| // ICData will get an (kSmiCid, kSmiCid, binary_smi_target) entry. |
| const ICData* GetOrAddInstanceCallICData(intptr_t deopt_id, |
| const String& target_name, |
| const Array& arguments_descriptor, |
| intptr_t num_args_tested, |
| const AbstractType& receiver_type, |
| const Function& binary_smi_target); |
| |
| const ICData* GetOrAddStaticCallICData(intptr_t deopt_id, |
| const Function& target, |
| const Array& arguments_descriptor, |
| intptr_t num_args_tested, |
| ICData::RebindRule rebind_rule); |
| |
| static const CallTargets* ResolveCallTargetsForReceiverCid( |
| intptr_t cid, |
| const String& selector, |
| const Array& args_desc_array); |
| |
| const ZoneGrowableArray<const ICData*>& deopt_id_to_ic_data() const { |
| return *deopt_id_to_ic_data_; |
| } |
| |
| Thread* thread() const { return thread_; } |
| IsolateGroup* isolate_group() const { return thread_->isolate_group(); } |
| Zone* zone() const { return zone_; } |
| |
| void AddStubCallTarget(const Code& code); |
| void AddDispatchTableCallTarget(const compiler::TableSelector* selector); |
| |
| ArrayPtr edge_counters_array() const { return edge_counters_array_.ptr(); } |
| |
| ArrayPtr InliningIdToFunction() const; |
| |
| void BeginCodeSourceRange(const InstructionSource& source); |
| void EndCodeSourceRange(const InstructionSource& source); |
| |
| static bool LookupMethodFor(int class_id, |
| const String& name, |
| const ArgumentsDescriptor& args_desc, |
| Function* fn_return, |
| bool* class_is_abstract_return = NULL); |
| |
| // Returns new class-id bias. |
| // |
| // TODO(kustermann): We should move this code out of the [FlowGraphCompiler]! |
| static int EmitTestAndCallCheckCid(compiler::Assembler* assembler, |
| compiler::Label* label, |
| Register class_id_reg, |
| const CidRangeValue& range, |
| int bias, |
| bool jump_on_miss = true); |
| |
| bool IsEmptyBlock(BlockEntryInstr* block) const; |
| |
| private: |
| friend class BoxInt64Instr; // For AddPcRelativeCallStubTarget(). |
| friend class CheckNullInstr; // For AddPcRelativeCallStubTarget(). |
| friend class NullErrorSlowPath; // For AddPcRelativeCallStubTarget(). |
| friend class CheckStackOverflowInstr; // For AddPcRelativeCallStubTarget(). |
| friend class StoreIndexedInstr; // For AddPcRelativeCallStubTarget(). |
| friend class StoreInstanceFieldInstr; // For AddPcRelativeCallStubTarget(). |
| friend class CheckStackOverflowSlowPath; // For pending_deoptimization_env_. |
| friend class CheckedSmiSlowPath; // Same. |
| friend class CheckedSmiComparisonSlowPath; // Same. |
| friend class GraphInstrinsicCodeGenScope; // For optimizing_. |
| |
| // Architecture specific implementation of simple native moves. |
| void EmitNativeMoveArchitecture(const compiler::ffi::NativeLocation& dst, |
| const compiler::ffi::NativeLocation& src); |
| |
| void EmitFrameEntry(); |
| |
| bool TryIntrinsifyHelper(); |
| void AddPcRelativeCallTarget(const Function& function, |
| Code::EntryKind entry_kind); |
| void AddPcRelativeCallStubTarget(const Code& stub_code); |
| void AddPcRelativeTailCallStubTarget(const Code& stub_code); |
| void AddPcRelativeTTSCallTypeTarget(const AbstractType& type); |
| void AddStaticCallTarget(const Function& function, |
| Code::EntryKind entry_kind); |
| |
| void GenerateDeferredCode(); |
| |
| void EmitInstructionPrologue(Instruction* instr); |
| void EmitInstructionEpilogue(Instruction* instr); |
| |
| // Emit code to load a Value into register 'dst'. |
| void LoadValue(Register dst, Value* value); |
| |
| void EmitOptimizedStaticCall( |
| const Function& function, |
| const Array& arguments_descriptor, |
| intptr_t size_with_type_args, |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| LocationSummary* locs, |
| Code::EntryKind entry_kind = Code::EntryKind::kNormal); |
| |
| void EmitUnoptimizedStaticCall( |
| intptr_t size_with_type_args, |
| intptr_t deopt_id, |
| const InstructionSource& source, |
| LocationSummary* locs, |
| const ICData& ic_data, |
| Code::EntryKind entry_kind = Code::EntryKind::kNormal); |
| |
| // Helper for TestAndCall that calculates a good bias that |
| // allows more compact instructions to be emitted. |
| intptr_t ComputeGoodBiasForCidComparison(const CallTargets& sorted, |
| intptr_t max_immediate); |
| |
| // More helpers for EmitTestAndCall. |
| |
| static Register EmitTestCidRegister(); |
| |
| void EmitTestAndCallLoadReceiver(intptr_t count_without_type_args, |
| const Array& arguments_descriptor); |
| |
| void EmitTestAndCallSmiBranch(compiler::Label* label, bool jump_if_smi); |
| |
| void EmitTestAndCallLoadCid(Register class_id_reg); |
| |
| // Type checking helper methods. |
| void CheckClassIds(Register class_id_reg, |
| const GrowableArray<intptr_t>& class_ids, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_lbl); |
| |
| SubtypeTestCachePtr GenerateInlineInstanceof( |
| const InstructionSource& source, |
| const AbstractType& type, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_lbl); |
| |
| SubtypeTestCachePtr GenerateInstantiatedTypeWithArgumentsTest( |
| const InstructionSource& source, |
| const AbstractType& dst_type, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_lbl); |
| |
| bool GenerateInstantiatedTypeNoArgumentsTest( |
| const InstructionSource& source, |
| const AbstractType& dst_type, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_lbl); |
| |
| SubtypeTestCachePtr GenerateUninstantiatedTypeTest( |
| const InstructionSource& source, |
| const AbstractType& dst_type, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_label); |
| |
| SubtypeTestCachePtr GenerateFunctionTypeTest( |
| const InstructionSource& source, |
| const AbstractType& dst_type, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_label); |
| |
| SubtypeTestCachePtr GenerateSubtype1TestCacheLookup( |
| const InstructionSource& source, |
| const Class& type_class, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_lbl); |
| |
| enum TypeTestStubKind { |
| kTestTypeOneArg, |
| kTestTypeThreeArgs, |
| kTestTypeFiveArgs, |
| kTestTypeSevenArgs, |
| }; |
| |
| // Returns type test stub kind for a type test against type parameter type. |
| TypeTestStubKind GetTypeTestStubKindForTypeParameter( |
| const TypeParameter& type_param); |
| |
| // Takes input from TypeTestABI registers (or stack on IA32), see |
| // StubCodeCompiler::GenerateSubtypeNTestCacheStub for caller-save registers. |
| SubtypeTestCachePtr GenerateCallSubtypeTestStub( |
| TypeTestStubKind test_kind, |
| compiler::Label* is_instance_lbl, |
| compiler::Label* is_not_instance_lbl); |
| |
| void GenerateBoolToJump(Register bool_reg, |
| compiler::Label* is_true, |
| compiler::Label* is_false); |
| |
| void GenerateMethodExtractorIntrinsic(const Function& extracted_method, |
| intptr_t type_arguments_field_offset); |
| |
| void GenerateGetterIntrinsic(const Function& accessor, const Field& field); |
| |
| // Perform a greedy local register allocation. Consider all registers free. |
| void AllocateRegistersLocally(Instruction* instr); |
| |
| // Map a block number in a forward iteration into the block number in the |
| // corresponding reverse iteration. Used to obtain an index into |
| // block_order for reverse iterations. |
| intptr_t reverse_index(intptr_t index) const { |
| return block_order_.length() - index - 1; |
| } |
| |
| void set_current_instruction(Instruction* current_instruction) { |
| current_instruction_ = current_instruction; |
| } |
| |
| Instruction* current_instruction() { return current_instruction_; } |
| |
| void CompactBlock(BlockEntryInstr* block); |
| void CompactBlocks(); |
| |
| bool IsListClass(const Class& cls) const { |
| return cls.ptr() == list_class_.ptr(); |
| } |
| |
| void EmitSourceLine(Instruction* instr); |
| |
| intptr_t GetOptimizationThreshold() const; |
| |
| #if defined(DEBUG) |
| void FrameStateUpdateWith(Instruction* instr); |
| void FrameStatePush(Definition* defn); |
| void FrameStatePop(intptr_t count); |
| bool FrameStateIsSafeToCall(); |
| void FrameStateClear(); |
| #endif |
| |
| // Returns true if instruction lookahead (window size one) |
| // is amenable to a peephole optimization. |
| bool IsPeephole(Instruction* instr) const; |
| |
| #if defined(DEBUG) |
| bool CanCallDart() const { |
| return current_instruction_ == nullptr || |
| current_instruction_->CanCallDart(); |
| } |
| #else |
| bool CanCallDart() const { return true; } |
| #endif |
| |
| bool CanPcRelativeCall(const Function& target) const; |
| bool CanPcRelativeCall(const Code& target) const; |
| bool CanPcRelativeCall(const AbstractType& target) const; |
| |
| // This struct contains either function or code, the other one being NULL. |
| class StaticCallsStruct : public ZoneAllocated { |
| public: |
| Code::CallKind call_kind; |
| Code::CallEntryPoint entry_point; |
| const intptr_t offset; |
| const Function* function; // Can be nullptr. |
| const Code* code; // Can be nullptr. |
| const AbstractType* dst_type; // Can be nullptr. |
| StaticCallsStruct(Code::CallKind call_kind, |
| Code::CallEntryPoint entry_point, |
| intptr_t offset_arg, |
| const Function* function_arg, |
| const Code* code_arg, |
| const AbstractType* dst_type) |
| : call_kind(call_kind), |
| entry_point(entry_point), |
| offset(offset_arg), |
| function(function_arg), |
| code(code_arg), |
| dst_type(dst_type) { |
| ASSERT(function == nullptr || function->IsZoneHandle()); |
| ASSERT(code == nullptr || code->IsZoneHandle() || |
| code->IsReadOnlyHandle()); |
| ASSERT(dst_type == nullptr || dst_type->IsZoneHandle() || |
| dst_type->IsReadOnlyHandle()); |
| ASSERT(code == nullptr || dst_type == nullptr); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(StaticCallsStruct); |
| }; |
| |
| Thread* thread_; |
| Zone* zone_; |
| compiler::Assembler* assembler_; |
| const ParsedFunction& parsed_function_; |
| const FlowGraph& flow_graph_; |
| const FlowGraph* intrinsic_flow_graph_ = nullptr; |
| const GrowableArray<BlockEntryInstr*>& block_order_; |
| |
| #if defined(DEBUG) |
| GrowableArray<Representation> frame_state_; |
| #endif |
| |
| // Compiler specific per-block state. Indexed by postorder block number |
| // for convenience. This is not the block's index in the block order, |
| // which is reverse postorder. |
| BlockEntryInstr* current_block_; |
| ExceptionHandlerList* exception_handlers_list_; |
| DescriptorList* pc_descriptors_list_; |
| CompressedStackMapsBuilder* compressed_stackmaps_builder_; |
| CodeSourceMapBuilder* code_source_map_builder_; |
| CatchEntryMovesMapBuilder* catch_entry_moves_maps_builder_; |
| GrowableArray<BlockInfo*> block_info_; |
| GrowableArray<CompilerDeoptInfo*> deopt_infos_; |
| GrowableArray<SlowPathCode*> slow_path_code_; |
| // Fields that were referenced by generated code. |
| // This list is needed by precompiler to ensure they are retained. |
| GrowableArray<const Field*> used_static_fields_; |
| // Stores static call targets as well as stub targets. |
| // TODO(srdjan): Evaluate if we should store allocation stub targets into a |
| // separate table? |
| GrowableArray<StaticCallsStruct*> static_calls_target_table_; |
| // The table selectors of all dispatch table calls in the current function. |
| GrowableArray<const compiler::TableSelector*> dispatch_table_call_targets_; |
| GrowableArray<IndirectGotoInstr*> indirect_gotos_; |
| bool is_optimizing_; |
| SpeculativeInliningPolicy* speculative_policy_; |
| // Set to true if optimized code has IC calls. |
| bool may_reoptimize_; |
| // True while emitting intrinsic code. |
| bool intrinsic_mode_; |
| compiler::Label* intrinsic_slow_path_label_ = nullptr; |
| bool fully_intrinsified_ = false; |
| CodeStatistics* stats_; |
| |
| // The definition whose value is supposed to be at the top of the |
| // expression stack. Used by peephole optimization (window size one) |
| // to eliminate redundant push/pop pairs. |
| Definition* top_of_stack_ = nullptr; |
| |
| const Class& double_class_; |
| const Class& mint_class_; |
| const Class& float32x4_class_; |
| const Class& float64x2_class_; |
| const Class& int32x4_class_; |
| const Class& list_class_; |
| |
| ParallelMoveResolver parallel_move_resolver_; |
| |
| // Currently instructions generate deopt stubs internally by |
| // calling AddDeoptStub. To communicate deoptimization environment |
| // that should be used when deoptimizing we store it in this variable. |
| // In future AddDeoptStub should be moved out of the instruction template. |
| Environment* pending_deoptimization_env_; |
| |
| ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data_; |
| Array& edge_counters_array_; |
| |
| // Instruction currently running EmitNativeCode(). |
| Instruction* current_instruction_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(FlowGraphCompiler); |
| }; |
| |
| } // namespace dart |
| |
| #endif // RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_COMPILER_H_ |