|  | // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | #include "vm/code_descriptors.h" | 
|  |  | 
|  | #include "platform/utils.h" | 
|  | #include "vm/compiler/api/deopt_id.h" | 
|  | #include "vm/flags.h" | 
|  | #include "vm/log.h" | 
|  | #include "vm/object.h" | 
|  | #include "vm/object_store.h" | 
|  | #include "vm/zone_text_buffer.h" | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | DescriptorList::DescriptorList( | 
|  | Zone* zone, | 
|  | const GrowableArray<const Function*>* inline_id_to_function) | 
|  | : function_(Function::Handle( | 
|  | zone, | 
|  | FLAG_check_token_positions && (inline_id_to_function != nullptr) | 
|  | ? inline_id_to_function->At(0)->ptr() | 
|  | : Function::null())), | 
|  | script_(Script::Handle( | 
|  | zone, | 
|  | function_.IsNull() ? Script::null() : function_.script())), | 
|  | encoded_data_(zone, kInitialStreamSize), | 
|  | prev_pc_offset(0), | 
|  | prev_deopt_id(0), | 
|  | prev_token_pos(0) {} | 
|  |  | 
|  | void DescriptorList::AddDescriptor(UntaggedPcDescriptors::Kind kind, | 
|  | intptr_t pc_offset, | 
|  | intptr_t deopt_id, | 
|  | const TokenPosition token_pos, | 
|  | intptr_t try_index, | 
|  | intptr_t yield_index) { | 
|  | // yield index 0 is reserved for normal entry. | 
|  | RELEASE_ASSERT(yield_index != 0); | 
|  |  | 
|  | ASSERT((kind == UntaggedPcDescriptors::kRuntimeCall) || | 
|  | (kind == UntaggedPcDescriptors::kBSSRelocation) || | 
|  | (kind == UntaggedPcDescriptors::kOther) || | 
|  | (yield_index != UntaggedPcDescriptors::kInvalidYieldIndex) || | 
|  | (deopt_id != DeoptId::kNone)); | 
|  |  | 
|  | // When precompiling, we only use pc descriptors for exceptions, | 
|  | // relocations and yield indices. | 
|  | if (!FLAG_precompiled_mode || try_index != -1 || | 
|  | yield_index != UntaggedPcDescriptors::kInvalidYieldIndex || | 
|  | kind == UntaggedPcDescriptors::kBSSRelocation) { | 
|  | const int32_t kind_and_metadata = | 
|  | UntaggedPcDescriptors::KindAndMetadata::Encode(kind, try_index, | 
|  | yield_index); | 
|  |  | 
|  | encoded_data_.WriteSLEB128(kind_and_metadata); | 
|  | encoded_data_.WriteSLEB128(pc_offset - prev_pc_offset); | 
|  | prev_pc_offset = pc_offset; | 
|  |  | 
|  | if (!FLAG_precompiled_mode) { | 
|  | if (FLAG_check_token_positions && token_pos.IsReal()) { | 
|  | if (!function_.IsNull() && | 
|  | !token_pos.IsWithin(function_.token_pos(), | 
|  | function_.end_token_pos())) { | 
|  | FATAL("Token position %s for PC descriptor %s at offset 0x%" Px | 
|  | " invalid for function %s (%s, %s)", | 
|  | token_pos.ToCString(), | 
|  | UntaggedPcDescriptors::KindToCString(kind), pc_offset, | 
|  | function_.ToFullyQualifiedCString(), | 
|  | function_.token_pos().ToCString(), | 
|  | function_.end_token_pos().ToCString()); | 
|  | } | 
|  | if (!script_.IsNull() && !script_.IsValidTokenPosition(token_pos)) { | 
|  | FATAL("Token position %s for PC descriptor %s at offset 0x%" Px | 
|  | " invalid for script %s of function %s", | 
|  | token_pos.ToCString(), | 
|  | UntaggedPcDescriptors::KindToCString(kind), pc_offset, | 
|  | script_.ToCString(), function_.ToFullyQualifiedCString()); | 
|  | } | 
|  | } | 
|  | const int32_t encoded_pos = token_pos.Serialize(); | 
|  | encoded_data_.WriteSLEB128(deopt_id - prev_deopt_id); | 
|  | encoded_data_.WriteSLEB128( | 
|  | Utils::SubWithWrapAround(encoded_pos, prev_token_pos)); | 
|  | prev_deopt_id = deopt_id; | 
|  | prev_token_pos = encoded_pos; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | PcDescriptorsPtr DescriptorList::FinalizePcDescriptors(uword entry_point) { | 
|  | if (encoded_data_.bytes_written() == 0) { | 
|  | return Object::empty_descriptors().ptr(); | 
|  | } | 
|  | return PcDescriptors::New(encoded_data_.buffer(), | 
|  | encoded_data_.bytes_written()); | 
|  | } | 
|  |  | 
|  | void CompressedStackMapsBuilder::AddEntry(intptr_t pc_offset, | 
|  | BitmapBuilder* bitmap, | 
|  | intptr_t spill_slot_bit_count) { | 
|  | ASSERT(bitmap != nullptr); | 
|  | ASSERT(pc_offset > last_pc_offset_); | 
|  | ASSERT(spill_slot_bit_count >= 0 && spill_slot_bit_count <= bitmap->Length()); | 
|  | const uword pc_delta = pc_offset - last_pc_offset_; | 
|  | const uword non_spill_slot_bit_count = | 
|  | bitmap->Length() - spill_slot_bit_count; | 
|  | encoded_bytes_.WriteLEB128(pc_delta); | 
|  | encoded_bytes_.WriteLEB128(spill_slot_bit_count); | 
|  | encoded_bytes_.WriteLEB128(non_spill_slot_bit_count); | 
|  | bitmap->AppendAsBytesTo(&encoded_bytes_); | 
|  | last_pc_offset_ = pc_offset; | 
|  | } | 
|  |  | 
|  | CompressedStackMapsPtr CompressedStackMapsBuilder::Finalize() const { | 
|  | if (encoded_bytes_.bytes_written() == 0) { | 
|  | return Object::empty_compressed_stackmaps().ptr(); | 
|  | } | 
|  | return CompressedStackMaps::NewInlined(encoded_bytes_.buffer(), | 
|  | encoded_bytes_.bytes_written()); | 
|  | } | 
|  |  | 
|  | ExceptionHandlersPtr ExceptionHandlerList::FinalizeExceptionHandlers( | 
|  | uword entry_point) const { | 
|  | intptr_t num_handlers = Length(); | 
|  | if (num_handlers == 0) { | 
|  | return has_async_handler_ ? Object::empty_async_exception_handlers().ptr() | 
|  | : Object::empty_exception_handlers().ptr(); | 
|  | } | 
|  | const ExceptionHandlers& handlers = | 
|  | ExceptionHandlers::Handle(ExceptionHandlers::New(num_handlers)); | 
|  | handlers.set_has_async_handler(has_async_handler_); | 
|  | for (intptr_t i = 0; i < num_handlers; i++) { | 
|  | // Assert that every element in the array has been initialized. | 
|  | if (list_[i].handler_types == nullptr) { | 
|  | // Unreachable handler, entry not computed. | 
|  | // Initialize it to some meaningful value. | 
|  | const bool has_catch_all = false; | 
|  | // Check it is uninitialized. | 
|  | ASSERT((list_[i].outer_try_index == -1) && | 
|  | (list_[i].pc_offset == ExceptionHandlers::kInvalidPcOffset)); | 
|  | handlers.SetHandlerInfo(i, list_[i].outer_try_index, list_[i].pc_offset, | 
|  | list_[i].needs_stacktrace, has_catch_all, | 
|  | list_[i].is_generated); | 
|  | handlers.SetHandledTypes(i, Array::empty_array()); | 
|  | } else { | 
|  | const bool has_catch_all = ContainsCatchAllType(*list_[i].handler_types); | 
|  | handlers.SetHandlerInfo(i, list_[i].outer_try_index, list_[i].pc_offset, | 
|  | list_[i].needs_stacktrace, has_catch_all, | 
|  | list_[i].is_generated); | 
|  | handlers.SetHandledTypes(i, *list_[i].handler_types); | 
|  | } | 
|  | } | 
|  | return handlers.ptr(); | 
|  | } | 
|  |  | 
|  | #if !defined(DART_PRECOMPILED_RUNTIME) | 
|  | class CatchEntryMovesMapBuilder::TrieNode : public ZoneAllocated { | 
|  | public: | 
|  | TrieNode() : move_(), entry_state_offset_(-1) {} | 
|  | TrieNode(CatchEntryMove move, intptr_t index) | 
|  | : move_(move), entry_state_offset_(index) {} | 
|  |  | 
|  | intptr_t Offset() { return entry_state_offset_; } | 
|  |  | 
|  | TrieNode* Insert(TrieNode* node) { | 
|  | children_.Add(node); | 
|  | return node; | 
|  | } | 
|  |  | 
|  | TrieNode* Follow(CatchEntryMove next) { | 
|  | for (intptr_t i = 0; i < children_.length(); i++) { | 
|  | if (children_[i]->move_ == next) return children_[i]; | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | private: | 
|  | CatchEntryMove move_; | 
|  | const intptr_t entry_state_offset_; | 
|  | GrowableArray<TrieNode*> children_; | 
|  | }; | 
|  |  | 
|  | CatchEntryMovesMapBuilder::CatchEntryMovesMapBuilder() | 
|  | : zone_(Thread::Current()->zone()), | 
|  | root_(new TrieNode()), | 
|  | current_pc_offset_(0), | 
|  | stream_(zone_, 64) {} | 
|  |  | 
|  | void CatchEntryMovesMapBuilder::Append(const CatchEntryMove& move) { | 
|  | moves_.Add(move); | 
|  | } | 
|  |  | 
|  | void CatchEntryMovesMapBuilder::NewMapping(intptr_t pc_offset) { | 
|  | moves_.Clear(); | 
|  | current_pc_offset_ = pc_offset; | 
|  | } | 
|  |  | 
|  | void CatchEntryMovesMapBuilder::EndMapping() { | 
|  | intptr_t suffix_length = 0; | 
|  | TrieNode* suffix = root_; | 
|  | // Find the largest common suffix, get the last node of the path. | 
|  | for (intptr_t i = moves_.length() - 1; i >= 0; i--) { | 
|  | TrieNode* n = suffix->Follow(moves_[i]); | 
|  | if (n == nullptr) break; | 
|  | suffix_length++; | 
|  | suffix = n; | 
|  | } | 
|  | intptr_t length = moves_.length() - suffix_length; | 
|  | intptr_t current_offset = stream_.bytes_written(); | 
|  |  | 
|  | typedef ZoneWriteStream::Raw<sizeof(intptr_t), intptr_t> Writer; | 
|  | Writer::Write(&stream_, current_pc_offset_); | 
|  | Writer::Write(&stream_, length); | 
|  | Writer::Write(&stream_, suffix_length); | 
|  | Writer::Write(&stream_, suffix->Offset()); | 
|  |  | 
|  | // Write the unshared part, adding it to the trie. | 
|  | TrieNode* node = suffix; | 
|  | for (intptr_t i = length - 1; i >= 0; i--) { | 
|  | moves_[i].WriteTo(&stream_); | 
|  |  | 
|  | TrieNode* child = new (zone_) TrieNode(moves_[i], current_offset); | 
|  | node->Insert(child); | 
|  | node = child; | 
|  | } | 
|  | } | 
|  |  | 
|  | TypedDataPtr CatchEntryMovesMapBuilder::FinalizeCatchEntryMovesMap() { | 
|  | TypedData& td = TypedData::Handle(TypedData::New( | 
|  | kTypedDataInt8ArrayCid, stream_.bytes_written(), Heap::kOld)); | 
|  | NoSafepointScope no_safepoint; | 
|  | uint8_t* dest = reinterpret_cast<uint8_t*>(td.DataAddr(0)); | 
|  | uint8_t* src = stream_.buffer(); | 
|  | for (intptr_t i = 0; i < stream_.bytes_written(); i++) { | 
|  | dest[i] = src[i]; | 
|  | } | 
|  | return td.ptr(); | 
|  | } | 
|  | #endif  // !defined(DART_PRECOMPILED_RUNTIME) | 
|  |  | 
|  | uint8_t CodeSourceMapOps::Read(ReadStream* stream, | 
|  | int32_t* arg1, | 
|  | int32_t* arg2) { | 
|  | ASSERT(stream != nullptr && arg1 != nullptr); | 
|  | const int32_t n = stream->Read<int32_t>(); | 
|  | const uint8_t op = OpField::decode(n); | 
|  | *arg1 = ArgField::decode(n); | 
|  | if (*arg1 > kMaxArgValue) { | 
|  | *arg1 |= kSignBits; | 
|  | } | 
|  | #if defined(DART_PRECOMPILER) | 
|  | // The special handling for non-symbolic stack trace mode only needs to | 
|  | // happen in the precompiler, because those CSMs are not serialized in | 
|  | // precompiled snapshots. | 
|  | if (op == kChangePosition && FLAG_dwarf_stack_traces_mode) { | 
|  | const int32_t m = stream->Read<int32_t>(); | 
|  | if (arg2 != nullptr) { | 
|  | *arg2 = m; | 
|  | } | 
|  | } | 
|  | #endif | 
|  | return op; | 
|  | } | 
|  |  | 
|  | void CodeSourceMapOps::Write(BaseWriteStream* stream, | 
|  | uint8_t op, | 
|  | int32_t arg1, | 
|  | int32_t arg2) { | 
|  | ASSERT(stream != nullptr); | 
|  | ASSERT(arg1 >= kMinArgValue && arg1 <= kMaxArgValue); | 
|  | if (arg1 < 0) { | 
|  | arg1 &= ~kSignBits; | 
|  | } | 
|  | const int32_t n = OpField::encode(op) | ArgField::encode(arg1); | 
|  | stream->Write(n); | 
|  | #if defined(DART_PRECOMPILER) | 
|  | if (op == kChangePosition && FLAG_dwarf_stack_traces_mode) { | 
|  | // For non-symbolic stack traces, the CodeSourceMaps are not serialized, | 
|  | // so we need not worry about increasing snapshot size by including more | 
|  | // information here. | 
|  | stream->Write(arg2); | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | const TokenPosition& CodeSourceMapBuilder::kInitialPosition = | 
|  | TokenPosition::kDartCodePrologue; | 
|  |  | 
|  | CodeSourceMapBuilder::CodeSourceMapBuilder( | 
|  | Zone* zone, | 
|  | bool stack_traces_only, | 
|  | const GrowableArray<intptr_t>& caller_inline_id, | 
|  | const GrowableArray<TokenPosition>& inline_id_to_token_pos, | 
|  | const GrowableArray<const Function*>& inline_id_to_function) | 
|  | : zone_(zone), | 
|  | buffered_pc_offset_(0), | 
|  | buffered_inline_id_stack_(), | 
|  | buffered_token_pos_stack_(), | 
|  | written_pc_offset_(0), | 
|  | written_inline_id_stack_(), | 
|  | written_token_pos_stack_(), | 
|  | caller_inline_id_(caller_inline_id), | 
|  | inline_id_to_token_pos_(inline_id_to_token_pos), | 
|  | inline_id_to_function_(inline_id_to_function), | 
|  | inlined_functions_( | 
|  | GrowableObjectArray::Handle(GrowableObjectArray::New(Heap::kOld))), | 
|  | script_(Script::Handle(zone, Script::null())), | 
|  | stream_(zone, 64), | 
|  | stack_traces_only_(stack_traces_only) { | 
|  | buffered_inline_id_stack_.Add(0); | 
|  | buffered_token_pos_stack_.Add(kInitialPosition); | 
|  | written_inline_id_stack_.Add(0); | 
|  | written_token_pos_stack_.Add(kInitialPosition); | 
|  | } | 
|  |  | 
|  | void CodeSourceMapBuilder::FlushBuffer() { | 
|  | // 1. Flush the inlining stack. | 
|  | // | 
|  | // The top-most position where the buffered and written stack match. | 
|  | intptr_t common_index; | 
|  | for (common_index = buffered_inline_id_stack_.length() - 1; common_index >= 0; | 
|  | common_index--) { | 
|  | intptr_t buffered_id = buffered_inline_id_stack_[common_index]; | 
|  | if (common_index < written_inline_id_stack_.length()) { | 
|  | intptr_t written_id = written_inline_id_stack_[common_index]; | 
|  | if (buffered_id == written_id) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | if (common_index < 0) { | 
|  | // The base, which is the root function, should _always_ match. | 
|  | UNREACHABLE(); | 
|  | } | 
|  | while (written_inline_id_stack_.length() > common_index + 1) { | 
|  | WritePop(); | 
|  | } | 
|  | for (intptr_t j = common_index + 1; j < buffered_inline_id_stack_.length(); | 
|  | j++) { | 
|  | const auto& buffered_pos = buffered_token_pos_stack_[j - 1]; | 
|  | const auto& written_pos = written_token_pos_stack_[j - 1]; | 
|  | if (buffered_pos != written_pos) { | 
|  | WriteChangePosition(buffered_pos); | 
|  | } | 
|  | WritePush(buffered_inline_id_stack_[j]); | 
|  | } | 
|  |  | 
|  | ASSERT_EQUAL(buffered_token_pos_stack_.length(), | 
|  | written_token_pos_stack_.length()); | 
|  |  | 
|  | // 2. Flush the current token position. | 
|  | intptr_t top = buffered_token_pos_stack_.length() - 1; | 
|  | const auto& buffered_pos = buffered_token_pos_stack_[top]; | 
|  | const auto& written_pos = written_token_pos_stack_[top]; | 
|  | if (buffered_pos != written_pos) { | 
|  | WriteChangePosition(buffered_pos); | 
|  | } | 
|  |  | 
|  | // 3. Flush the current PC offset. | 
|  | if (buffered_pc_offset_ != written_pc_offset_) { | 
|  | WriteAdvancePC(buffered_pc_offset_ - written_pc_offset_); | 
|  | } | 
|  | } | 
|  |  | 
|  | void CodeSourceMapBuilder::StartInliningInterval( | 
|  | int32_t pc_offset, | 
|  | const InstructionSource& source) { | 
|  | if (!source.token_pos.IsReal() && !source.token_pos.IsSynthetic()) { | 
|  | // Only record inlining intervals for token positions that might need | 
|  | // to be checked against the appropriate function and/or script. | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (buffered_inline_id_stack_.Last() == source.inlining_id) { | 
|  | // No change in function stack. | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (source.inlining_id < 0) { | 
|  | // Inlining ID is unset for this source, so assume the current inlining ID. | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!stack_traces_only_) { | 
|  | FlushBuffer(); | 
|  | } | 
|  |  | 
|  | // Find a minimal set of pops and pushes to bring us to the new function | 
|  | // stack. | 
|  |  | 
|  | // Pop to a common ancestor. | 
|  | intptr_t common_parent = source.inlining_id; | 
|  | while (!IsOnBufferedStack(common_parent)) { | 
|  | common_parent = caller_inline_id_[common_parent]; | 
|  | } | 
|  | while (buffered_inline_id_stack_.Last() != common_parent) { | 
|  | BufferPop(); | 
|  | } | 
|  |  | 
|  | // Push to the new top-of-stack function. | 
|  | GrowableArray<intptr_t> to_push; | 
|  | for (intptr_t id = source.inlining_id; id != common_parent; | 
|  | id = caller_inline_id_[id]) { | 
|  | to_push.Add(id); | 
|  | } | 
|  | for (intptr_t i = to_push.length() - 1; i >= 0; i--) { | 
|  | intptr_t callee_id = to_push[i]; | 
|  | // We should never push the root function or its "caller". | 
|  | ASSERT(callee_id > 0); | 
|  | BufferChangePosition(inline_id_to_token_pos_[callee_id - 1]); | 
|  | BufferPush(callee_id); | 
|  | } | 
|  | if (FLAG_check_token_positions) { | 
|  | // Only update the cached script_ on inlining interval changes, since it's | 
|  | // a non-trivial computation. | 
|  | script_ = inline_id_to_function_[source.inlining_id]->script(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void CodeSourceMapBuilder::WriteFunctionEntrySourcePosition( | 
|  | const InstructionSource& source) { | 
|  | ASSERT(written_pc_offset_ == 0 && buffered_pc_offset_ == 0); | 
|  | ASSERT(stream_.bytes_written() == 0); | 
|  | WriteChangePosition(source.token_pos); | 
|  | WriteAdvancePC(0); | 
|  | } | 
|  |  | 
|  | void CodeSourceMapBuilder::BeginCodeSourceRange( | 
|  | int32_t pc_offset, | 
|  | const InstructionSource& source) { | 
|  | StartInliningInterval(pc_offset, source); | 
|  | } | 
|  |  | 
|  | void CodeSourceMapBuilder::EndCodeSourceRange(int32_t pc_offset, | 
|  | const InstructionSource& source) { | 
|  | if (pc_offset == buffered_pc_offset_) { | 
|  | return;  // Empty intermediate instruction. | 
|  | } | 
|  | StartInliningInterval(pc_offset, source); | 
|  | if (source.token_pos != buffered_token_pos_stack_.Last()) { | 
|  | if (!stack_traces_only_) { | 
|  | FlushBuffer(); | 
|  | } | 
|  | BufferChangePosition(source.token_pos); | 
|  | } | 
|  | BufferAdvancePC(pc_offset - buffered_pc_offset_); | 
|  | } | 
|  |  | 
|  | void CodeSourceMapBuilder::NoteDescriptor(UntaggedPcDescriptors::Kind kind, | 
|  | int32_t pc_offset, | 
|  | const InstructionSource& source) { | 
|  | const uint8_t kCanThrow = | 
|  | UntaggedPcDescriptors::kIcCall | UntaggedPcDescriptors::kUnoptStaticCall | | 
|  | UntaggedPcDescriptors::kRuntimeCall | UntaggedPcDescriptors::kOther; | 
|  | if ((kind & kCanThrow) != 0) { | 
|  | StartInliningInterval(pc_offset, source); | 
|  | BufferChangePosition(source.token_pos); | 
|  | BufferAdvancePC(pc_offset - buffered_pc_offset_); | 
|  | FlushBuffer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void CodeSourceMapBuilder::NoteNullCheck(int32_t pc_offset, | 
|  | const InstructionSource& source, | 
|  | intptr_t name_index) { | 
|  | StartInliningInterval(pc_offset, source); | 
|  | BufferChangePosition(source.token_pos); | 
|  | BufferAdvancePC(pc_offset - buffered_pc_offset_); | 
|  | FlushBuffer(); | 
|  | WriteNullCheck(name_index); | 
|  | } | 
|  |  | 
|  | intptr_t CodeSourceMapBuilder::GetFunctionId(intptr_t inline_id) { | 
|  | const Function& function = *inline_id_to_function_[inline_id]; | 
|  | for (intptr_t i = 0; i < inlined_functions_.Length(); i++) { | 
|  | if (inlined_functions_.At(i) == function.ptr()) { | 
|  | return i; | 
|  | } | 
|  | } | 
|  | RELEASE_ASSERT(!function.IsNull()); | 
|  | inlined_functions_.Add(function, Heap::kOld); | 
|  | return inlined_functions_.Length() - 1; | 
|  | } | 
|  |  | 
|  | TokenPosition CodeSourceMapBuilder::RootPosition( | 
|  | const InstructionSource& source) { | 
|  | if (source.inlining_id <= 0) return source.token_pos; | 
|  |  | 
|  | intptr_t id = source.inlining_id; | 
|  | while (caller_inline_id_[id] != 0) { | 
|  | id = caller_inline_id_[id]; | 
|  | } | 
|  | return inline_id_to_token_pos_[id - 1]; | 
|  | } | 
|  |  | 
|  | ArrayPtr CodeSourceMapBuilder::InliningIdToFunction() { | 
|  | if (inlined_functions_.Length() == 0) { | 
|  | return Object::empty_array().ptr(); | 
|  | } | 
|  | return Array::MakeFixedLength(inlined_functions_); | 
|  | } | 
|  |  | 
|  | CodeSourceMapPtr CodeSourceMapBuilder::Finalize() { | 
|  | if (!stack_traces_only_) { | 
|  | FlushBuffer(); | 
|  | } | 
|  | intptr_t length = stream_.bytes_written(); | 
|  | const auto& map = CodeSourceMap::Handle(zone_, CodeSourceMap::New(length)); | 
|  | NoSafepointScope no_safepoint; | 
|  | if (length > 0) { | 
|  | ASSERT(stream_.buffer() != nullptr); | 
|  | memmove(map.Data(), stream_.buffer(), length); | 
|  | } | 
|  | return map.ptr(); | 
|  | } | 
|  |  | 
|  | void CodeSourceMapBuilder::BufferChangePosition(TokenPosition pos) { | 
|  | if (FLAG_check_token_positions && pos.IsReal()) { | 
|  | const intptr_t inline_id = buffered_inline_id_stack_.Last(); | 
|  | const auto& function = *inline_id_to_function_[inline_id]; | 
|  | if (function.end_token_pos().IsReal() && | 
|  | !pos.IsWithin(function.token_pos(), function.end_token_pos())) { | 
|  | TextBuffer buffer(256); | 
|  | buffer.Printf("Token position %s is invalid for function %s (%s, %s)", | 
|  | pos.ToCString(), function.ToFullyQualifiedCString(), | 
|  | function.token_pos().ToCString(), | 
|  | function.end_token_pos().ToCString()); | 
|  | if (inline_id > 0) { | 
|  | buffer.Printf(" while compiling function %s", | 
|  | inline_id_to_function_[0]->ToFullyQualifiedCString()); | 
|  | } | 
|  | FATAL("%s", buffer.buffer()); | 
|  | } | 
|  | script_ = function.script(); | 
|  | if (!script_.IsNull() && !script_.IsValidTokenPosition(pos)) { | 
|  | TextBuffer buffer(256); | 
|  | buffer.Printf("Token position %s is invalid for script %s of function %s", | 
|  | pos.ToCString(), script_.ToCString(), | 
|  | function.ToFullyQualifiedCString()); | 
|  | if (inline_id != 0) { | 
|  | buffer.Printf(" inlined into function %s", | 
|  | inline_id_to_function_[0]->ToFullyQualifiedCString()); | 
|  | } | 
|  | FATAL("%s", buffer.buffer()); | 
|  | } | 
|  | } | 
|  | buffered_token_pos_stack_.Last() = pos; | 
|  | } | 
|  |  | 
|  | void CodeSourceMapBuilder::WriteChangePosition(const TokenPosition pos) { | 
|  | const TokenPosition& last_written = written_token_pos_stack_.Last(); | 
|  | intptr_t position_or_line = | 
|  | Utils::SubWithWrapAround(pos.Serialize(), last_written.Serialize()); | 
|  | intptr_t column = TokenPosition::kNoSource.Serialize(); | 
|  | #if defined(DART_PRECOMPILER) | 
|  | if (FLAG_precompiled_mode) { | 
|  | // Don't use the raw position value directly in precompiled mode. Instead, | 
|  | // use the value of kNoSource as a fallback when no line or column | 
|  | // information is found. | 
|  | position_or_line = TokenPosition::kNoSource.Serialize(); | 
|  | const intptr_t inline_id = written_inline_id_stack_.Last(); | 
|  | ASSERT(inline_id < inline_id_to_function_.length()); | 
|  | script_ = inline_id_to_function_[inline_id]->script(); | 
|  | script_.GetTokenLocation(pos, &position_or_line, &column); | 
|  | intptr_t old_line = TokenPosition::kNoSource.Serialize(); | 
|  | script_.GetTokenLocation(last_written, &old_line); | 
|  | position_or_line = | 
|  | Utils::SubWithWrapAround<int32_t>(position_or_line, old_line); | 
|  | } | 
|  | #endif | 
|  | CodeSourceMapOps::Write(&stream_, CodeSourceMapOps::kChangePosition, | 
|  | position_or_line, column); | 
|  | written_token_pos_stack_.Last() = pos; | 
|  | } | 
|  |  | 
|  | void CodeSourceMapReader::GetInlinedFunctionsAt( | 
|  | int32_t pc_offset, | 
|  | GrowableArray<const Function*>* function_stack, | 
|  | GrowableArray<TokenPosition>* token_positions) { | 
|  | function_stack->Clear(); | 
|  | token_positions->Clear(); | 
|  |  | 
|  | NoSafepointScope no_safepoint; | 
|  | ReadStream stream(map_.Data(), map_.Length()); | 
|  |  | 
|  | int32_t current_pc_offset = 0; | 
|  | function_stack->Add(&root_); | 
|  | token_positions->Add(InitialPosition()); | 
|  |  | 
|  | while (stream.PendingBytes() > 0) { | 
|  | int32_t arg; | 
|  | const uint8_t opcode = CodeSourceMapOps::Read(&stream, &arg); | 
|  | switch (opcode) { | 
|  | case CodeSourceMapOps::kChangePosition: { | 
|  | const TokenPosition& old_token = token_positions->Last(); | 
|  | token_positions->Last() = TokenPosition::Deserialize( | 
|  | Utils::AddWithWrapAround(arg, old_token.Serialize())); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kAdvancePC: { | 
|  | current_pc_offset += arg; | 
|  | if (current_pc_offset > pc_offset) { | 
|  | return; | 
|  | } | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPushFunction: { | 
|  | function_stack->Add( | 
|  | &Function::Handle(Function::RawCast(functions_.At(arg)))); | 
|  | token_positions->Add(InitialPosition()); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPopFunction: { | 
|  | // We never pop the root function. | 
|  | ASSERT(function_stack->length() > 1); | 
|  | ASSERT(token_positions->length() > 1); | 
|  | function_stack->RemoveLast(); | 
|  | token_positions->RemoveLast(); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kNullCheck: { | 
|  | break; | 
|  | } | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void CodeSourceMapReader::PrintJSONInlineIntervals(JSONObject* jsobj) { | 
|  | { | 
|  | JSONArray inlined_functions(jsobj, "_inlinedFunctions"); | 
|  | Function& function = Function::Handle(); | 
|  | for (intptr_t i = 0; i < functions_.Length(); i++) { | 
|  | function ^= functions_.At(i); | 
|  | ASSERT(!function.IsNull()); | 
|  | inlined_functions.AddValue(function); | 
|  | } | 
|  | } | 
|  |  | 
|  | GrowableArray<intptr_t> function_stack; | 
|  | JSONArray inline_intervals(jsobj, "_inlinedIntervals"); | 
|  | NoSafepointScope no_safepoint; | 
|  | ReadStream stream(map_.Data(), map_.Length()); | 
|  |  | 
|  | int32_t current_pc_offset = 0; | 
|  | function_stack.Add(0); | 
|  |  | 
|  | while (stream.PendingBytes() > 0) { | 
|  | int32_t arg; | 
|  | const uint8_t opcode = CodeSourceMapOps::Read(&stream, &arg); | 
|  | switch (opcode) { | 
|  | case CodeSourceMapOps::kChangePosition: { | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kAdvancePC: { | 
|  | // Format: [start, end, inline functions...] | 
|  | JSONArray inline_interval(&inline_intervals); | 
|  | inline_interval.AddValue(static_cast<intptr_t>(current_pc_offset)); | 
|  | inline_interval.AddValue( | 
|  | static_cast<intptr_t>(current_pc_offset + arg - 1)); | 
|  | for (intptr_t i = 0; i < function_stack.length(); i++) { | 
|  | inline_interval.AddValue(function_stack[i]); | 
|  | } | 
|  | current_pc_offset += arg; | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPushFunction: { | 
|  | function_stack.Add(arg); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPopFunction: { | 
|  | // We never pop the root function. | 
|  | ASSERT(function_stack.length() > 1); | 
|  | function_stack.RemoveLast(); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kNullCheck: { | 
|  | break; | 
|  | } | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | } | 
|  | } | 
|  | #endif  // !PRODUCT | 
|  |  | 
|  | void CodeSourceMapReader::DumpInlineIntervals(uword start) { | 
|  | GrowableArray<const Function*> function_stack; | 
|  | GrowableArray<TokenPosition> token_positions; | 
|  | LogBlock lb; | 
|  | NoSafepointScope no_safepoint; | 
|  | ReadStream stream(map_.Data(), map_.Length()); | 
|  |  | 
|  | int32_t current_pc_offset = 0; | 
|  | function_stack.Add(&root_); | 
|  | token_positions.Add(InitialPosition()); | 
|  |  | 
|  | THR_Print("Inline intervals for function '%s' {\n", | 
|  | root_.ToFullyQualifiedCString()); | 
|  | while (stream.PendingBytes() > 0) { | 
|  | int32_t arg; | 
|  | const uint8_t opcode = CodeSourceMapOps::Read(&stream, &arg); | 
|  | switch (opcode) { | 
|  | case CodeSourceMapOps::kChangePosition: { | 
|  | const TokenPosition& old_token = token_positions.Last(); | 
|  | token_positions.Last() = TokenPosition::Deserialize( | 
|  | Utils::AddWithWrapAround(arg, old_token.Serialize())); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kAdvancePC: { | 
|  | THR_Print("%" Px "-%" Px ": ", start + current_pc_offset, | 
|  | start + current_pc_offset + arg - 1); | 
|  | for (intptr_t i = 0; i < function_stack.length(); i++) { | 
|  | THR_Print("%s", function_stack[i]->ToCString()); | 
|  | if (token_positions[i].IsReal()) { | 
|  | THR_Print(" @%" Pd, token_positions[i].Pos()); | 
|  | } | 
|  | } | 
|  | THR_Print("\n"); | 
|  | current_pc_offset += arg; | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPushFunction: { | 
|  | function_stack.Add( | 
|  | &Function::Handle(Function::RawCast(functions_.At(arg)))); | 
|  | token_positions.Add(InitialPosition()); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPopFunction: { | 
|  | // We never pop the root function. | 
|  | ASSERT(function_stack.length() > 1); | 
|  | ASSERT(token_positions.length() > 1); | 
|  | function_stack.RemoveLast(); | 
|  | token_positions.RemoveLast(); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kNullCheck: { | 
|  | break; | 
|  | } | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | } | 
|  | THR_Print("}\n"); | 
|  | } | 
|  |  | 
|  | void CodeSourceMapReader::DumpSourcePositions(uword start) { | 
|  | GrowableArray<const Function*> function_stack; | 
|  | GrowableArray<TokenPosition> token_positions; | 
|  | LogBlock lb; | 
|  | NoSafepointScope no_safepoint; | 
|  | ReadStream stream(map_.Data(), map_.Length()); | 
|  |  | 
|  | int32_t current_pc_offset = 0; | 
|  | function_stack.Add(&root_); | 
|  | token_positions.Add(InitialPosition()); | 
|  |  | 
|  | THR_Print("Source positions for function '%s' {\n", | 
|  | root_.ToFullyQualifiedCString()); | 
|  | while (stream.PendingBytes() > 0) { | 
|  | int32_t arg; | 
|  | const uint8_t opcode = CodeSourceMapOps::Read(&stream, &arg); | 
|  | switch (opcode) { | 
|  | case CodeSourceMapOps::kChangePosition: { | 
|  | const TokenPosition& old_token = | 
|  | token_positions[token_positions.length() - 1]; | 
|  | token_positions[token_positions.length() - 1] = | 
|  | TokenPosition::Deserialize( | 
|  | Utils::AddWithWrapAround(arg, old_token.Serialize())); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kAdvancePC: { | 
|  | THR_Print("%" Px "-%" Px ": ", start + current_pc_offset, | 
|  | start + current_pc_offset + arg - 1); | 
|  | for (intptr_t i = 0; i < function_stack.length(); i++) { | 
|  | THR_Print("%s@%s", function_stack[i]->ToCString(), | 
|  | token_positions[i].ToCString()); | 
|  | } | 
|  | THR_Print("\n"); | 
|  | current_pc_offset += arg; | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPushFunction: { | 
|  | function_stack.Add( | 
|  | &Function::Handle(Function::RawCast(functions_.At(arg)))); | 
|  | token_positions.Add(InitialPosition()); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPopFunction: { | 
|  | // We never pop the root function. | 
|  | ASSERT(function_stack.length() > 1); | 
|  | ASSERT(token_positions.length() > 1); | 
|  | function_stack.RemoveLast(); | 
|  | token_positions.RemoveLast(); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kNullCheck: { | 
|  | THR_Print("%" Px "-%" Px ": null check PP#%" Pd32 "\n", | 
|  | start + current_pc_offset, start + current_pc_offset, arg); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | } | 
|  | THR_Print("}\n"); | 
|  | } | 
|  |  | 
|  | intptr_t CodeSourceMapReader::GetNullCheckNameIndexAt(int32_t pc_offset) { | 
|  | NoSafepointScope no_safepoint; | 
|  | ReadStream stream(map_.Data(), map_.Length()); | 
|  |  | 
|  | int32_t current_pc_offset = 0; | 
|  |  | 
|  | while (stream.PendingBytes() > 0) { | 
|  | int32_t arg; | 
|  | const uint8_t opcode = CodeSourceMapOps::Read(&stream, &arg); | 
|  | switch (opcode) { | 
|  | case CodeSourceMapOps::kChangePosition: { | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kAdvancePC: { | 
|  | current_pc_offset += arg; | 
|  | RELEASE_ASSERT(current_pc_offset <= pc_offset); | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPushFunction: { | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kPopFunction: { | 
|  | break; | 
|  | } | 
|  | case CodeSourceMapOps::kNullCheck: { | 
|  | if (current_pc_offset == pc_offset) { | 
|  | return arg; | 
|  | } | 
|  | break; | 
|  | } | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | } | 
|  |  | 
|  | UNREACHABLE(); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | }  // namespace dart |