| // 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; |
| } |
| |
| void 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. |
| 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 (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()); |
| } |
| } |
| } |
| |
| 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 ZoneAllocated { |
| 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) |