// 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(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(sp() + ((is_interpreted() ? kKBCSavedPcSlotFromSp : kSavedPcSlotFromSp) * kWordSize)) = value; pc_ = value; } void set_pc_marker(CodePtr code) { *reinterpret_cast( 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_BYTECODE_INTERPRETER) 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( fp() + ((is_interpreted() ? kKBCSavedCallerFpSlotFromFp : kSavedCallerFpSlotFromFp) * kWordSize))); } uword GetCallerPc() const { uword raw_pc = *(reinterpret_cast( 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_BYTECODE_INTERPRETER) 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( 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_BYTECODE_INTERPRETER) 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_BYTECODE_INTERPRETER) 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 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(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_