mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 01:30:32 +00:00
bf4bb95308
The new implementation moves away from desugaring of async functions on kernel AST, state machine generated in the flow graph and capturing all local variables in the context. Instead, async/await is implemented using a few stubs (InitSuspendableFunction, Suspend, Resume, Return and AsyncExceptionHandler). The stubs are implemented in a platform-independent way using (macro-)assembler helpers. When suspending a function, its frame is copied into a SuspendState object, and when resuming a function it is copied back onto the stack. No extra code is generated for accessing local variables. Callback closures are created lazily on the first await. Design doc: go/compact-async-await. Part 1 (kernel): https://dart-review.googlesource.com/c/sdk/+/241842 TEST=ci Issue: https://github.com/dart-lang/sdk/issues/48378 Change-Id: Ibad757035b7cc438ebdff80b460728b1d3eff1f5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/242000 Reviewed-by: Ryan Macnak <rmacnak@google.com> Reviewed-by: Slava Egorov <vegorov@google.com>
841 lines
28 KiB
C++
841 lines
28 KiB
C++
// 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
|