| // Copyright (c) 2011, 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. | |
| #ifndef RUNTIME_VM_STACK_FRAME_H_ | |
| #define RUNTIME_VM_STACK_FRAME_H_ | |
| #include "vm/allocation.h" | |
| #include "vm/frame_layout.h" | |
| #include "vm/interpreter.h" | |
| #include "vm/object.h" | |
| #include "vm/stack_frame_kbc.h" | |
| #include "vm/stub_code.h" | |
| #if defined(TARGET_ARCH_IA32) | |
| #include "vm/stack_frame_ia32.h" | |
| #elif defined(TARGET_ARCH_X64) | |
| #include "vm/stack_frame_x64.h" | |
| #elif defined(TARGET_ARCH_ARM) | |
| #include "vm/stack_frame_arm.h" | |
| #elif defined(TARGET_ARCH_ARM64) | |
| #include "vm/stack_frame_arm64.h" | |
| #elif defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) | |
| #include "vm/stack_frame_riscv.h" | |
| #else | |
| #error Unknown architecture. | |
| #endif | |
| namespace dart { | |
| // Forward declarations. | |
| class ObjectPointerVisitor; | |
| class LocalVariable; | |
| extern FrameLayout runtime_frame_layout; | |
| // Generic stack frame. | |
| class StackFrame : public ValueObject { | |
| public: | |
| virtual ~StackFrame() {} | |
| // Accessors to get the pc, sp and fp of a frame. | |
| uword sp() const { return sp_; } | |
| uword fp() const { return fp_; } | |
| uword pc() const { return pc_; } | |
| // The pool pointer is not implemented on all architectures. | |
| static int SavedCallerPpSlotFromFp() { | |
| // Never called on an interpreter frame. | |
| if (runtime_frame_layout.saved_caller_pp_from_fp != | |
| kSavedCallerFpSlotFromFp) { | |
| return runtime_frame_layout.saved_caller_pp_from_fp; | |
| } | |
| UNREACHABLE(); | |
| return 0; | |
| } | |
| bool IsMarkedForLazyDeopt() const { | |
| ASSERT(!is_interpreted()); | |
| uword raw_pc = | |
| *reinterpret_cast<uword*>(sp() + (kSavedPcSlotFromSp * kWordSize)); | |
| return raw_pc == StubCode::DeoptimizeLazyFromReturn().EntryPoint(); | |
| } | |
| void MarkForLazyDeopt() { | |
| ASSERT(!is_interpreted()); | |
| set_pc(StubCode::DeoptimizeLazyFromReturn().EntryPoint()); | |
| } | |
| void UnmarkForLazyDeopt() { | |
| // If this frame was marked for lazy deopt, pc_ was computed to be the | |
| // original return address using the pending deopts table in GetCallerPc. | |
| // Write this value back into the frame. | |
| ASSERT(!is_interpreted()); | |
| uword original_pc = pc(); | |
| ASSERT(original_pc != StubCode::DeoptimizeLazyFromReturn().EntryPoint()); | |
| set_pc(original_pc); | |
| } | |
| void set_pc(uword value) { | |
| *reinterpret_cast<uword*>(sp() + ((is_interpreted() ? kKBCSavedPcSlotFromSp | |
| : kSavedPcSlotFromSp) * | |
| kWordSize)) = value; | |
| pc_ = value; | |
| } | |
| void set_pc_marker(CodePtr code) { | |
| *reinterpret_cast<CodePtr*>( | |
| fp() + ((is_interpreted() ? kKBCPcMarkerSlotFromFp | |
| : runtime_frame_layout.code_from_fp) * | |
| kWordSize)) = code; | |
| } | |
| // Visit objects in the frame. | |
| virtual void VisitObjectPointers(ObjectPointerVisitor* visitor); | |
| const char* ToCString() const; | |
| // Check validity of a frame, used for assertion purposes. | |
| virtual bool IsValid() const; | |
| // Returns true iff the current frame is a bare instructions dart frame. | |
| bool IsBareInstructionsDartFrame() const; | |
| // Returns true iff the current frame is a bare instructions stub frame. | |
| bool IsBareInstructionsStubFrame() const; | |
| // Frame type. | |
| virtual bool IsDartFrame(bool validate = true) const { | |
| ASSERT(!validate || IsValid()); | |
| return !(IsEntryFrame() || IsExitFrame() || IsStubFrame()); | |
| } | |
| virtual bool IsStubFrame() const; | |
| virtual bool IsEntryFrame() const { return false; } | |
| virtual bool IsExitFrame() const { return false; } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| bool is_interpreted() const { return is_interpreted_; } | |
| #else | |
| bool is_interpreted() const { return false; } | |
| #endif | |
| FunctionPtr LookupDartFunction() const; | |
| CodePtr LookupDartCode() const; | |
| BytecodePtr LookupDartBytecode() const; | |
| bool FindExceptionHandler(Thread* thread, | |
| uword* handler_pc, | |
| bool* needs_stacktrace, | |
| bool* is_catch_all, | |
| bool* is_optimized) const; | |
| // Returns token_pos of the pc(), or -1 if none exists. | |
| TokenPosition GetTokenPos() const; | |
| static void DumpCurrentTrace(); | |
| static void DumpCurrentTrace(uword sp, uword fp, uword pc); | |
| uword GetCallerSp() const { | |
| return fp() + | |
| ((is_interpreted() ? kKBCCallerSpSlotFromFp : kCallerSpSlotFromFp) * | |
| kWordSize); | |
| } | |
| protected: | |
| explicit StackFrame(Thread* thread) | |
| : fp_(0), sp_(0), pc_(0), thread_(thread) {} | |
| // Name of the frame, used for generic frame printing functionality. | |
| virtual const char* GetName() const { | |
| if (IsBareInstructionsStubFrame()) return "bare-stub"; | |
| if (IsStubFrame()) return "stub"; | |
| return IsBareInstructionsDartFrame() ? "bare-dart" : "dart"; | |
| } | |
| Isolate* isolate() const { return thread_->isolate(); } | |
| IsolateGroup* isolate_group() const { return thread_->isolate_group(); } | |
| Thread* thread() const { return thread_; } | |
| private: | |
| CodePtr GetCodeObject() const; | |
| BytecodePtr GetBytecodeObject() const; | |
| uword GetCallerFp() const { | |
| return *(reinterpret_cast<uword*>( | |
| fp() + ((is_interpreted() ? kKBCSavedCallerFpSlotFromFp | |
| : kSavedCallerFpSlotFromFp) * | |
| kWordSize))); | |
| } | |
| uword GetCallerPc() const { | |
| uword raw_pc = *(reinterpret_cast<uword*>( | |
| fp() + ((is_interpreted() ? kKBCSavedCallerPcSlotFromFp | |
| : kSavedCallerPcSlotFromFp) * | |
| kWordSize))); | |
| ASSERT(raw_pc != StubCode::DeoptimizeLazyFromThrow().EntryPoint()); | |
| if (raw_pc == StubCode::DeoptimizeLazyFromReturn().EntryPoint()) { | |
| return thread_->pending_deopts().FindPendingDeopt(GetCallerFp()); | |
| } | |
| return raw_pc; | |
| } | |
| uword fp_; | |
| uword sp_; | |
| uword pc_; | |
| Thread* thread_; | |
| #if defined(DART_DYNAMIC_MODULES) | |
| bool is_interpreted_ = false; | |
| #endif | |
| // The iterators FrameSetIterator and StackFrameIterator set the private | |
| // fields fp_ and sp_ when they return the respective frame objects. | |
| friend class FrameSetIterator; | |
| friend class StackFrameIterator; | |
| // UntaggedSuspendState::VisitSuspendStatePointers creates a temporary | |
| // StackFrame objects for the copied frames of the suspended functions. | |
| friend class UntaggedSuspendState; | |
| friend class ProfilerDartStackWalker; | |
| DISALLOW_COPY_AND_ASSIGN(StackFrame); | |
| }; | |
| // Exit frame is used to mark the transition from dart code into dart VM | |
| // runtime code. | |
| class ExitFrame : public StackFrame { | |
| public: | |
| bool IsValid() const { return sp() == 0; } | |
| bool IsDartFrame(bool validate = true) const { return false; } | |
| bool IsStubFrame() const { return false; } | |
| bool IsExitFrame() const { return true; } | |
| // Visit objects in the frame. | |
| virtual void VisitObjectPointers(ObjectPointerVisitor* visitor); | |
| protected: | |
| virtual const char* GetName() const { return "exit"; } | |
| private: | |
| explicit ExitFrame(Thread* thread) : StackFrame(thread) {} | |
| friend class StackFrameIterator; | |
| DISALLOW_COPY_AND_ASSIGN(ExitFrame); | |
| }; | |
| // Entry Frame is used to mark the transition from dart VM runtime code into | |
| // dart code. | |
| class EntryFrame : public StackFrame { | |
| public: | |
| bool IsValid() const { | |
| return StubCode::InInvocationStub(thread(), pc(), is_interpreted()); | |
| } | |
| bool IsDartFrame(bool validate = true) const { return false; } | |
| bool IsStubFrame() const { return false; } | |
| bool IsEntryFrame() const { return true; } | |
| // Visit objects in the frame. | |
| virtual void VisitObjectPointers(ObjectPointerVisitor* visitor); | |
| protected: | |
| virtual const char* GetName() const { return "entry"; } | |
| private: | |
| explicit EntryFrame(Thread* thread) : StackFrame(thread) {} | |
| friend class StackFrameIterator; | |
| DISALLOW_COPY_AND_ASSIGN(EntryFrame); | |
| }; | |
| // A StackFrameIterator can be initialized with a thread other than the | |
| // current thread. Because this is generally a bad idea, it is only allowed on | |
| // Windows- where it is needed for the profiler. It is the responsibility of | |
| // users of StackFrameIterator to ensure that the thread given is not running | |
| // concurrently. | |
| class StackFrameIterator { | |
| public: | |
| enum CrossThreadPolicy { | |
| kNoCrossThreadIteration = 0, | |
| kAllowCrossThreadIteration = 1, | |
| }; | |
| // Iterators for iterating over all frames from the last ExitFrame to the | |
| // first EntryFrame. | |
| StackFrameIterator(ValidationPolicy validation_policy, | |
| Thread* thread, | |
| CrossThreadPolicy cross_thread_policy); | |
| StackFrameIterator(uword last_fp, | |
| ValidationPolicy validation_policy, | |
| Thread* thread, | |
| CrossThreadPolicy cross_thread_policy); | |
| // Iterator for iterating over all frames from the current frame (given by its | |
| // fp, sp, and pc) to the first EntryFrame. | |
| StackFrameIterator(uword fp, | |
| uword sp, | |
| uword pc, | |
| ValidationPolicy validation_policy, | |
| Thread* thread, | |
| CrossThreadPolicy cross_thread_policy); | |
| explicit StackFrameIterator(const StackFrameIterator& orig); | |
| // Checks if a next frame exists. | |
| bool HasNextFrame() const { return frames_.fp_ != 0; } | |
| // Get next frame. Becomes invalid after the next call to NextFrame. | |
| StackFrame* NextFrame(); | |
| bool validate() const { return validate_; } | |
| private: | |
| // Iterator for iterating over the set of frames (dart or stub) which exist | |
| // in one EntryFrame and ExitFrame block. | |
| class FrameSetIterator : public ValueObject { | |
| public: | |
| // Checks if a next non entry/exit frame exists in the set. | |
| bool HasNext() const { | |
| if (fp_ == 0) { | |
| return false; | |
| } | |
| const uword pc = *(reinterpret_cast<uword*>( | |
| sp_ + | |
| ((is_interpreted() ? kKBCSavedPcSlotFromSp : kSavedPcSlotFromSp) * | |
| kWordSize))); | |
| return !StubCode::InInvocationStub(thread_, pc, is_interpreted()); | |
| } | |
| // Get next non entry/exit frame in the set (assumes a next frame exists). | |
| StackFrame* NextFrame(bool validate); | |
| private: | |
| explicit FrameSetIterator(Thread* thread) | |
| : fp_(0), sp_(0), pc_(0), stack_frame_(thread), thread_(thread) {} | |
| #if defined(DART_DYNAMIC_MODULES) | |
| bool is_interpreted() const { return is_interpreted_; } | |
| void CheckIfInterpreted(uword exit_marker); | |
| #else | |
| bool is_interpreted() const { return false; } | |
| #endif | |
| void Unpoison(); | |
| uword fp_; | |
| uword sp_; | |
| uword pc_; | |
| StackFrame stack_frame_; // Singleton frame returned by NextFrame(). | |
| Thread* thread_; | |
| #if defined(DART_DYNAMIC_MODULES) | |
| bool is_interpreted_ = false; | |
| #endif | |
| friend class StackFrameIterator; | |
| DISALLOW_COPY_AND_ASSIGN(FrameSetIterator); | |
| }; | |
| // Get next exit frame. | |
| ExitFrame* NextExitFrame(); | |
| // Get next entry frame. | |
| EntryFrame* NextEntryFrame(); | |
| // Get an iterator to the next set of frames between an entry and exit | |
| // frame. | |
| FrameSetIterator* NextFrameSet() { return &frames_; } | |
| // Setup last or next exit frames so that we are ready to iterate over | |
| // stack frames. | |
| void SetupLastExitFrameData(); | |
| void SetupNextExitFrameData(); | |
| void CheckInterpreterExitFrame(uword exit_marker); | |
| bool validate_; // Validate each frame as we traverse the frames. | |
| EntryFrame entry_; // Singleton entry frame returned by NextEntryFrame(). | |
| ExitFrame exit_; // Singleton exit frame returned by NextExitFrame(). | |
| FrameSetIterator frames_; | |
| StackFrame* current_frame_; // Points to the current frame in the iterator. | |
| Thread* thread_; | |
| friend class ProfilerDartStackWalker; | |
| }; | |
| // Iterator for iterating over all dart frames (skips over exit frames, | |
| // entry frames and stub frames). | |
| // A DartFrameIterator can be initialized with an isolate other than the | |
| // current thread's isolate. Because this is generally a bad idea, | |
| // it is only allowed on Windows- where it is needed for the profiler. | |
| // It is the responsibility of users of DartFrameIterator to ensure that the | |
| // isolate given is not running concurrently on another thread. | |
| class DartFrameIterator { | |
| public: | |
| explicit DartFrameIterator( | |
| Thread* thread, | |
| StackFrameIterator::CrossThreadPolicy cross_thread_policy) | |
| : frames_(ValidationPolicy::kDontValidateFrames, | |
| thread, | |
| cross_thread_policy) {} | |
| explicit DartFrameIterator( | |
| uword last_fp, | |
| Thread* thread, | |
| StackFrameIterator::CrossThreadPolicy cross_thread_policy) | |
| : frames_(last_fp, | |
| ValidationPolicy::kDontValidateFrames, | |
| thread, | |
| cross_thread_policy) {} | |
| DartFrameIterator(uword fp, | |
| uword sp, | |
| uword pc, | |
| Thread* thread, | |
| StackFrameIterator::CrossThreadPolicy cross_thread_policy) | |
| : frames_(fp, | |
| sp, | |
| pc, | |
| ValidationPolicy::kDontValidateFrames, | |
| thread, | |
| cross_thread_policy) {} | |
| explicit DartFrameIterator(const DartFrameIterator& orig) | |
| : frames_(orig.frames_) {} | |
| // Get next dart frame. | |
| StackFrame* NextFrame() { | |
| StackFrame* frame = frames_.NextFrame(); | |
| while (frame != nullptr && !frame->IsDartFrame(frames_.validate())) { | |
| frame = frames_.NextFrame(); | |
| } | |
| return frame; | |
| } | |
| private: | |
| StackFrameIterator frames_; | |
| }; | |
| // Iterator for iterating over all inlined dart functions in an optimized | |
| // dart frame (the iteration includes the function that is inlining the | |
| // other functions). | |
| class InlinedFunctionsIterator : public ValueObject { | |
| public: | |
| InlinedFunctionsIterator(const Code& code, uword pc); | |
| bool Done() const { return index_ == -1; } | |
| void Advance(); | |
| FunctionPtr function() const { | |
| ASSERT(!Done()); | |
| return function_.ptr(); | |
| } | |
| uword pc() const { | |
| ASSERT(!Done()); | |
| return pc_; | |
| } | |
| CodePtr code() const { | |
| ASSERT(!Done()); | |
| return code_.ptr(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| intptr_t GetDeoptFpOffset() const; | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| private: | |
| void SetDone() { index_ = -1; } | |
| intptr_t index_; | |
| intptr_t num_materializations_; | |
| intptr_t dest_frame_size_; | |
| Code& code_; | |
| TypedData& deopt_info_; | |
| Function& function_; | |
| uword pc_; | |
| GrowableArray<DeoptInstr*> deopt_instructions_; | |
| ObjectPool& object_table_; | |
| DISALLOW_COPY_AND_ASSIGN(InlinedFunctionsIterator); | |
| }; | |
| #if defined(DEBUG) | |
| void ValidateFrames(); | |
| #endif | |
| DART_FORCE_INLINE static intptr_t LocalVarIndex(intptr_t fp_offset, | |
| intptr_t var_index) { | |
| return fp_offset + var_index; | |
| } | |
| DART_FORCE_INLINE static uword ParamAddress(uword fp, intptr_t reverse_index) { | |
| return fp + (kParamEndSlotFromFp * kWordSize) + (reverse_index * kWordSize); | |
| } | |
| // Both fp and other_fp are compiled code frame pointers. | |
| DART_FORCE_INLINE static bool IsCalleeFrameOf(uword fp, uword other_fp) { | |
| return other_fp < fp; | |
| } | |
| // Value for stack limit that is used to cause an interrupt. | |
| static constexpr uword kInterruptStackLimit = ~static_cast<uword>(0); | |
| DART_FORCE_INLINE static uword LocalVarAddress(uword fp, intptr_t index) { | |
| return fp + LocalVarIndex(0, index) * kWordSize; | |
| } | |
| } // namespace dart | |
| #endif // RUNTIME_VM_STACK_FRAME_H_ |