| // 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::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) { | |
| 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(), PcDescriptors::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(), PcDescriptors::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 ZoneObject { | |
| 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 |