mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:09:48 +00:00
2707880f1b
Bug: https://github.com/dart-lang/sdk/issues/39427 Change-Id: I74e0eee623d88005fb2893d03e284a87daa09260 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/146696 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
874 lines
32 KiB
C++
874 lines
32 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/stack_frame.h"
|
|
|
|
#include "platform/memory_sanitizer.h"
|
|
#include "vm/code_descriptors.h"
|
|
#include "vm/compiler/runtime_api.h"
|
|
#include "vm/heap/become.h"
|
|
#include "vm/isolate.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/os.h"
|
|
#include "vm/parser.h"
|
|
#include "vm/raw_object.h"
|
|
#include "vm/reusable_handles.h"
|
|
#include "vm/reverse_pc_lookup_cache.h"
|
|
#include "vm/scopes.h"
|
|
#include "vm/stub_code.h"
|
|
#include "vm/visitor.h"
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
#include "vm/deopt_instructions.h"
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
namespace dart {
|
|
|
|
DECLARE_FLAG(bool, enable_interpreter);
|
|
|
|
const FrameLayout invalid_frame_layout = {
|
|
/*.first_object_from_fp = */ -1,
|
|
/*.last_fixed_object_from_fp = */ -1,
|
|
/*.param_end_from_fp = */ -1,
|
|
/*.last_param_from_entry_sp = */ -1,
|
|
/*.first_local_from_fp = */ -1,
|
|
/*.dart_fixed_frame_size = */ -1,
|
|
/*.saved_caller_pp_from_fp = */ -1,
|
|
/*.code_from_fp = */ -1,
|
|
/*.exit_link_slot_from_entry_fp = */ -1,
|
|
};
|
|
|
|
const FrameLayout default_frame_layout = {
|
|
/*.first_object_from_fp = */ kFirstObjectSlotFromFp,
|
|
/*.last_fixed_object_from_fp = */ kLastFixedObjectSlotFromFp,
|
|
/*.param_end_from_fp = */ kParamEndSlotFromFp,
|
|
/*.last_param_from_entry_sp = */ kLastParamSlotFromEntrySp,
|
|
/*.first_local_from_fp = */ kFirstLocalSlotFromFp,
|
|
/*.dart_fixed_frame_size = */ kDartFrameFixedSize,
|
|
/*.saved_caller_pp_from_fp = */ kSavedCallerPpSlotFromFp,
|
|
/*.code_from_fp = */ kPcMarkerSlotFromFp,
|
|
/*.exit_link_slot_from_entry_fp = */ kExitLinkSlotFromEntryFp,
|
|
};
|
|
const FrameLayout bare_instructions_frame_layout = {
|
|
/*.first_object_from_pc =*/kFirstObjectSlotFromFp, // No saved PP slot.
|
|
/*.last_fixed_object_from_fp = */ kLastFixedObjectSlotFromFp +
|
|
2, // No saved CODE, PP slots
|
|
/*.param_end_from_fp = */ kParamEndSlotFromFp,
|
|
/*.last_param_from_entry_sp = */ kLastParamSlotFromEntrySp,
|
|
/*.first_local_from_fp =*/kFirstLocalSlotFromFp +
|
|
2, // No saved CODE, PP slots.
|
|
/*.dart_fixed_frame_size =*/kDartFrameFixedSize -
|
|
2, // No saved CODE, PP slots.
|
|
/*.saved_caller_pp_from_fp = */ 0, // No saved PP slot.
|
|
/*.code_from_fp = */ 0, // No saved CODE
|
|
/*.exit_link_slot_from_entry_fp = */ kExitLinkSlotFromEntryFp,
|
|
};
|
|
|
|
namespace compiler {
|
|
|
|
namespace target {
|
|
FrameLayout frame_layout = invalid_frame_layout;
|
|
}
|
|
|
|
} // namespace compiler
|
|
|
|
FrameLayout runtime_frame_layout = invalid_frame_layout;
|
|
|
|
int FrameLayout::FrameSlotForVariable(const LocalVariable* variable) const {
|
|
ASSERT(!variable->is_captured());
|
|
return this->FrameSlotForVariableIndex(variable->index().value());
|
|
}
|
|
|
|
int FrameLayout::FrameSlotForVariableIndex(int variable_index) const {
|
|
// Variable indices are:
|
|
// [1, 2, ..., M] for the M parameters.
|
|
// [0, -1, -2, ... -(N-1)] for the N [LocalVariable]s
|
|
// See (runtime/vm/scopes.h)
|
|
return variable_index <= 0 ? (variable_index + first_local_from_fp)
|
|
: (variable_index + param_end_from_fp);
|
|
}
|
|
|
|
void FrameLayout::Init() {
|
|
// By default we use frames with CODE_REG/PP in the frame.
|
|
compiler::target::frame_layout = default_frame_layout;
|
|
runtime_frame_layout = default_frame_layout;
|
|
|
|
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
|
|
compiler::target::frame_layout = bare_instructions_frame_layout;
|
|
}
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
|
|
compiler::target::frame_layout = invalid_frame_layout;
|
|
runtime_frame_layout = bare_instructions_frame_layout;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
IsolateGroup* StackFrame::IsolateGroupOfBareInstructionsFrame() const {
|
|
IsolateGroup* isolate_group = Dart::vm_isolate()->group();
|
|
if (isolate_group->object_store()->code_order_table() != Object::null()) {
|
|
auto rct = isolate_group->reverse_pc_lookup_cache();
|
|
if (rct->Contains(pc())) return isolate_group;
|
|
}
|
|
|
|
isolate_group = this->isolate_group();
|
|
auto* object_store = isolate_group->object_store();
|
|
if (object_store != nullptr) {
|
|
if (object_store->code_order_table() != Object::null()) {
|
|
auto rct = isolate_group->reverse_pc_lookup_cache();
|
|
if (rct->Contains(pc())) return isolate_group;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool StackFrame::IsBareInstructionsDartFrame() const {
|
|
NoSafepointScope no_safepoint;
|
|
|
|
if (auto isolate_group = IsolateGroupOfBareInstructionsFrame()) {
|
|
Code code;
|
|
auto rct = isolate_group->reverse_pc_lookup_cache();
|
|
code = rct->Lookup(pc(), /*is_return_address=*/true);
|
|
|
|
auto const cid = code.OwnerClassId();
|
|
ASSERT(cid == kNullCid || cid == kClassCid || cid == kFunctionCid);
|
|
return cid == kFunctionCid;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool StackFrame::IsBareInstructionsStubFrame() const {
|
|
NoSafepointScope no_safepoint;
|
|
|
|
if (auto isolate_group = IsolateGroupOfBareInstructionsFrame()) {
|
|
Code code;
|
|
auto rct = isolate_group->reverse_pc_lookup_cache();
|
|
code = rct->Lookup(pc(), /*is_return_address=*/true);
|
|
|
|
auto const cid = code.OwnerClassId();
|
|
ASSERT(cid == kNullCid || cid == kClassCid || cid == kFunctionCid);
|
|
return cid == kNullCid || cid == kClassCid;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool StackFrame::IsStubFrame() const {
|
|
if (is_interpreted()) {
|
|
return false;
|
|
}
|
|
|
|
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
|
|
return IsBareInstructionsStubFrame();
|
|
}
|
|
|
|
ASSERT(!(IsEntryFrame() || IsExitFrame()));
|
|
#if !defined(HOST_OS_WINDOWS) && !defined(HOST_OS_FUCHSIA)
|
|
// On Windows and Fuchsia, the profiler calls this from a separate thread
|
|
// where Thread::Current() is NULL, so we cannot create a NoSafepointScope.
|
|
NoSafepointScope no_safepoint;
|
|
#endif
|
|
|
|
CodePtr code = GetCodeObject();
|
|
ASSERT(code != Object::null());
|
|
auto const cid = Code::OwnerClassIdOf(code);
|
|
ASSERT(cid == kNullCid || cid == kClassCid || cid == kFunctionCid);
|
|
return cid == kNullCid || cid == kClassCid;
|
|
}
|
|
|
|
const char* StackFrame::ToCString() const {
|
|
ASSERT(thread_ == Thread::Current());
|
|
Zone* zone = Thread::Current()->zone();
|
|
if (IsDartFrame()) {
|
|
if (is_interpreted()) {
|
|
const Bytecode& bytecode = Bytecode::Handle(zone, LookupDartBytecode());
|
|
ASSERT(!bytecode.IsNull());
|
|
return zone->PrintToString("[%-8s : sp(%#" Px ") fp(%#" Px ") pc(%#" Px
|
|
" offset:0x%" Px ") %s ]",
|
|
GetName(), sp(), fp(), pc(),
|
|
pc() - bytecode.PayloadStart(),
|
|
bytecode.FullyQualifiedName());
|
|
}
|
|
const Code& code = Code::Handle(zone, LookupDartCode());
|
|
ASSERT(!code.IsNull());
|
|
const auto& owner = Object::Handle(
|
|
zone, WeakSerializationReference::UnwrapIfTarget(code.owner()));
|
|
ASSERT(!owner.IsNull());
|
|
auto const opt = code.IsFunctionCode() && code.is_optimized() ? "*" : "";
|
|
auto const owner_name =
|
|
owner.IsFunction() ? Function::Cast(owner).ToFullyQualifiedCString()
|
|
: owner.ToCString();
|
|
return zone->PrintToString("[%-8s : sp(%#" Px ") fp(%#" Px ") pc(%#" Px
|
|
") %s%s ]",
|
|
GetName(), sp(), fp(), pc(), opt, owner_name);
|
|
} else {
|
|
return zone->PrintToString("[%-8s : sp(%#" Px ") fp(%#" Px ") pc(%#" Px
|
|
")]",
|
|
GetName(), sp(), fp(), pc());
|
|
}
|
|
}
|
|
|
|
void ExitFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) {
|
|
ASSERT(visitor != NULL);
|
|
// Visit pc marker and saved pool pointer, or, for interpreted frame, code
|
|
// object and function object.
|
|
ObjectPtr* last_fixed =
|
|
reinterpret_cast<ObjectPtr*>(fp()) +
|
|
(is_interpreted() ? kKBCLastFixedObjectSlotFromFp
|
|
: runtime_frame_layout.first_object_from_fp);
|
|
ObjectPtr* first_fixed =
|
|
reinterpret_cast<ObjectPtr*>(fp()) +
|
|
(is_interpreted() ? kKBCFirstObjectSlotFromFp
|
|
: runtime_frame_layout.last_fixed_object_from_fp);
|
|
if (first_fixed <= last_fixed) {
|
|
visitor->VisitPointers(first_fixed, last_fixed);
|
|
} else {
|
|
ASSERT(runtime_frame_layout.first_object_from_fp ==
|
|
runtime_frame_layout.first_local_from_fp);
|
|
}
|
|
}
|
|
|
|
void EntryFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) {
|
|
ASSERT(visitor != NULL);
|
|
// Visit objects between SP and (FP - callee_save_area).
|
|
ObjectPtr* first = is_interpreted() ? reinterpret_cast<ObjectPtr*>(fp()) +
|
|
kKBCSavedArgDescSlotFromEntryFp
|
|
: reinterpret_cast<ObjectPtr*>(sp());
|
|
ObjectPtr* last = is_interpreted() ? reinterpret_cast<ObjectPtr*>(sp())
|
|
: reinterpret_cast<ObjectPtr*>(fp()) +
|
|
kExitLinkSlotFromEntryFp - 1;
|
|
// There may not be any pointer to visit; in this case, first > last.
|
|
visitor->VisitPointers(first, last);
|
|
}
|
|
|
|
void StackFrame::VisitObjectPointers(ObjectPointerVisitor* visitor) {
|
|
ASSERT(visitor != NULL);
|
|
// NOTE: This code runs while GC is in progress and runs within
|
|
// a NoHandleScope block. Hence it is not ok to use regular Zone or
|
|
// Scope handles. We use direct stack handles, the raw pointers in
|
|
// these handles are not traversed. The use of handles is mainly to
|
|
// be able to reuse the handle based code and avoid having to add
|
|
// helper functions to the raw object interface.
|
|
NoSafepointScope no_safepoint;
|
|
Code code;
|
|
|
|
if (auto isolate_group = IsolateGroupOfBareInstructionsFrame()) {
|
|
auto const rct = isolate_group->reverse_pc_lookup_cache();
|
|
code = rct->Lookup(pc(), /*is_return_address=*/true);
|
|
} else {
|
|
ObjectPtr pc_marker = *(reinterpret_cast<ObjectPtr*>(
|
|
fp() + ((is_interpreted() ? kKBCPcMarkerSlotFromFp
|
|
: runtime_frame_layout.code_from_fp) *
|
|
kWordSize)));
|
|
// May forward raw code. Note we don't just visit the pc marker slot first
|
|
// because the visitor's forwarding might not be idempotent.
|
|
visitor->VisitPointer(&pc_marker);
|
|
if (pc_marker->IsHeapObject() && (pc_marker->GetClassId() == kCodeCid)) {
|
|
code ^= pc_marker;
|
|
} else {
|
|
ASSERT(pc_marker == Object::null() ||
|
|
(is_interpreted() && (!pc_marker->IsHeapObject() ||
|
|
(pc_marker->GetClassId() == kBytecodeCid))));
|
|
}
|
|
}
|
|
|
|
if (!code.IsNull()) {
|
|
// Optimized frames have a stack map. We need to visit the frame based
|
|
// on the stack map.
|
|
CompressedStackMaps maps;
|
|
maps = code.compressed_stackmaps();
|
|
CompressedStackMaps global_table;
|
|
|
|
// The GC does not have an active isolate, only an active isolate group,
|
|
// yet the global compressed stack map table is only stored in the object
|
|
// store. It has the same contents for all isolates, so we just pick the
|
|
// one from the first isolate here.
|
|
// TODO(dartbug.com/36097): Avoid having this per-isolate and instead store
|
|
// it per isolate group.
|
|
auto isolate = isolate_group()->isolates_.First();
|
|
|
|
global_table = isolate->object_store()->canonicalized_stack_map_entries();
|
|
CompressedStackMapsIterator it(maps, global_table);
|
|
const uword start = code.PayloadStart();
|
|
const uint32_t pc_offset = pc() - start;
|
|
if (it.Find(pc_offset)) {
|
|
if (is_interpreted()) {
|
|
UNIMPLEMENTED();
|
|
}
|
|
ObjectPtr* first = reinterpret_cast<ObjectPtr*>(sp());
|
|
ObjectPtr* last = reinterpret_cast<ObjectPtr*>(
|
|
fp() + (runtime_frame_layout.first_local_from_fp * kWordSize));
|
|
|
|
// A stack map is present in the code object, use the stack map to
|
|
// visit frame slots which are marked as having objects.
|
|
//
|
|
// The layout of the frame is (lower addresses to the right):
|
|
// | spill slots | outgoing arguments | saved registers | slow-path args |
|
|
// |XXXXXXXXXXXXX|--------------------|XXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXX|
|
|
//
|
|
// The spill slots and any saved registers are described in the stack
|
|
// map. The outgoing arguments are assumed to be tagged; the number
|
|
// of outgoing arguments is not explicitly tracked.
|
|
|
|
// Spill slots are at the 'bottom' of the frame.
|
|
intptr_t spill_slot_count = it.SpillSlotBitCount();
|
|
for (intptr_t bit = 0; bit < spill_slot_count; ++bit) {
|
|
if (it.IsObject(bit)) {
|
|
visitor->VisitPointer(last);
|
|
}
|
|
--last;
|
|
}
|
|
|
|
// The live registers at the 'top' of the frame comprise the rest of the
|
|
// stack map.
|
|
for (intptr_t bit = it.Length() - 1; bit >= spill_slot_count; --bit) {
|
|
if (it.IsObject(bit)) {
|
|
visitor->VisitPointer(first);
|
|
}
|
|
++first;
|
|
}
|
|
|
|
// The last slot can be one slot (but not more) past the last slot
|
|
// in the case that all slots were covered by the stack map.
|
|
ASSERT((last + 1) >= first);
|
|
visitor->VisitPointers(first, last);
|
|
|
|
// Now visit other slots which might be part of the calling convention.
|
|
first = reinterpret_cast<ObjectPtr*>(
|
|
fp() + ((runtime_frame_layout.first_local_from_fp + 1) * kWordSize));
|
|
last = reinterpret_cast<ObjectPtr*>(
|
|
fp() + (runtime_frame_layout.first_object_from_fp * kWordSize));
|
|
visitor->VisitPointers(first, last);
|
|
return;
|
|
}
|
|
|
|
// If we are missing a stack map for a given PC offset, this must either be
|
|
// unoptimized code, code with no stack map information at all, or the entry
|
|
// to an osr function. In each of these cases, all stack slots contain
|
|
// tagged pointers, so fall through.
|
|
ASSERT(!code.is_optimized() || maps.IsNull() ||
|
|
(pc_offset == code.EntryPoint() - code.PayloadStart()));
|
|
}
|
|
|
|
// For normal unoptimized Dart frames and Stub frames each slot
|
|
// between the first and last included are tagged objects.
|
|
if (is_interpreted()) {
|
|
// Do not visit caller's pc or caller's fp.
|
|
ObjectPtr* first =
|
|
reinterpret_cast<ObjectPtr*>(fp()) + kKBCFirstObjectSlotFromFp;
|
|
ObjectPtr* last =
|
|
reinterpret_cast<ObjectPtr*>(fp()) + kKBCLastFixedObjectSlotFromFp;
|
|
|
|
visitor->VisitPointers(first, last);
|
|
}
|
|
ObjectPtr* first =
|
|
reinterpret_cast<ObjectPtr*>(is_interpreted() ? fp() : sp());
|
|
ObjectPtr* last = reinterpret_cast<ObjectPtr*>(
|
|
is_interpreted()
|
|
? sp()
|
|
: fp() + (runtime_frame_layout.first_object_from_fp * kWordSize));
|
|
|
|
visitor->VisitPointers(first, last);
|
|
}
|
|
|
|
FunctionPtr StackFrame::LookupDartFunction() const {
|
|
if (is_interpreted()) {
|
|
ObjectPtr result = *(reinterpret_cast<FunctionPtr*>(
|
|
fp() + kKBCFunctionSlotFromFp * kWordSize));
|
|
ASSERT((result == Object::null()) ||
|
|
(result->GetClassId() == kFunctionCid));
|
|
return static_cast<FunctionPtr>(result);
|
|
}
|
|
const Code& code = Code::Handle(LookupDartCode());
|
|
if (!code.IsNull()) {
|
|
return code.function();
|
|
}
|
|
return Function::null();
|
|
}
|
|
|
|
CodePtr StackFrame::LookupDartCode() const {
|
|
// We add a no gc scope to ensure that the code below does not trigger
|
|
// a GC as we are handling raw object references here. It is possible
|
|
// that the code is called while a GC is in progress, that is ok.
|
|
#if !defined(HOST_OS_WINDOWS) && !defined(HOST_OS_FUCHSIA)
|
|
// On Windows and Fuchsia, the profiler calls this from a separate thread
|
|
// where Thread::Current() is NULL, so we cannot create a NoSafepointScope.
|
|
NoSafepointScope no_safepoint;
|
|
#endif
|
|
if (auto isolate_group = IsolateGroupOfBareInstructionsFrame()) {
|
|
auto const rct = isolate_group->reverse_pc_lookup_cache();
|
|
return rct->Lookup(pc(), /*is_return_address=*/true);
|
|
}
|
|
|
|
CodePtr code = GetCodeObject();
|
|
if ((code != Code::null()) && Code::OwnerClassIdOf(code) == kFunctionCid) {
|
|
return code;
|
|
}
|
|
return Code::null();
|
|
}
|
|
|
|
CodePtr StackFrame::GetCodeObject() const {
|
|
ASSERT(!is_interpreted());
|
|
if (auto isolate_group = IsolateGroupOfBareInstructionsFrame()) {
|
|
auto const rct = isolate_group->reverse_pc_lookup_cache();
|
|
return rct->Lookup(pc(), /*is_return_address=*/true);
|
|
} else {
|
|
ObjectPtr pc_marker = *(reinterpret_cast<ObjectPtr*>(
|
|
fp() + runtime_frame_layout.code_from_fp * kWordSize));
|
|
ASSERT((pc_marker == Object::null()) ||
|
|
(pc_marker->GetClassId() == kCodeCid));
|
|
return static_cast<CodePtr>(pc_marker);
|
|
}
|
|
}
|
|
|
|
BytecodePtr StackFrame::LookupDartBytecode() const {
|
|
// We add a no gc scope to ensure that the code below does not trigger
|
|
// a GC as we are handling raw object references here. It is possible
|
|
// that the code is called while a GC is in progress, that is ok.
|
|
#if !defined(HOST_OS_WINDOWS) && !defined(HOST_OS_FUCHSIA)
|
|
// On Windows and Fuchsia, the profiler calls this from a separate thread
|
|
// where Thread::Current() is NULL, so we cannot create a NoSafepointScope.
|
|
NoSafepointScope no_safepoint;
|
|
#endif
|
|
return GetBytecodeObject();
|
|
}
|
|
|
|
BytecodePtr StackFrame::GetBytecodeObject() const {
|
|
ASSERT(is_interpreted());
|
|
ObjectPtr pc_marker = *(
|
|
reinterpret_cast<ObjectPtr*>(fp() + kKBCPcMarkerSlotFromFp * kWordSize));
|
|
ASSERT((pc_marker == Object::null()) ||
|
|
(pc_marker->GetClassId() == kBytecodeCid));
|
|
return static_cast<BytecodePtr>(pc_marker);
|
|
}
|
|
|
|
bool StackFrame::FindExceptionHandler(Thread* thread,
|
|
uword* handler_pc,
|
|
bool* needs_stacktrace,
|
|
bool* has_catch_all,
|
|
bool* is_optimized) const {
|
|
REUSABLE_CODE_HANDLESCOPE(thread);
|
|
Code& code = reused_code_handle.Handle();
|
|
REUSABLE_BYTECODE_HANDLESCOPE(thread);
|
|
Bytecode& bytecode = reused_bytecode_handle.Handle();
|
|
REUSABLE_EXCEPTION_HANDLERS_HANDLESCOPE(thread);
|
|
ExceptionHandlers& handlers = reused_exception_handlers_handle.Handle();
|
|
REUSABLE_PC_DESCRIPTORS_HANDLESCOPE(thread);
|
|
PcDescriptors& descriptors = reused_pc_descriptors_handle.Handle();
|
|
uword start;
|
|
if (is_interpreted()) {
|
|
bytecode = LookupDartBytecode();
|
|
ASSERT(!bytecode.IsNull());
|
|
start = bytecode.PayloadStart();
|
|
handlers = bytecode.exception_handlers();
|
|
} else {
|
|
code = LookupDartCode();
|
|
if (code.IsNull()) {
|
|
return false; // Stub frames do not have exception handlers.
|
|
}
|
|
start = code.PayloadStart();
|
|
handlers = code.exception_handlers();
|
|
descriptors = code.pc_descriptors();
|
|
*is_optimized = code.is_optimized();
|
|
}
|
|
HandlerInfoCache* cache = thread->isolate()->handler_info_cache();
|
|
ExceptionHandlerInfo* info = cache->Lookup(pc());
|
|
if (info != NULL) {
|
|
*handler_pc = start + info->handler_pc_offset;
|
|
*needs_stacktrace = (info->needs_stacktrace != 0);
|
|
*has_catch_all = (info->has_catch_all != 0);
|
|
return true;
|
|
}
|
|
|
|
if (handlers.num_entries() == 0) {
|
|
return false;
|
|
}
|
|
|
|
intptr_t try_index = -1;
|
|
if (is_interpreted()) {
|
|
try_index = bytecode.GetTryIndexAtPc(pc());
|
|
} else {
|
|
uword pc_offset = pc() - code.PayloadStart();
|
|
PcDescriptors::Iterator iter(descriptors, PcDescriptorsLayout::kAnyKind);
|
|
while (iter.MoveNext()) {
|
|
const intptr_t current_try_index = iter.TryIndex();
|
|
if ((iter.PcOffset() == pc_offset) && (current_try_index != -1)) {
|
|
try_index = current_try_index;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (try_index == -1) {
|
|
return false;
|
|
}
|
|
ExceptionHandlerInfo handler_info;
|
|
handlers.GetHandlerInfo(try_index, &handler_info);
|
|
*handler_pc = start + handler_info.handler_pc_offset;
|
|
*needs_stacktrace = (handler_info.needs_stacktrace != 0);
|
|
*has_catch_all = (handler_info.has_catch_all != 0);
|
|
cache->Insert(pc(), handler_info);
|
|
return true;
|
|
}
|
|
|
|
TokenPosition StackFrame::GetTokenPos() const {
|
|
if (is_interpreted()) {
|
|
const Bytecode& bytecode = Bytecode::Handle(LookupDartBytecode());
|
|
if (bytecode.IsNull()) {
|
|
return TokenPosition::kNoSource; // Stub frames do not have token_pos.
|
|
}
|
|
return bytecode.GetTokenIndexOfPC(pc());
|
|
}
|
|
const Code& code = Code::Handle(LookupDartCode());
|
|
if (code.IsNull()) {
|
|
return TokenPosition::kNoSource; // Stub frames do not have token_pos.
|
|
}
|
|
uword pc_offset = pc() - code.PayloadStart();
|
|
const PcDescriptors& descriptors =
|
|
PcDescriptors::Handle(code.pc_descriptors());
|
|
ASSERT(!descriptors.IsNull());
|
|
PcDescriptors::Iterator iter(descriptors, PcDescriptorsLayout::kAnyKind);
|
|
while (iter.MoveNext()) {
|
|
if (iter.PcOffset() == pc_offset) {
|
|
return TokenPosition(iter.TokenPos());
|
|
}
|
|
}
|
|
return TokenPosition::kNoSource;
|
|
}
|
|
|
|
bool StackFrame::IsValid() const {
|
|
if (IsEntryFrame() || IsExitFrame() || IsStubFrame()) {
|
|
return true;
|
|
}
|
|
if (is_interpreted()) {
|
|
return (LookupDartBytecode() != Bytecode::null());
|
|
}
|
|
return (LookupDartCode() != Code::null());
|
|
}
|
|
|
|
void StackFrame::DumpCurrentTrace() {
|
|
StackFrameIterator frames(ValidationPolicy::kDontValidateFrames,
|
|
Thread::Current(),
|
|
StackFrameIterator::kNoCrossThreadIteration);
|
|
StackFrame* frame = frames.NextFrame();
|
|
while (frame != nullptr) {
|
|
OS::PrintErr("%s\n", frame->ToCString());
|
|
frame = frames.NextFrame();
|
|
}
|
|
}
|
|
|
|
void StackFrameIterator::SetupLastExitFrameData() {
|
|
ASSERT(thread_ != NULL);
|
|
uword exit_marker = thread_->top_exit_frame_info();
|
|
frames_.fp_ = exit_marker;
|
|
frames_.sp_ = 0;
|
|
frames_.pc_ = 0;
|
|
if (FLAG_enable_interpreter) {
|
|
frames_.CheckIfInterpreted(exit_marker);
|
|
}
|
|
frames_.Unpoison();
|
|
}
|
|
|
|
void StackFrameIterator::SetupNextExitFrameData() {
|
|
ASSERT(entry_.fp() != 0);
|
|
uword exit_address =
|
|
entry_.fp() + ((entry_.is_interpreted() ? kKBCExitLinkSlotFromEntryFp
|
|
: kExitLinkSlotFromEntryFp) *
|
|
kWordSize);
|
|
uword exit_marker = *reinterpret_cast<uword*>(exit_address);
|
|
frames_.fp_ = exit_marker;
|
|
frames_.sp_ = 0;
|
|
frames_.pc_ = 0;
|
|
if (FLAG_enable_interpreter) {
|
|
frames_.CheckIfInterpreted(exit_marker);
|
|
}
|
|
frames_.Unpoison();
|
|
}
|
|
|
|
StackFrameIterator::StackFrameIterator(ValidationPolicy validation_policy,
|
|
Thread* thread,
|
|
CrossThreadPolicy cross_thread_policy)
|
|
: validate_(validation_policy == ValidationPolicy::kValidateFrames),
|
|
entry_(thread),
|
|
exit_(thread),
|
|
frames_(thread),
|
|
current_frame_(NULL),
|
|
thread_(thread) {
|
|
ASSERT(cross_thread_policy == kAllowCrossThreadIteration ||
|
|
thread_ == Thread::Current());
|
|
SetupLastExitFrameData(); // Setup data for last exit frame.
|
|
}
|
|
|
|
StackFrameIterator::StackFrameIterator(uword last_fp,
|
|
ValidationPolicy validation_policy,
|
|
Thread* thread,
|
|
CrossThreadPolicy cross_thread_policy)
|
|
: validate_(validation_policy == ValidationPolicy::kValidateFrames),
|
|
entry_(thread),
|
|
exit_(thread),
|
|
frames_(thread),
|
|
current_frame_(NULL),
|
|
thread_(thread) {
|
|
ASSERT(cross_thread_policy == kAllowCrossThreadIteration ||
|
|
thread_ == Thread::Current());
|
|
frames_.fp_ = last_fp;
|
|
frames_.sp_ = 0;
|
|
frames_.pc_ = 0;
|
|
if (FLAG_enable_interpreter) {
|
|
frames_.CheckIfInterpreted(last_fp);
|
|
}
|
|
frames_.Unpoison();
|
|
}
|
|
|
|
StackFrameIterator::StackFrameIterator(uword fp,
|
|
uword sp,
|
|
uword pc,
|
|
ValidationPolicy validation_policy,
|
|
Thread* thread,
|
|
CrossThreadPolicy cross_thread_policy)
|
|
: validate_(validation_policy == ValidationPolicy::kValidateFrames),
|
|
entry_(thread),
|
|
exit_(thread),
|
|
frames_(thread),
|
|
current_frame_(NULL),
|
|
thread_(thread) {
|
|
ASSERT(cross_thread_policy == kAllowCrossThreadIteration ||
|
|
thread_ == Thread::Current());
|
|
frames_.fp_ = fp;
|
|
frames_.sp_ = sp;
|
|
frames_.pc_ = pc;
|
|
if (FLAG_enable_interpreter) {
|
|
frames_.CheckIfInterpreted(fp);
|
|
}
|
|
frames_.Unpoison();
|
|
}
|
|
|
|
StackFrame* StackFrameIterator::NextFrame() {
|
|
// When we are at the start of iteration after having created an
|
|
// iterator object, current_frame_ will be NULL as we haven't seen
|
|
// any frames yet (unless we start iterating in the simulator from a given
|
|
// triplet of fp, sp, and pc). At this point, if NextFrame is called, it tries
|
|
// to set up the next exit frame by reading the top_exit_frame_info
|
|
// from the isolate. If we do not have any dart invocations yet,
|
|
// top_exit_frame_info will be 0 and so we would return NULL.
|
|
|
|
// current_frame_ will also be NULL, when we are at the end of having
|
|
// iterated through all the frames. If NextFrame is called at this
|
|
// point, we will try and set up the next exit frame, but since we are
|
|
// at the end of the iteration, fp_ will be 0 and we would return NULL.
|
|
if (current_frame_ == NULL) {
|
|
if (!HasNextFrame()) {
|
|
return NULL;
|
|
}
|
|
if (frames_.pc_ == 0) {
|
|
// Iteration starts from an exit frame given by its fp.
|
|
current_frame_ = NextExitFrame();
|
|
} else if (*(reinterpret_cast<uword*>(
|
|
frames_.fp_ +
|
|
((frames_.is_interpreted() ? kKBCSavedCallerFpSlotFromFp
|
|
: kSavedCallerFpSlotFromFp) *
|
|
kWordSize))) == 0) {
|
|
// Iteration starts from an entry frame given by its fp, sp, and pc.
|
|
current_frame_ = NextEntryFrame();
|
|
} else {
|
|
// Iteration starts from a Dart or stub frame given by its fp, sp, and pc.
|
|
current_frame_ = frames_.NextFrame(validate_);
|
|
}
|
|
return current_frame_;
|
|
}
|
|
ASSERT(!validate_ || current_frame_->IsValid());
|
|
if (current_frame_->IsEntryFrame()) {
|
|
if (HasNextFrame()) { // We have another chained block.
|
|
current_frame_ = NextExitFrame();
|
|
return current_frame_;
|
|
}
|
|
current_frame_ = NULL; // No more frames.
|
|
return current_frame_;
|
|
}
|
|
ASSERT(!validate_ || current_frame_->IsExitFrame() ||
|
|
current_frame_->IsDartFrame(validate_) ||
|
|
current_frame_->IsStubFrame());
|
|
|
|
// Consume dart/stub frames using StackFrameIterator::FrameSetIterator
|
|
// until we are out of dart/stub frames at which point we return the
|
|
// corresponding entry frame for that set of dart/stub frames.
|
|
current_frame_ =
|
|
(frames_.HasNext()) ? frames_.NextFrame(validate_) : NextEntryFrame();
|
|
return current_frame_;
|
|
}
|
|
|
|
void StackFrameIterator::FrameSetIterator::CheckIfInterpreted(
|
|
uword exit_marker) {
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
// TODO(regis): We should rely on a new thread vm_tag to identify an
|
|
// interpreter frame and not need the HasFrame() method.
|
|
ASSERT(FLAG_enable_interpreter);
|
|
Interpreter* interpreter = thread_->interpreter();
|
|
is_interpreted_ = (interpreter != NULL) && interpreter->HasFrame(exit_marker);
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
}
|
|
|
|
// Tell MemorySanitizer that generated code initializes part of the stack.
|
|
void StackFrameIterator::FrameSetIterator::Unpoison() {
|
|
// When using a simulator, all writes to the stack happened from MSAN
|
|
// instrumented C++, so there is nothing to unpoison. Additionally,
|
|
// fp_ will be somewhere in the simulator's stack instead of the OSThread's
|
|
// stack.
|
|
#if !defined(USING_SIMULATOR)
|
|
if (fp_ == 0) return;
|
|
// Note that Thread::os_thread_ is cleared when the thread is descheduled.
|
|
ASSERT(is_interpreted_ || (thread_->os_thread() == nullptr) ||
|
|
((thread_->os_thread()->stack_limit() < fp_) &&
|
|
(thread_->os_thread()->stack_base() > fp_)));
|
|
uword lower;
|
|
if (sp_ == 0) {
|
|
// Exit frame: guess sp.
|
|
lower = fp_ - kDartFrameFixedSize * kWordSize;
|
|
} else {
|
|
lower = sp_;
|
|
}
|
|
uword upper = fp_ + kSavedCallerPcSlotFromFp * kWordSize;
|
|
// Both lower and upper are inclusive, so we add one word when computing size.
|
|
MSAN_UNPOISON(reinterpret_cast<void*>(lower), upper - lower + kWordSize);
|
|
#endif // !defined(USING_SIMULATOR)
|
|
}
|
|
|
|
StackFrame* StackFrameIterator::FrameSetIterator::NextFrame(bool validate) {
|
|
StackFrame* frame;
|
|
ASSERT(HasNext());
|
|
frame = &stack_frame_;
|
|
frame->sp_ = sp_;
|
|
frame->fp_ = fp_;
|
|
frame->pc_ = pc_;
|
|
frame->is_interpreted_ = is_interpreted_;
|
|
sp_ = frame->GetCallerSp();
|
|
fp_ = frame->GetCallerFp();
|
|
pc_ = frame->GetCallerPc();
|
|
Unpoison();
|
|
ASSERT(is_interpreted_ == frame->is_interpreted_);
|
|
ASSERT(!validate || frame->IsValid());
|
|
return frame;
|
|
}
|
|
|
|
ExitFrame* StackFrameIterator::NextExitFrame() {
|
|
exit_.sp_ = frames_.sp_;
|
|
exit_.fp_ = frames_.fp_;
|
|
exit_.pc_ = frames_.pc_;
|
|
exit_.is_interpreted_ = frames_.is_interpreted_;
|
|
frames_.sp_ = exit_.GetCallerSp();
|
|
frames_.fp_ = exit_.GetCallerFp();
|
|
frames_.pc_ = exit_.GetCallerPc();
|
|
frames_.Unpoison();
|
|
ASSERT(frames_.is_interpreted_ == exit_.is_interpreted_);
|
|
ASSERT(!validate_ || exit_.IsValid());
|
|
return &exit_;
|
|
}
|
|
|
|
EntryFrame* StackFrameIterator::NextEntryFrame() {
|
|
ASSERT(!frames_.HasNext());
|
|
entry_.sp_ = frames_.sp_;
|
|
entry_.fp_ = frames_.fp_;
|
|
entry_.pc_ = frames_.pc_;
|
|
entry_.is_interpreted_ = frames_.is_interpreted_;
|
|
SetupNextExitFrameData(); // Setup data for next exit frame in chain.
|
|
ASSERT(!validate_ || entry_.IsValid());
|
|
return &entry_;
|
|
}
|
|
|
|
InlinedFunctionsIterator::InlinedFunctionsIterator(const Code& code, uword pc)
|
|
: index_(0),
|
|
num_materializations_(0),
|
|
dest_frame_size_(0),
|
|
code_(Code::Handle(code.raw())),
|
|
deopt_info_(TypedData::Handle()),
|
|
function_(Function::Handle()),
|
|
pc_(pc),
|
|
deopt_instructions_(),
|
|
object_table_(ObjectPool::Handle()) {
|
|
ASSERT(code_.is_optimized());
|
|
ASSERT(pc_ != 0);
|
|
ASSERT(code.ContainsInstructionAt(pc));
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
ASSERT(deopt_info_.IsNull());
|
|
function_ = code_.function();
|
|
#else
|
|
ICData::DeoptReasonId deopt_reason = ICData::kDeoptUnknown;
|
|
uint32_t deopt_flags = 0;
|
|
deopt_info_ = code_.GetDeoptInfoAtPc(pc, &deopt_reason, &deopt_flags);
|
|
if (deopt_info_.IsNull()) {
|
|
// This is the case when a call without deopt info in optimized code
|
|
// throws an exception. (e.g. in the parameter copying prologue).
|
|
// In that case there won't be any inlined frames.
|
|
function_ = code_.function();
|
|
} else {
|
|
// Unpack deopt info into instructions (translate away suffixes).
|
|
const Array& deopt_table = Array::Handle(code_.deopt_info_array());
|
|
ASSERT(!deopt_table.IsNull());
|
|
DeoptInfo::Unpack(deopt_table, deopt_info_, &deopt_instructions_);
|
|
num_materializations_ = DeoptInfo::NumMaterializations(deopt_instructions_);
|
|
dest_frame_size_ = DeoptInfo::FrameSize(deopt_info_);
|
|
object_table_ = code_.GetObjectPool();
|
|
Advance();
|
|
}
|
|
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
|
}
|
|
|
|
void InlinedFunctionsIterator::Advance() {
|
|
// Iterate over the deopt instructions and determine the inlined
|
|
// functions if any and iterate over them.
|
|
ASSERT(!Done());
|
|
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
ASSERT(deopt_info_.IsNull());
|
|
SetDone();
|
|
return;
|
|
#else
|
|
if (deopt_info_.IsNull()) {
|
|
SetDone();
|
|
return;
|
|
}
|
|
|
|
ASSERT(deopt_instructions_.length() != 0);
|
|
while (index_ < deopt_instructions_.length()) {
|
|
DeoptInstr* deopt_instr = deopt_instructions_[index_++];
|
|
if (deopt_instr->kind() == DeoptInstr::kRetAddress) {
|
|
pc_ = DeoptInstr::GetRetAddress(deopt_instr, object_table_, &code_);
|
|
function_ = code_.function();
|
|
return;
|
|
}
|
|
}
|
|
SetDone();
|
|
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
|
}
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
// Finds the potential offset for the current function's FP if the
|
|
// current frame were to be deoptimized.
|
|
intptr_t InlinedFunctionsIterator::GetDeoptFpOffset() const {
|
|
ASSERT(deopt_instructions_.length() != 0);
|
|
for (intptr_t index = index_; index < deopt_instructions_.length(); index++) {
|
|
DeoptInstr* deopt_instr = deopt_instructions_[index];
|
|
if (deopt_instr->kind() == DeoptInstr::kCallerFp) {
|
|
return index - num_materializations_;
|
|
}
|
|
}
|
|
UNREACHABLE();
|
|
return 0;
|
|
}
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
#if defined(DEBUG)
|
|
void ValidateFrames() {
|
|
StackFrameIterator frames(ValidationPolicy::kValidateFrames,
|
|
Thread::Current(),
|
|
StackFrameIterator::kNoCrossThreadIteration);
|
|
StackFrame* frame = frames.NextFrame();
|
|
while (frame != NULL) {
|
|
frame = frames.NextFrame();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
} // namespace dart
|