| // 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. | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| #include "vm/deopt_instructions.h" | |
| #include "vm/code_patcher.h" | |
| #include "vm/compiler/assembler/assembler.h" | |
| #include "vm/compiler/assembler/disassembler.h" | |
| #include "vm/compiler/backend/il.h" | |
| #include "vm/compiler/backend/locations.h" | |
| #include "vm/compiler/jit/compiler.h" | |
| #include "vm/parser.h" | |
| #include "vm/stack_frame.h" | |
| #include "vm/thread.h" | |
| #include "vm/timeline.h" | |
| namespace dart { | |
| DEFINE_FLAG(bool, | |
| compress_deopt_info, | |
| true, | |
| "Compress the size of the deoptimization info for optimized code."); | |
| DECLARE_FLAG(bool, trace_deoptimization); | |
| DECLARE_FLAG(bool, trace_deoptimization_verbose); | |
| DeoptContext::DeoptContext(const StackFrame* frame, | |
| const Code& code, | |
| DestFrameOptions dest_options, | |
| fpu_register_t* fpu_registers, | |
| intptr_t* cpu_registers, | |
| bool is_lazy_deopt, | |
| bool deoptimizing_code) | |
| : code_(code.ptr()), | |
| object_pool_(code.GetObjectPool()), | |
| deopt_info_(TypedData::null()), | |
| dest_frame_is_allocated_(false), | |
| dest_frame_(nullptr), | |
| dest_frame_size_(0), | |
| source_frame_is_allocated_(false), | |
| source_frame_(nullptr), | |
| source_frame_size_(0), | |
| cpu_registers_(cpu_registers), | |
| fpu_registers_(fpu_registers), | |
| num_args_(0), | |
| deopt_reason_(ICData::kDeoptUnknown), | |
| deopt_flags_(0), | |
| thread_(Thread::Current()), | |
| deopt_start_micros_(0), | |
| deferred_slots_(nullptr), | |
| deferred_objects_count_(0), | |
| deferred_objects_(nullptr), | |
| is_lazy_deopt_(is_lazy_deopt), | |
| deoptimizing_code_(deoptimizing_code) { | |
| const TypedData& deopt_info = TypedData::Handle( | |
| code.GetDeoptInfoAtPc(frame->pc(), &deopt_reason_, &deopt_flags_)); | |
| #if defined(DEBUG) | |
| if (deopt_info.IsNull()) { | |
| OS::PrintErr("Missing deopt info for pc %" Px "\n", frame->pc()); | |
| DisassembleToStdout formatter; | |
| code.Disassemble(&formatter); | |
| } | |
| #endif | |
| ASSERT(!deopt_info.IsNull()); | |
| deopt_info_ = deopt_info.ptr(); | |
| const Function& function = Function::Handle(code.function()); | |
| // Do not include incoming arguments if there are optional arguments | |
| // (they are copied into local space at method entry). | |
| num_args_ = | |
| function.MakesCopyOfParameters() ? 0 : function.num_fixed_parameters(); | |
| // The fixed size section of the (fake) Dart frame called via a stub by the | |
| // optimized function contains FP, PP (ARM only), PC-marker and | |
| // return-address. This section is copied as well, so that its contained | |
| // values can be updated before returning to the deoptimized function. | |
| ASSERT(frame->fp() >= frame->sp()); | |
| const intptr_t frame_size = (frame->fp() - frame->sp()) / kWordSize; | |
| source_frame_size_ = +kDartFrameFixedSize // For saved values below sp. | |
| + frame_size // For frame size incl. sp. | |
| + 1 // For fp. | |
| + kParamEndSlotFromFp // For saved values above fp. | |
| + num_args_; // For arguments. | |
| source_frame_ = FrameBase(frame); | |
| if (dest_options == kDestIsOriginalFrame) { | |
| // Work from a copy of the source frame. | |
| intptr_t* original_frame = source_frame_; | |
| source_frame_ = new intptr_t[source_frame_size_]; | |
| ASSERT(source_frame_ != nullptr); | |
| for (intptr_t i = 0; i < source_frame_size_; i++) { | |
| source_frame_[i] = original_frame[i]; | |
| } | |
| source_frame_is_allocated_ = true; | |
| } | |
| caller_fp_ = GetSourceFp(); | |
| dest_frame_size_ = DeoptInfo::FrameSize(deopt_info); | |
| if (dest_options == kDestIsAllocated) { | |
| dest_frame_ = new intptr_t[dest_frame_size_]; | |
| ASSERT(source_frame_ != nullptr); | |
| for (intptr_t i = 0; i < dest_frame_size_; i++) { | |
| dest_frame_[i] = 0; | |
| } | |
| dest_frame_is_allocated_ = true; | |
| } | |
| if (dest_options != kDestIsAllocated) { | |
| // kDestIsAllocated is used by the debugger to generate a stack trace | |
| // and does not signal a real deopt. | |
| deopt_start_micros_ = OS::GetCurrentMonotonicMicros(); | |
| } | |
| if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) { | |
| THR_Print( | |
| "Deoptimizing (reason %d '%s') at " | |
| "pc=%" Pp " fp=%" Pp " '%s' (count %d)\n", | |
| deopt_reason(), DeoptReasonToCString(deopt_reason()), frame->pc(), | |
| frame->fp(), function.ToFullyQualifiedCString(), | |
| function.deoptimization_counter()); | |
| } | |
| } | |
| DeoptContext::~DeoptContext() { | |
| // Delete memory for source frame and registers. | |
| if (source_frame_is_allocated_) { | |
| delete[] source_frame_; | |
| } | |
| source_frame_ = nullptr; | |
| delete[] fpu_registers_; | |
| delete[] cpu_registers_; | |
| fpu_registers_ = nullptr; | |
| cpu_registers_ = nullptr; | |
| if (dest_frame_is_allocated_) { | |
| delete[] dest_frame_; | |
| } | |
| dest_frame_ = nullptr; | |
| // Delete all deferred objects. | |
| for (intptr_t i = 0; i < deferred_objects_count_; i++) { | |
| delete deferred_objects_[i]; | |
| } | |
| delete[] deferred_objects_; | |
| deferred_objects_ = nullptr; | |
| deferred_objects_count_ = 0; | |
| #if defined(SUPPORT_TIMELINE) | |
| if (deopt_start_micros_ != 0) { | |
| TimelineStream* compiler_stream = Timeline::GetCompilerStream(); | |
| ASSERT(compiler_stream != nullptr); | |
| if (compiler_stream->enabled()) { | |
| // Allocate all Dart objects needed before calling StartEvent, | |
| // which blocks safe points until Complete is called. | |
| const Code& code = Code::Handle(zone(), code_); | |
| const Function& function = Function::Handle(zone(), code.function()); | |
| const String& function_name = | |
| String::Handle(zone(), function.QualifiedScrubbedName()); | |
| const char* reason = DeoptReasonToCString(deopt_reason()); | |
| const int counter = function.deoptimization_counter(); | |
| TimelineEvent* timeline_event = compiler_stream->StartEvent(); | |
| if (timeline_event != nullptr) { | |
| timeline_event->Duration("Deoptimize", deopt_start_micros_, | |
| OS::GetCurrentMonotonicMicros()); | |
| timeline_event->SetNumArguments(3); | |
| timeline_event->CopyArgument(0, "function", function_name.ToCString()); | |
| timeline_event->CopyArgument(1, "reason", reason); | |
| timeline_event->FormatArgument(2, "deoptimizationCount", "%d", counter); | |
| timeline_event->Complete(); | |
| } | |
| } | |
| } | |
| #endif // !PRODUCT | |
| } | |
| void DeoptContext::VisitObjectPointers(ObjectPointerVisitor* visitor) { | |
| visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&code_)); | |
| visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&object_pool_)); | |
| visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&deopt_info_)); | |
| // Visit any object pointers on the destination stack. | |
| if (dest_frame_is_allocated_) { | |
| for (intptr_t i = 0; i < dest_frame_size_; i++) { | |
| if (dest_frame_[i] != 0) { | |
| visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&dest_frame_[i])); | |
| } | |
| } | |
| } | |
| } | |
| intptr_t DeoptContext::DestStackAdjustment() const { | |
| return dest_frame_size_ - kDartFrameFixedSize - num_args_ - 1 // For fp. | |
| - kParamEndSlotFromFp; | |
| } | |
| intptr_t DeoptContext::GetSourceFp() const { | |
| return source_frame_[source_frame_size_ - 1 - num_args_ - | |
| kParamEndSlotFromFp + kSavedCallerFpSlotFromFp]; | |
| } | |
| intptr_t DeoptContext::GetSourcePp() const { | |
| return source_frame_[source_frame_size_ - 1 - num_args_ - | |
| kParamEndSlotFromFp + | |
| StackFrame::SavedCallerPpSlotFromFp()]; | |
| } | |
| intptr_t DeoptContext::GetSourcePc() const { | |
| return source_frame_[source_frame_size_ - num_args_ + kSavedPcSlotFromSp]; | |
| } | |
| intptr_t DeoptContext::GetCallerFp() const { | |
| return caller_fp_; | |
| } | |
| void DeoptContext::SetCallerFp(intptr_t caller_fp) { | |
| caller_fp_ = caller_fp; | |
| } | |
| static bool IsObjectInstruction(DeoptInstr::Kind kind) { | |
| switch (kind) { | |
| case DeoptInstr::kConstant: | |
| case DeoptInstr::kPp: | |
| case DeoptInstr::kCallerPp: | |
| case DeoptInstr::kMaterializedObjectRef: | |
| case DeoptInstr::kFloat32x4: | |
| case DeoptInstr::kInt32x4: | |
| case DeoptInstr::kFloat64x2: | |
| case DeoptInstr::kWord: | |
| case DeoptInstr::kFloat: | |
| case DeoptInstr::kDouble: | |
| case DeoptInstr::kMint: | |
| case DeoptInstr::kMintPair: | |
| case DeoptInstr::kInt32: | |
| case DeoptInstr::kUint32: | |
| return true; | |
| case DeoptInstr::kRetAddress: | |
| case DeoptInstr::kPcMarker: | |
| case DeoptInstr::kCallerFp: | |
| case DeoptInstr::kCallerPc: | |
| return false; | |
| case DeoptInstr::kMaterializeObject: | |
| default: | |
| // We should not encounter these instructions when filling stack slots. | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| intptr_t DeoptContext::FillDestFrame() { | |
| const Code& code = Code::Handle(code_); | |
| const TypedData& deopt_info = TypedData::Handle(deopt_info_); | |
| GrowableArray<DeoptInstr*> deopt_instructions; | |
| const Array& deopt_table = Array::Handle(code.deopt_info_array()); | |
| ASSERT(!deopt_table.IsNull()); | |
| DeoptInfo::Unpack(deopt_table, deopt_info, &deopt_instructions); | |
| const intptr_t len = deopt_instructions.length(); | |
| const intptr_t frame_size = dest_frame_size_; | |
| // For now, we never place non-objects in the deoptimized frame if | |
| // the destination frame is a copy. This allows us to copy the | |
| // deoptimized frame into an Array. | |
| const bool objects_only = dest_frame_is_allocated_; | |
| // All kMaterializeObject instructions are emitted before the instructions | |
| // that describe stack frames. Skip them and defer materialization of | |
| // objects until the frame is fully reconstructed and it is safe to perform | |
| // GC. | |
| // Arguments (class of the instance to allocate and field-value pairs) are | |
| // described as part of the expression stack for the bottom-most deoptimized | |
| // frame. They will be used during materialization and removed from the stack | |
| // right before control switches to the unoptimized code. | |
| const intptr_t num_materializations = | |
| DeoptInfo::NumMaterializations(deopt_instructions); | |
| PrepareForDeferredMaterialization(num_materializations); | |
| for (intptr_t from_index = 0, to_index = kDartFrameFixedSize; | |
| from_index < num_materializations; from_index++) { | |
| const intptr_t field_count = | |
| DeoptInstr::GetFieldCount(deopt_instructions[from_index]); | |
| intptr_t* args = GetDestFrameAddressAt(to_index); | |
| DeferredObject* obj = new DeferredObject(field_count, args); | |
| SetDeferredObjectAt(from_index, obj); | |
| to_index += obj->ArgumentCount(); | |
| } | |
| // Populate stack frames. | |
| intptr_t frame_count = 0; | |
| for (intptr_t to_index = frame_size - 1, from_index = len - 1; to_index >= 0; | |
| to_index--, from_index--) { | |
| intptr_t* to_addr = GetDestFrameAddressAt(to_index); | |
| DeoptInstr* instr = deopt_instructions[from_index]; | |
| if (!objects_only || IsObjectInstruction(instr->kind())) { | |
| instr->Execute(this, to_addr); | |
| } else { | |
| *reinterpret_cast<ObjectPtr*>(to_addr) = Object::null(); | |
| } | |
| if (instr->kind() == DeoptInstr::kRetAddress) { | |
| frame_count++; | |
| } | |
| } | |
| if (FLAG_trace_deoptimization_verbose) { | |
| for (intptr_t i = 0; i < frame_size; i++) { | |
| intptr_t* to_addr = GetDestFrameAddressAt(i); | |
| THR_Print("*%" Pd ". [%p] 0x%" Px " [%s]\n", i, to_addr, *to_addr, | |
| deopt_instructions[i + (len - frame_size)]->ToCString()); | |
| } | |
| } | |
| return frame_count; | |
| } | |
| static void FillDeferredSlots(DeoptContext* deopt_context, | |
| DeferredSlot** slot_list) { | |
| DeferredSlot* slot = *slot_list; | |
| *slot_list = nullptr; | |
| while (slot != nullptr) { | |
| DeferredSlot* current = slot; | |
| slot = slot->next(); | |
| current->Materialize(deopt_context); | |
| delete current; | |
| } | |
| } | |
| // Materializes all deferred objects. Returns the total number of | |
| // artificial arguments used during deoptimization. | |
| intptr_t DeoptContext::MaterializeDeferredObjects() { | |
| // Populate slots with references to all unboxed "primitive" values (doubles, | |
| // mints, simd) and deferred objects. Deferred objects are only allocated | |
| // but not filled with data. This is done later because deferred objects | |
| // can references each other. | |
| FillDeferredSlots(this, &deferred_slots_); | |
| // Compute total number of artificial arguments used during deoptimization. | |
| intptr_t deopt_arg_count = 0; | |
| for (intptr_t i = 0; i < DeferredObjectsCount(); i++) { | |
| GetDeferredObject(i)->Fill(); | |
| deopt_arg_count += GetDeferredObject(i)->ArgumentCount(); | |
| } | |
| // Since this is the only step where GC can occur during deoptimization, | |
| // use it to report the source line where deoptimization occurred. | |
| if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) { | |
| DartFrameIterator iterator(Thread::Current(), | |
| StackFrameIterator::kNoCrossThreadIteration); | |
| StackFrame* top_frame = iterator.NextFrame(); | |
| ASSERT(top_frame != nullptr); | |
| const Code& code = Code::Handle(top_frame->LookupDartCode()); | |
| const Function& top_function = Function::Handle(code.function()); | |
| const Script& script = Script::Handle(top_function.script()); | |
| const TokenPosition token_pos = code.GetTokenIndexOfPC(top_frame->pc()); | |
| THR_Print(" Function: %s\n", top_function.ToFullyQualifiedCString()); | |
| intptr_t line; | |
| if (script.GetTokenLocation(token_pos, &line)) { | |
| String& line_string = String::Handle(script.GetLine(line)); | |
| char line_buffer[80]; | |
| Utils::SNPrint(line_buffer, sizeof(line_buffer), " Line %" Pd ": '%s'", | |
| line, line_string.ToCString()); | |
| THR_Print("%s\n", line_buffer); | |
| } | |
| THR_Print(" Deopt args: %" Pd "\n", deopt_arg_count); | |
| } | |
| return deopt_arg_count; | |
| } | |
| ArrayPtr DeoptContext::DestFrameAsArray() { | |
| ASSERT(dest_frame_ != nullptr && dest_frame_is_allocated_); | |
| const Array& dest_array = Array::Handle(zone(), Array::New(dest_frame_size_)); | |
| PassiveObject& obj = PassiveObject::Handle(zone()); | |
| for (intptr_t i = 0; i < dest_frame_size_; i++) { | |
| obj = static_cast<ObjectPtr>(dest_frame_[i]); | |
| dest_array.SetAt(i, obj); | |
| } | |
| return dest_array.ptr(); | |
| } | |
| // Deoptimization instruction creating return address using function and | |
| // deopt-id stored at 'object_table_index'. | |
| class DeoptRetAddressInstr : public DeoptInstr { | |
| public: | |
| DeoptRetAddressInstr(intptr_t object_table_index, intptr_t deopt_id) | |
| : object_table_index_(object_table_index), deopt_id_(deopt_id) { | |
| ASSERT(object_table_index >= 0); | |
| ASSERT(deopt_id >= 0); | |
| } | |
| explicit DeoptRetAddressInstr(intptr_t source_index) | |
| : object_table_index_(ObjectTableIndex::decode(source_index)), | |
| deopt_id_(DeoptId::decode(source_index)) {} | |
| virtual intptr_t source_index() const { | |
| return ObjectTableIndex::encode(object_table_index_) | | |
| DeoptId::encode(deopt_id_); | |
| } | |
| virtual DeoptInstr::Kind kind() const { return kRetAddress; } | |
| virtual const char* ArgumentsToCString() const { | |
| return Thread::Current()->zone()->PrintToString( | |
| "%" Pd ", %" Pd "", object_table_index_, deopt_id_); | |
| } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| *dest_addr = Smi::RawValue(0); | |
| deopt_context->DeferRetAddrMaterialization(object_table_index_, deopt_id_, | |
| dest_addr); | |
| } | |
| intptr_t object_table_index() const { return object_table_index_; } | |
| intptr_t deopt_id() const { return deopt_id_; } | |
| private: | |
| static constexpr intptr_t kFieldWidth = kBitsPerWord / 2; | |
| using ObjectTableIndex = BitField<intptr_t, intptr_t, 0, kFieldWidth>; | |
| using DeoptId = | |
| BitField<intptr_t, intptr_t, ObjectTableIndex::kNextBit, kFieldWidth>; | |
| const intptr_t object_table_index_; | |
| const intptr_t deopt_id_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptRetAddressInstr); | |
| }; | |
| // Deoptimization instruction moving a constant stored at 'object_table_index'. | |
| class DeoptConstantInstr : public DeoptInstr { | |
| public: | |
| explicit DeoptConstantInstr(intptr_t object_table_index) | |
| : object_table_index_(object_table_index) { | |
| ASSERT(object_table_index >= 0); | |
| } | |
| virtual intptr_t source_index() const { return object_table_index_; } | |
| virtual DeoptInstr::Kind kind() const { return kConstant; } | |
| virtual const char* ArgumentsToCString() const { | |
| return Thread::Current()->zone()->PrintToString("%" Pd "", | |
| object_table_index_); | |
| } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| const PassiveObject& obj = PassiveObject::Handle( | |
| deopt_context->zone(), deopt_context->ObjectAt(object_table_index_)); | |
| *reinterpret_cast<ObjectPtr*>(dest_addr) = obj.ptr(); | |
| } | |
| private: | |
| const intptr_t object_table_index_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptConstantInstr); | |
| }; | |
| // Deoptimization instruction moving value from optimized frame at | |
| // 'source_index' to specified slots in the unoptimized frame. | |
| // 'source_index' represents the slot index of the frame (0 being | |
| // first argument) and accounts for saved return address, frame | |
| // pointer, pool pointer and pc marker. | |
| // Deoptimization instruction moving a CPU register. | |
| class DeoptWordInstr : public DeoptInstr { | |
| public: | |
| explicit DeoptWordInstr(intptr_t source_index) : source_(source_index) {} | |
| explicit DeoptWordInstr(const CpuRegisterSource& source) : source_(source) {} | |
| virtual intptr_t source_index() const { return source_.source_index(); } | |
| virtual DeoptInstr::Kind kind() const { return kWord; } | |
| virtual const char* ArgumentsToCString() const { return source_.ToCString(); } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| *dest_addr = source_.Value<intptr_t>(deopt_context); | |
| } | |
| private: | |
| const CpuRegisterSource source_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptWordInstr); | |
| }; | |
| class DeoptIntegerInstrBase : public DeoptInstr { | |
| public: | |
| DeoptIntegerInstrBase() {} | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| const int64_t value = GetValue(deopt_context); | |
| if (Smi::IsValid(value)) { | |
| *dest_addr = Smi::RawValue(static_cast<intptr_t>(value)); | |
| } else { | |
| *dest_addr = Smi::RawValue(0); | |
| deopt_context->DeferMintMaterialization( | |
| value, reinterpret_cast<MintPtr*>(dest_addr)); | |
| } | |
| } | |
| virtual int64_t GetValue(DeoptContext* deopt_context) = 0; | |
| private: | |
| DISALLOW_COPY_AND_ASSIGN(DeoptIntegerInstrBase); | |
| }; | |
| class DeoptMintPairInstr : public DeoptIntegerInstrBase { | |
| public: | |
| explicit DeoptMintPairInstr(intptr_t source_index) | |
| : DeoptIntegerInstrBase(), | |
| lo_(LoRegister::decode(source_index)), | |
| hi_(HiRegister::decode(source_index)) {} | |
| DeoptMintPairInstr(const CpuRegisterSource& lo, const CpuRegisterSource& hi) | |
| : DeoptIntegerInstrBase(), lo_(lo), hi_(hi) {} | |
| virtual intptr_t source_index() const { | |
| return LoRegister::encode(lo_.source_index()) | | |
| HiRegister::encode(hi_.source_index()); | |
| } | |
| virtual DeoptInstr::Kind kind() const { return kMintPair; } | |
| virtual const char* ArgumentsToCString() const { | |
| return Thread::Current()->zone()->PrintToString("%s,%s", lo_.ToCString(), | |
| hi_.ToCString()); | |
| } | |
| virtual int64_t GetValue(DeoptContext* deopt_context) { | |
| return Utils::LowHighTo64Bits(lo_.Value<uint32_t>(deopt_context), | |
| hi_.Value<int32_t>(deopt_context)); | |
| } | |
| private: | |
| static constexpr intptr_t kFieldWidth = kBitsPerWord / 2; | |
| using LoRegister = BitField<intptr_t, intptr_t, 0, kFieldWidth>; | |
| using HiRegister = | |
| BitField<intptr_t, intptr_t, LoRegister::kNextBit, kFieldWidth>; | |
| const CpuRegisterSource lo_; | |
| const CpuRegisterSource hi_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptMintPairInstr); | |
| }; | |
| template <DeoptInstr::Kind K, CatchEntryMove::SourceKind slot_kind, typename T> | |
| class DeoptIntInstr : public DeoptIntegerInstrBase { | |
| public: | |
| explicit DeoptIntInstr(intptr_t source_index) | |
| : DeoptIntegerInstrBase(), source_(source_index) {} | |
| explicit DeoptIntInstr(const CpuRegisterSource& source) | |
| : DeoptIntegerInstrBase(), source_(source) {} | |
| virtual intptr_t source_index() const { return source_.source_index(); } | |
| virtual DeoptInstr::Kind kind() const { return K; } | |
| virtual const char* ArgumentsToCString() const { return source_.ToCString(); } | |
| virtual int64_t GetValue(DeoptContext* deopt_context) { | |
| return static_cast<int64_t>(source_.Value<T>(deopt_context)); | |
| } | |
| private: | |
| const CpuRegisterSource source_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptIntInstr); | |
| }; | |
| typedef DeoptIntInstr<DeoptInstr::kUint32, | |
| CatchEntryMove::SourceKind::kUint32Slot, | |
| uint32_t> | |
| DeoptUint32Instr; | |
| typedef DeoptIntInstr<DeoptInstr::kInt32, | |
| CatchEntryMove::SourceKind::kInt32Slot, | |
| int32_t> | |
| DeoptInt32Instr; | |
| typedef DeoptIntInstr<DeoptInstr::kMint, | |
| CatchEntryMove::SourceKind::kInt64Slot, | |
| int64_t> | |
| DeoptMintInstr; | |
| template <DeoptInstr::Kind K, | |
| CatchEntryMove::SourceKind slot_kind, | |
| typename Type, | |
| typename PtrType> | |
| class DeoptFpuInstr : public DeoptInstr { | |
| public: | |
| explicit DeoptFpuInstr(intptr_t source_index) : source_(source_index) {} | |
| explicit DeoptFpuInstr(const FpuRegisterSource& source) : source_(source) {} | |
| virtual intptr_t source_index() const { return source_.source_index(); } | |
| virtual DeoptInstr::Kind kind() const { return K; } | |
| virtual const char* ArgumentsToCString() const { return source_.ToCString(); } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| *dest_addr = Smi::RawValue(0); | |
| deopt_context->DeferMaterialization(source_.Value<Type>(deopt_context), | |
| reinterpret_cast<PtrType*>(dest_addr)); | |
| } | |
| private: | |
| const FpuRegisterSource source_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptFpuInstr); | |
| }; | |
| typedef DeoptFpuInstr<DeoptInstr::kFloat, | |
| CatchEntryMove::SourceKind::kFloatSlot, | |
| float, | |
| DoublePtr> | |
| DeoptFloatInstr; | |
| typedef DeoptFpuInstr<DeoptInstr::kDouble, | |
| CatchEntryMove::SourceKind::kDoubleSlot, | |
| double, | |
| DoublePtr> | |
| DeoptDoubleInstr; | |
| // Simd128 types. | |
| typedef DeoptFpuInstr<DeoptInstr::kFloat32x4, | |
| CatchEntryMove::SourceKind::kFloat32x4Slot, | |
| simd128_value_t, | |
| Float32x4Ptr> | |
| DeoptFloat32x4Instr; | |
| typedef DeoptFpuInstr<DeoptInstr::kFloat64x2, | |
| CatchEntryMove::SourceKind::kFloat64x2Slot, | |
| simd128_value_t, | |
| Float64x2Ptr> | |
| DeoptFloat64x2Instr; | |
| typedef DeoptFpuInstr<DeoptInstr::kInt32x4, | |
| CatchEntryMove::SourceKind::kInt32x4Slot, | |
| simd128_value_t, | |
| Int32x4Ptr> | |
| DeoptInt32x4Instr; | |
| // Deoptimization instruction creating a PC marker for the code of | |
| // function at 'object_table_index'. | |
| class DeoptPcMarkerInstr : public DeoptInstr { | |
| public: | |
| explicit DeoptPcMarkerInstr(intptr_t object_table_index) | |
| : object_table_index_(object_table_index) { | |
| ASSERT(object_table_index >= 0); | |
| } | |
| virtual intptr_t source_index() const { return object_table_index_; } | |
| virtual DeoptInstr::Kind kind() const { return kPcMarker; } | |
| virtual const char* ArgumentsToCString() const { | |
| return Thread::Current()->zone()->PrintToString("%" Pd "", | |
| object_table_index_); | |
| } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| Function& function = Function::Handle(deopt_context->zone()); | |
| function ^= deopt_context->ObjectAt(object_table_index_); | |
| if (function.IsNull()) { | |
| *reinterpret_cast<ObjectPtr*>(dest_addr) = | |
| deopt_context->is_lazy_deopt() | |
| ? StubCode::DeoptimizeLazyFromReturn().ptr() | |
| : StubCode::Deoptimize().ptr(); | |
| return; | |
| } | |
| // We don't always have the Code object for the frame's corresponding | |
| // unoptimized code as it may have been collected. Use a stub as the pc | |
| // marker until we can recreate that Code object during deferred | |
| // materialization to maintain the invariant that Dart frames always have | |
| // a pc marker. | |
| *reinterpret_cast<ObjectPtr*>(dest_addr) = | |
| StubCode::FrameAwaitingMaterialization().ptr(); | |
| deopt_context->DeferPcMarkerMaterialization(object_table_index_, dest_addr); | |
| } | |
| private: | |
| intptr_t object_table_index_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptPcMarkerInstr); | |
| }; | |
| // Deoptimization instruction creating a pool pointer for the code of | |
| // function at 'object_table_index'. | |
| class DeoptPpInstr : public DeoptInstr { | |
| public: | |
| explicit DeoptPpInstr(intptr_t object_table_index) | |
| : object_table_index_(object_table_index) { | |
| ASSERT(object_table_index >= 0); | |
| } | |
| virtual intptr_t source_index() const { return object_table_index_; } | |
| virtual DeoptInstr::Kind kind() const { return kPp; } | |
| virtual const char* ArgumentsToCString() const { | |
| return Thread::Current()->zone()->PrintToString("%" Pd "", | |
| object_table_index_); | |
| } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| *dest_addr = Smi::RawValue(0); | |
| deopt_context->DeferPpMaterialization( | |
| object_table_index_, reinterpret_cast<ObjectPtr*>(dest_addr)); | |
| } | |
| private: | |
| intptr_t object_table_index_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptPpInstr); | |
| }; | |
| // Deoptimization instruction copying the caller saved FP from optimized frame. | |
| class DeoptCallerFpInstr : public DeoptInstr { | |
| public: | |
| DeoptCallerFpInstr() {} | |
| virtual intptr_t source_index() const { return 0; } | |
| virtual DeoptInstr::Kind kind() const { return kCallerFp; } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| *dest_addr = deopt_context->GetCallerFp(); | |
| deopt_context->SetCallerFp( | |
| reinterpret_cast<intptr_t>(dest_addr - kSavedCallerFpSlotFromFp)); | |
| } | |
| private: | |
| DISALLOW_COPY_AND_ASSIGN(DeoptCallerFpInstr); | |
| }; | |
| // Deoptimization instruction copying the caller saved PP from optimized frame. | |
| class DeoptCallerPpInstr : public DeoptInstr { | |
| public: | |
| DeoptCallerPpInstr() {} | |
| virtual intptr_t source_index() const { return 0; } | |
| virtual DeoptInstr::Kind kind() const { return kCallerPp; } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| *dest_addr = deopt_context->GetSourcePp(); | |
| } | |
| private: | |
| DISALLOW_COPY_AND_ASSIGN(DeoptCallerPpInstr); | |
| }; | |
| // Deoptimization instruction copying the caller return address from optimized | |
| // frame. | |
| class DeoptCallerPcInstr : public DeoptInstr { | |
| public: | |
| DeoptCallerPcInstr() {} | |
| virtual intptr_t source_index() const { return 0; } | |
| virtual DeoptInstr::Kind kind() const { return kCallerPc; } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| *dest_addr = deopt_context->GetSourcePc(); | |
| } | |
| private: | |
| DISALLOW_COPY_AND_ASSIGN(DeoptCallerPcInstr); | |
| }; | |
| // Write reference to a materialized object with the given index into the | |
| // stack slot. | |
| class DeoptMaterializedObjectRefInstr : public DeoptInstr { | |
| public: | |
| explicit DeoptMaterializedObjectRefInstr(intptr_t index) : index_(index) { | |
| ASSERT(index >= 0); | |
| } | |
| virtual intptr_t source_index() const { return index_; } | |
| virtual DeoptInstr::Kind kind() const { return kMaterializedObjectRef; } | |
| virtual const char* ArgumentsToCString() const { | |
| return Thread::Current()->zone()->PrintToString("#%" Pd "", index_); | |
| } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| *reinterpret_cast<SmiPtr*>(dest_addr) = Smi::New(0); | |
| deopt_context->DeferMaterializedObjectRef(index_, dest_addr); | |
| } | |
| private: | |
| intptr_t index_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptMaterializedObjectRefInstr); | |
| }; | |
| // Materialize object with the given number of fields. | |
| // Arguments for materialization (class and field-value pairs) are pushed | |
| // to the expression stack of the bottom-most frame. | |
| class DeoptMaterializeObjectInstr : public DeoptInstr { | |
| public: | |
| explicit DeoptMaterializeObjectInstr(intptr_t field_count) | |
| : field_count_(field_count) { | |
| ASSERT(field_count >= 0); | |
| } | |
| virtual intptr_t source_index() const { return field_count_; } | |
| virtual DeoptInstr::Kind kind() const { return kMaterializeObject; } | |
| virtual const char* ArgumentsToCString() const { | |
| return Thread::Current()->zone()->PrintToString("%" Pd "", field_count_); | |
| } | |
| void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) { | |
| // This instructions are executed manually by the DeoptimizeWithDeoptInfo. | |
| UNREACHABLE(); | |
| } | |
| private: | |
| intptr_t field_count_; | |
| DISALLOW_COPY_AND_ASSIGN(DeoptMaterializeObjectInstr); | |
| }; | |
| uword DeoptInstr::GetRetAddress(DeoptInstr* instr, | |
| const ObjectPool& object_table, | |
| Code* code) { | |
| ASSERT(instr->kind() == kRetAddress); | |
| DeoptRetAddressInstr* ret_address_instr = | |
| static_cast<DeoptRetAddressInstr*>(instr); | |
| ASSERT(!object_table.IsNull()); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| Function& function = Function::Handle(zone); | |
| function ^= object_table.ObjectAt(ret_address_instr->object_table_index()); | |
| ASSERT(!function.ForceOptimize()); | |
| ASSERT(code != nullptr); | |
| const Error& error = | |
| Error::Handle(zone, Compiler::EnsureUnoptimizedCode(thread, function)); | |
| if (!error.IsNull()) { | |
| Exceptions::PropagateError(error); | |
| } | |
| *code = function.unoptimized_code(); | |
| ASSERT(!code->IsNull()); | |
| uword res = code->GetPcForDeoptId(ret_address_instr->deopt_id(), | |
| UntaggedPcDescriptors::kDeopt); | |
| ASSERT(res != 0); | |
| return res; | |
| } | |
| DeoptInstr* DeoptInstr::Create(intptr_t kind_as_int, intptr_t source_index) { | |
| Kind kind = static_cast<Kind>(kind_as_int); | |
| switch (kind) { | |
| case kWord: | |
| return new DeoptWordInstr(source_index); | |
| case kFloat: | |
| return new DeoptFloatInstr(source_index); | |
| case kDouble: | |
| return new DeoptDoubleInstr(source_index); | |
| case kMint: | |
| return new DeoptMintInstr(source_index); | |
| case kMintPair: | |
| return new DeoptMintPairInstr(source_index); | |
| case kInt32: | |
| return new DeoptInt32Instr(source_index); | |
| case kUint32: | |
| return new DeoptUint32Instr(source_index); | |
| case kFloat32x4: | |
| return new DeoptFloat32x4Instr(source_index); | |
| case kFloat64x2: | |
| return new DeoptFloat64x2Instr(source_index); | |
| case kInt32x4: | |
| return new DeoptInt32x4Instr(source_index); | |
| case kRetAddress: | |
| return new DeoptRetAddressInstr(source_index); | |
| case kConstant: | |
| return new DeoptConstantInstr(source_index); | |
| case kPcMarker: | |
| return new DeoptPcMarkerInstr(source_index); | |
| case kPp: | |
| return new DeoptPpInstr(source_index); | |
| case kCallerFp: | |
| return new DeoptCallerFpInstr(); | |
| case kCallerPp: | |
| return new DeoptCallerPpInstr(); | |
| case kCallerPc: | |
| return new DeoptCallerPcInstr(); | |
| case kMaterializedObjectRef: | |
| return new DeoptMaterializedObjectRefInstr(source_index); | |
| case kMaterializeObject: | |
| return new DeoptMaterializeObjectInstr(source_index); | |
| } | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| const char* DeoptInstr::KindToCString(Kind kind) { | |
| switch (kind) { | |
| case kWord: | |
| return "word"; | |
| case kFloat: | |
| return "float"; | |
| case kDouble: | |
| return "double"; | |
| case kMint: | |
| case kMintPair: | |
| return "mint"; | |
| case kInt32: | |
| return "int32"; | |
| case kUint32: | |
| return "uint32"; | |
| case kFloat32x4: | |
| return "float32x4"; | |
| case kFloat64x2: | |
| return "float64x2"; | |
| case kInt32x4: | |
| return "int32x4"; | |
| case kRetAddress: | |
| return "retaddr"; | |
| case kConstant: | |
| return "const"; | |
| case kPcMarker: | |
| return "pc"; | |
| case kPp: | |
| return "pp"; | |
| case kCallerFp: | |
| return "callerfp"; | |
| case kCallerPp: | |
| return "callerpp"; | |
| case kCallerPc: | |
| return "callerpc"; | |
| case kMaterializedObjectRef: | |
| return "ref"; | |
| case kMaterializeObject: | |
| return "mat"; | |
| } | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| class DeoptInfoBuilder::TrieNode : public ZoneObject { | |
| public: | |
| // Construct the root node representing the implicit "shared" terminator | |
| // at the end of each deopt info. | |
| TrieNode() : instruction_(nullptr), info_number_(-1), children_(16) {} | |
| // Construct a node representing a written instruction. | |
| TrieNode(DeoptInstr* instruction, intptr_t info_number) | |
| : instruction_(instruction), info_number_(info_number), children_(4) {} | |
| intptr_t info_number() const { return info_number_; } | |
| void AddChild(TrieNode* child) { | |
| if (child != nullptr) children_.Add(child); | |
| } | |
| TrieNode* FindChild(const DeoptInstr& instruction) { | |
| for (intptr_t i = 0; i < children_.length(); ++i) { | |
| TrieNode* child = children_[i]; | |
| if (child->instruction_->Equals(instruction)) return child; | |
| } | |
| return nullptr; | |
| } | |
| private: | |
| const DeoptInstr* instruction_; // Instruction that was written. | |
| const intptr_t info_number_; // Index of the deopt info it was written to. | |
| GrowableArray<TrieNode*> children_; | |
| }; | |
| DeoptInfoBuilder::DeoptInfoBuilder(Zone* zone, | |
| const intptr_t num_args, | |
| compiler::Assembler* assembler) | |
| : zone_(zone), | |
| instructions_(), | |
| num_args_(num_args), | |
| assembler_(assembler), | |
| trie_root_(new (zone) TrieNode()), | |
| current_info_number_(0), | |
| frame_start_(-1), | |
| materializations_() {} | |
| intptr_t DeoptInfoBuilder::FindOrAddObjectInTable(const Object& obj) const { | |
| return assembler_->object_pool_builder().FindObject(obj); | |
| } | |
| intptr_t DeoptInfoBuilder::CalculateStackIndex( | |
| const Location& source_loc) const { | |
| intptr_t index = -compiler::target::frame_layout.VariableIndexForFrameSlot( | |
| source_loc.stack_index()); | |
| return index < 0 ? index + num_args_ | |
| : index + num_args_ + kDartFrameFixedSize; | |
| } | |
| CpuRegisterSource DeoptInfoBuilder::ToCpuRegisterSource(const Location& loc) { | |
| if (loc.IsRegister()) { | |
| return CpuRegisterSource(CpuRegisterSource::kRegister, loc.reg()); | |
| } else { | |
| ASSERT(loc.IsStackSlot()); | |
| return CpuRegisterSource(CpuRegisterSource::kStackSlot, | |
| CalculateStackIndex(loc)); | |
| } | |
| } | |
| FpuRegisterSource DeoptInfoBuilder::ToFpuRegisterSource( | |
| const Location& loc, | |
| Location::Kind stack_slot_kind) { | |
| if (loc.IsFpuRegister()) { | |
| return FpuRegisterSource(FpuRegisterSource::kRegister, loc.fpu_reg()); | |
| } else { | |
| ASSERT((stack_slot_kind == Location::kQuadStackSlot) || | |
| (stack_slot_kind == Location::kDoubleStackSlot)); | |
| ASSERT(loc.kind() == stack_slot_kind); | |
| return FpuRegisterSource(FpuRegisterSource::kStackSlot, | |
| CalculateStackIndex(loc)); | |
| } | |
| } | |
| void DeoptInfoBuilder::AddReturnAddress(const Function& function, | |
| intptr_t deopt_id, | |
| intptr_t dest_index) { | |
| const intptr_t object_table_index = FindOrAddObjectInTable(function); | |
| ASSERT(dest_index == FrameSize()); | |
| instructions_.Add(new (zone()) | |
| DeoptRetAddressInstr(object_table_index, deopt_id)); | |
| } | |
| void DeoptInfoBuilder::AddPcMarker(const Function& function, | |
| intptr_t dest_index) { | |
| intptr_t object_table_index = FindOrAddObjectInTable(function); | |
| ASSERT(dest_index == FrameSize()); | |
| instructions_.Add(new (zone()) DeoptPcMarkerInstr(object_table_index)); | |
| } | |
| void DeoptInfoBuilder::AddPp(const Function& function, intptr_t dest_index) { | |
| intptr_t object_table_index = FindOrAddObjectInTable(function); | |
| ASSERT(dest_index == FrameSize()); | |
| instructions_.Add(new (zone()) DeoptPpInstr(object_table_index)); | |
| } | |
| void DeoptInfoBuilder::AddCopy(Value* value, | |
| const Location& source_loc, | |
| const intptr_t dest_index) { | |
| DeoptInstr* deopt_instr = nullptr; | |
| if (source_loc.IsConstant()) { | |
| intptr_t object_table_index = FindOrAddObjectInTable(source_loc.constant()); | |
| deopt_instr = new (zone()) DeoptConstantInstr(object_table_index); | |
| } else if (source_loc.IsInvalid() && | |
| value->definition()->IsMaterializeObject()) { | |
| const intptr_t index = | |
| FindMaterialization(value->definition()->AsMaterializeObject()); | |
| ASSERT(index >= 0); | |
| deopt_instr = new (zone()) DeoptMaterializedObjectRefInstr(index); | |
| } else { | |
| ASSERT(!source_loc.IsInvalid()); | |
| Representation rep = value->definition()->representation(); | |
| switch (rep) { | |
| case kTagged: | |
| deopt_instr = | |
| new (zone()) DeoptWordInstr(ToCpuRegisterSource(source_loc)); | |
| break; | |
| #if defined(TARGET_ARCH_IS_64_BIT) | |
| case kUntagged: | |
| #endif | |
| case kUnboxedInt64: { | |
| if (source_loc.IsPairLocation()) { | |
| PairLocation* pair = source_loc.AsPairLocation(); | |
| deopt_instr = | |
| new (zone()) DeoptMintPairInstr(ToCpuRegisterSource(pair->At(0)), | |
| ToCpuRegisterSource(pair->At(1))); | |
| } else { | |
| ASSERT(!source_loc.IsPairLocation()); | |
| deopt_instr = | |
| new (zone()) DeoptMintInstr(ToCpuRegisterSource(source_loc)); | |
| } | |
| break; | |
| } | |
| #if defined(TARGET_ARCH_IS_32_BIT) | |
| case kUntagged: | |
| #endif | |
| case kUnboxedInt32: | |
| deopt_instr = | |
| new (zone()) DeoptInt32Instr(ToCpuRegisterSource(source_loc)); | |
| break; | |
| case kUnboxedUint32: | |
| deopt_instr = | |
| new (zone()) DeoptUint32Instr(ToCpuRegisterSource(source_loc)); | |
| break; | |
| case kUnboxedFloat: | |
| deopt_instr = new (zone()) DeoptFloatInstr( | |
| ToFpuRegisterSource(source_loc, Location::kDoubleStackSlot)); | |
| break; | |
| case kUnboxedDouble: | |
| deopt_instr = new (zone()) DeoptDoubleInstr( | |
| ToFpuRegisterSource(source_loc, Location::kDoubleStackSlot)); | |
| break; | |
| case kUnboxedFloat32x4: | |
| deopt_instr = new (zone()) DeoptFloat32x4Instr( | |
| ToFpuRegisterSource(source_loc, Location::kQuadStackSlot)); | |
| break; | |
| case kUnboxedFloat64x2: | |
| deopt_instr = new (zone()) DeoptFloat64x2Instr( | |
| ToFpuRegisterSource(source_loc, Location::kQuadStackSlot)); | |
| break; | |
| case kUnboxedInt32x4: | |
| deopt_instr = new (zone()) DeoptInt32x4Instr( | |
| ToFpuRegisterSource(source_loc, Location::kQuadStackSlot)); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| } | |
| ASSERT(dest_index == FrameSize()); | |
| ASSERT(deopt_instr != nullptr); | |
| instructions_.Add(deopt_instr); | |
| } | |
| void DeoptInfoBuilder::AddCallerFp(intptr_t dest_index) { | |
| ASSERT(dest_index == FrameSize()); | |
| instructions_.Add(new (zone()) DeoptCallerFpInstr()); | |
| } | |
| void DeoptInfoBuilder::AddCallerPp(intptr_t dest_index) { | |
| ASSERT(dest_index == FrameSize()); | |
| instructions_.Add(new (zone()) DeoptCallerPpInstr()); | |
| } | |
| void DeoptInfoBuilder::AddCallerPc(intptr_t dest_index) { | |
| ASSERT(dest_index == FrameSize()); | |
| instructions_.Add(new (zone()) DeoptCallerPcInstr()); | |
| } | |
| void DeoptInfoBuilder::AddConstant(const Object& obj, intptr_t dest_index) { | |
| ASSERT(dest_index == FrameSize()); | |
| intptr_t object_table_index = FindOrAddObjectInTable(obj); | |
| instructions_.Add(new (zone()) DeoptConstantInstr(object_table_index)); | |
| } | |
| void DeoptInfoBuilder::AddMaterialization(MaterializeObjectInstr* mat) { | |
| const intptr_t index = FindMaterialization(mat); | |
| if (index >= 0) { | |
| return; // Already added. | |
| } | |
| materializations_.Add(mat); | |
| // Count initialized fields and emit kMaterializeObject instruction. | |
| // There is no need to write nulls into fields because object is null | |
| // initialized by default. | |
| intptr_t non_null_fields = 0; | |
| for (intptr_t i = 0; i < mat->InputCount(); i++) { | |
| if (!mat->InputAt(i)->BindsToConstantNull()) { | |
| non_null_fields++; | |
| } | |
| } | |
| instructions_.Add(new (zone()) DeoptMaterializeObjectInstr(non_null_fields)); | |
| for (intptr_t i = 0; i < mat->InputCount(); i++) { | |
| MaterializeObjectInstr* nested_mat = | |
| mat->InputAt(i)->definition()->AsMaterializeObject(); | |
| if (nested_mat != nullptr) { | |
| AddMaterialization(nested_mat); | |
| } | |
| } | |
| } | |
| intptr_t DeoptInfoBuilder::EmitMaterializationArguments(intptr_t dest_index) { | |
| ASSERT(dest_index == kDartFrameFixedSize); | |
| for (intptr_t i = 0; i < materializations_.length(); i++) { | |
| MaterializeObjectInstr* mat = materializations_[i]; | |
| // Class of the instance to allocate. | |
| AddConstant(mat->cls(), dest_index++); | |
| AddConstant(Smi::ZoneHandle(Smi::New(mat->length_or_shape())), | |
| dest_index++); | |
| for (intptr_t i = 0; i < mat->InputCount(); i++) { | |
| if (!mat->InputAt(i)->BindsToConstantNull()) { | |
| // Emit offset-value pair. | |
| AddConstant(Smi::ZoneHandle(Smi::New(mat->FieldOffsetAt(i))), | |
| dest_index++); | |
| AddCopy(mat->InputAt(i), mat->LocationAt(i), dest_index++); | |
| } | |
| } | |
| } | |
| return dest_index; | |
| } | |
| intptr_t DeoptInfoBuilder::FindMaterialization( | |
| MaterializeObjectInstr* mat) const { | |
| for (intptr_t i = 0; i < materializations_.length(); i++) { | |
| if (materializations_[i] == mat) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| TypedDataPtr DeoptInfoBuilder::CreateDeoptInfo(const Array& deopt_table) { | |
| intptr_t length = instructions_.length(); | |
| // Count the number of instructions that are a shared suffix of some deopt | |
| // info already written. | |
| TrieNode* suffix = trie_root_; | |
| intptr_t suffix_length = 0; | |
| if (FLAG_compress_deopt_info) { | |
| for (intptr_t i = length - 1; i >= 0; --i) { | |
| TrieNode* node = suffix->FindChild(*instructions_[i]); | |
| if (node == nullptr) break; | |
| suffix = node; | |
| ++suffix_length; | |
| } | |
| } | |
| // Allocate space for the translation. If the shared suffix is longer | |
| // than one instruction, we replace it with a single suffix instruction. | |
| const bool use_suffix = suffix_length > 1; | |
| if (use_suffix) { | |
| length -= (suffix_length - 1); | |
| } | |
| typedef ZoneWriteStream::Raw<sizeof(intptr_t), intptr_t> Writer; | |
| ZoneWriteStream stream(zone(), 2 * length * kWordSize); | |
| Writer::Write(&stream, FrameSize()); | |
| if (use_suffix) { | |
| Writer::Write(&stream, suffix_length); | |
| Writer::Write(&stream, suffix->info_number()); | |
| } else { | |
| Writer::Write(&stream, 0); | |
| } | |
| // Write the unshared instructions and build their sub-tree. | |
| TrieNode* node = use_suffix ? suffix : trie_root_; | |
| const intptr_t write_count = use_suffix ? length - 1 : length; | |
| for (intptr_t i = write_count - 1; i >= 0; --i) { | |
| DeoptInstr* instr = instructions_[i]; | |
| Writer::Write(&stream, instr->kind()); | |
| Writer::Write(&stream, instr->source_index()); | |
| TrieNode* child = new (zone()) TrieNode(instr, current_info_number_); | |
| node->AddChild(child); | |
| node = child; | |
| } | |
| const TypedData& deopt_info = TypedData::Handle( | |
| zone(), TypedData::New(kTypedDataUint8ArrayCid, stream.bytes_written(), | |
| Heap::kOld)); | |
| { | |
| NoSafepointScope no_safepoint; | |
| memmove(deopt_info.DataAddr(0), stream.buffer(), stream.bytes_written()); | |
| } | |
| ASSERT( | |
| DeoptInfo::VerifyDecompression(instructions_, deopt_table, deopt_info)); | |
| instructions_.Clear(); | |
| materializations_.Clear(); | |
| frame_start_ = -1; | |
| ++current_info_number_; | |
| return deopt_info.ptr(); | |
| } | |
| intptr_t DeoptTable::SizeFor(intptr_t length) { | |
| return length * kEntrySize; | |
| } | |
| void DeoptTable::SetEntry(const Array& table, | |
| intptr_t index, | |
| const Smi& offset, | |
| const TypedData& info, | |
| const Smi& reason) { | |
| ASSERT((table.Length() % kEntrySize) == 0); | |
| intptr_t i = index * kEntrySize; | |
| table.SetAt(i, offset); | |
| table.SetAt(i + 1, info); | |
| table.SetAt(i + 2, reason); | |
| } | |
| intptr_t DeoptTable::GetLength(const Array& table) { | |
| ASSERT((table.Length() % kEntrySize) == 0); | |
| return table.Length() / kEntrySize; | |
| } | |
| void DeoptTable::GetEntry(const Array& table, | |
| intptr_t index, | |
| Smi* offset, | |
| TypedData* info, | |
| Smi* reason) { | |
| intptr_t i = index * kEntrySize; | |
| *offset ^= table.At(i); | |
| *info ^= table.At(i + 1); | |
| *reason ^= table.At(i + 2); | |
| } | |
| intptr_t DeoptInfo::FrameSize(const TypedData& packed) { | |
| NoSafepointScope no_safepoint; | |
| typedef ReadStream::Raw<sizeof(intptr_t), intptr_t> Reader; | |
| ReadStream read_stream(reinterpret_cast<uint8_t*>(packed.DataAddr(0)), | |
| packed.LengthInBytes()); | |
| return Reader::Read(&read_stream); | |
| } | |
| intptr_t DeoptInfo::NumMaterializations( | |
| const GrowableArray<DeoptInstr*>& unpacked) { | |
| intptr_t num = 0; | |
| while (unpacked[num]->kind() == DeoptInstr::kMaterializeObject) { | |
| num++; | |
| } | |
| return num; | |
| } | |
| void DeoptInfo::UnpackInto(const Array& table, | |
| const TypedData& packed, | |
| GrowableArray<DeoptInstr*>* unpacked, | |
| intptr_t length) { | |
| NoSafepointScope no_safepoint; | |
| typedef ReadStream::Raw<sizeof(intptr_t), intptr_t> Reader; | |
| ReadStream read_stream(reinterpret_cast<uint8_t*>(packed.DataAddr(0)), | |
| packed.LengthInBytes()); | |
| const intptr_t frame_size = Reader::Read(&read_stream); // Skip frame size. | |
| USE(frame_size); | |
| const intptr_t suffix_length = Reader::Read(&read_stream); | |
| if (suffix_length != 0) { | |
| ASSERT(suffix_length > 1); | |
| const intptr_t info_number = Reader::Read(&read_stream); | |
| TypedData& suffix = TypedData::Handle(); | |
| Smi& offset = Smi::Handle(); | |
| Smi& reason_and_flags = Smi::Handle(); | |
| DeoptTable::GetEntry(table, info_number, &offset, &suffix, | |
| &reason_and_flags); | |
| UnpackInto(table, suffix, unpacked, suffix_length); | |
| } | |
| while ((read_stream.PendingBytes() > 0) && (unpacked->length() < length)) { | |
| const intptr_t instruction = Reader::Read(&read_stream); | |
| const intptr_t from_index = Reader::Read(&read_stream); | |
| unpacked->Add(DeoptInstr::Create(instruction, from_index)); | |
| } | |
| } | |
| void DeoptInfo::Unpack(const Array& table, | |
| const TypedData& packed, | |
| GrowableArray<DeoptInstr*>* unpacked) { | |
| ASSERT(unpacked->is_empty()); | |
| // Pass kMaxInt32 as the length to unpack all instructions from the | |
| // packed stream. | |
| UnpackInto(table, packed, unpacked, kMaxInt32); | |
| unpacked->Reverse(); | |
| } | |
| const char* DeoptInfo::ToCString(const Array& deopt_table, | |
| const TypedData& packed) { | |
| #define FORMAT "[%s]" | |
| GrowableArray<DeoptInstr*> deopt_instrs; | |
| Unpack(deopt_table, packed, &deopt_instrs); | |
| // Compute the buffer size required. | |
| intptr_t len = 1; // Trailing '\0'. | |
| for (intptr_t i = 0; i < deopt_instrs.length(); i++) { | |
| len += Utils::SNPrint(nullptr, 0, FORMAT, deopt_instrs[i]->ToCString()); | |
| } | |
| // Allocate the buffer. | |
| char* buffer = Thread::Current()->zone()->Alloc<char>(len); | |
| // Layout the fields in the buffer. | |
| intptr_t index = 0; | |
| for (intptr_t i = 0; i < deopt_instrs.length(); i++) { | |
| index += Utils::SNPrint((buffer + index), (len - index), FORMAT, | |
| deopt_instrs[i]->ToCString()); | |
| } | |
| return buffer; | |
| #undef FORMAT | |
| } | |
| // Returns a bool so it can be asserted. | |
| bool DeoptInfo::VerifyDecompression(const GrowableArray<DeoptInstr*>& original, | |
| const Array& deopt_table, | |
| const TypedData& packed) { | |
| GrowableArray<DeoptInstr*> unpacked; | |
| Unpack(deopt_table, packed, &unpacked); | |
| ASSERT(unpacked.length() == original.length()); | |
| for (intptr_t i = 0; i < unpacked.length(); ++i) { | |
| ASSERT(unpacked[i]->Equals(*original[i])); | |
| } | |
| return true; | |
| } | |
| } // namespace dart | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) |