| // 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 == NULL) { |
| // 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 NULL; |
| } |
| |
| 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 == NULL) 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; |
| 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)[token_positions->length() - 1]; |
| (*token_positions)[token_positions->length() - 1] = |
| 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; |
| LogBlock lb; |
| NoSafepointScope no_safepoint; |
| ReadStream stream(map_.Data(), map_.Length()); |
| |
| int32_t current_pc_offset = 0; |
| function_stack.Add(&root_); |
| |
| 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: { |
| 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()); |
| } |
| THR_Print("\n"); |
| current_pc_offset += arg; |
| break; |
| } |
| case CodeSourceMapOps::kPushFunction: { |
| function_stack.Add( |
| &Function::Handle(Function::RawCast(functions_.At(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(); |
| } |
| } |
| 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 |