Expose primop to atomically "clone" a thread stack
Motivation
Due to mutation, the stack is a moving target for tools that should analyze it, like ghc-heap
("stack decoding"), ghc-debug
and the stack unwinder (#18163 (closed)).
Proposal
To make those analyses safe, we could add a pair of primops to atomically "clone" a stack:
-- | A snapshot of the state of an evaluation stack.
data StackSnapshot#
-- | Capture a 'StackSnapshot#' of the state of the current thread's stack.
cloneMyStack :: State# RealWorld -> (# State# RealWorld, StackSnapshot# #)
-- | Capture a 'StackSnapshot#' of the state of another thread's stack.
cloneThreadStack :: ThreadId#
-> State# RealWorld -> (# State# RealWorld, StackSnapshot# #)
"Cloning" means in this context to make a deep copy of every closure to non garbage collected memory. "Deep" means that all payload closures are copied, too, and that a copy only references other copies as payload.
With a little help from the RTS (by way of a new Message
type), cloneThreadStack
would likely be implemented mostly in Haskell as follows:
data StackSnapshot = StackSnapshot StackSnapshot#
cloneThreadStack :: ThreadId# -> IO StackSnapshot
cloneThreadStack tid# = do
resultVar <- newEmptyMVar @StackSnapshot
-- Use the RTS's "message" mechanism to request that
-- the thread captures its stack, saving the result
-- into resultVar.
sendCaptureStackMessage tid# resultVar
takeMVar resultVar
In addition, we will ultimately expose an interface in ghc-heap
for examining the frames of the stack:
data StackFrame
-- | Decode a 'StackSnapshot' into its constituent stack frames.
decodeStack :: StackSnapshot -> [StackFrame]
To start out StackFrame
would likely have a simple, abstract structure:
-- | Is a stack frame an update frame? If so, provides a reference to the updatee.
stackFrameIsUpdateFrame :: StackFrame -> Maybe Box
-- | Enumerate the pointers retained by a 'StackFrame'.
stackFramePointers :: StackFrame -> [Box]
-- | Is a stack frame a "return" frame of a @case@ analysis?
-- The source location may give the source location (assuming either DWARF or
-- something like @mpickering's !3469 is available.
stackFrameIsReturnFrame :: StackFrame -> Maybe SrcLoc
StackFrame
would