Rodrigo Mesquita pushed to branch wip/romes/step-out at Glasgow Haskell Compiler / GHC
Commits:
-
cb62ba50
by Rodrigo Mesquita at 2025-05-16T14:02:46+01:00
20 changed files:
- compiler/GHC/Driver/Config.hs
- compiler/GHC/Runtime/Eval.hs
- compiler/GHC/Runtime/Eval/Types.hs
- docs/users_guide/ghci.rst
- ghc/GHCi/UI.hs
- libraries/ghci/GHCi/Message.hs
- libraries/ghci/GHCi/Run.hs
- rts/Interpreter.c
- rts/RtsSymbols.c
- rts/include/stg/MiscClosures.h
- + testsuite/tests/ghci.debugger/scripts/T26042a.hs
- + testsuite/tests/ghci.debugger/scripts/T26042a.script
- + testsuite/tests/ghci.debugger/scripts/T26042a.stdout
- + testsuite/tests/ghci.debugger/scripts/T26042b.hs
- + testsuite/tests/ghci.debugger/scripts/T26042b.script
- + testsuite/tests/ghci.debugger/scripts/T26042b.stdout
- + testsuite/tests/ghci.debugger/scripts/T26042c.hs
- + testsuite/tests/ghci.debugger/scripts/T26042c.script
- + testsuite/tests/ghci.debugger/scripts/T26042c.stdout
- testsuite/tests/ghci.debugger/scripts/all.T
Changes:
... | ... | @@ -3,6 +3,7 @@ module GHC.Driver.Config |
3 | 3 | ( initOptCoercionOpts
|
4 | 4 | , initSimpleOpts
|
5 | 5 | , initEvalOpts
|
6 | + , EvalStep(..)
|
|
6 | 7 | )
|
7 | 8 | where
|
8 | 9 | |
... | ... | @@ -28,13 +29,28 @@ initSimpleOpts dflags = SimpleOpts |
28 | 29 | , so_inline = True
|
29 | 30 | }
|
30 | 31 | |
32 | +-- | Instruct the interpreter evaluation to break...
|
|
33 | +data EvalStep
|
|
34 | + -- | ... at every breakpoint tick
|
|
35 | + = EvalStepSingle
|
|
36 | + -- | ... after every return stmt
|
|
37 | + | EvalStepOut
|
|
38 | + -- | ... only on explicit breakpoints
|
|
39 | + | EvalStepNone
|
|
40 | + |
|
31 | 41 | -- | Extract GHCi options from DynFlags and step
|
32 | -initEvalOpts :: DynFlags -> Bool -> EvalOpts
|
|
42 | +initEvalOpts :: DynFlags -> EvalStep -> EvalOpts
|
|
33 | 43 | initEvalOpts dflags step =
|
34 | 44 | EvalOpts
|
35 | 45 | { useSandboxThread = gopt Opt_GhciSandbox dflags
|
36 | - , singleStep = step
|
|
46 | + , singleStep = singleStep
|
|
47 | + , stepOut = stepOut
|
|
37 | 48 | , breakOnException = gopt Opt_BreakOnException dflags
|
38 | 49 | , breakOnError = gopt Opt_BreakOnError dflags
|
39 | 50 | }
|
51 | + where
|
|
52 | + (singleStep, stepOut) = case step of
|
|
53 | + EvalStepSingle -> (True, False)
|
|
54 | + EvalStepOut -> (False, True)
|
|
55 | + EvalStepNone -> (False, False)
|
|
40 | 56 |
... | ... | @@ -343,7 +343,12 @@ handleRunStatus step expr bindings final_ids status history0 = do |
343 | 343 | setSession hsc_env2
|
344 | 344 | return (ExecBreak names Nothing)
|
345 | 345 | |
346 | - -- Just case: we stopped at a breakpoint
|
|
346 | + -- EvalBreak (Just ...) case: the interpreter stopped at a breakpoint
|
|
347 | + --
|
|
348 | + -- The interpreter yields on a breakpoint if:
|
|
349 | + -- - the breakpoint was explicitly enabled (in @BreakArray@)
|
|
350 | + -- - or @singleStep = True@ in EvalOpts
|
|
351 | + -- - or @stepOut = True@ in EvalOpts
|
|
347 | 352 | EvalBreak apStack_ref (Just eval_break) resume_ctxt ccs -> do
|
348 | 353 | let ibi = evalBreakpointToId eval_break
|
349 | 354 | tick_brks <- liftIO $ readModBreaks hsc_env (ibi_tick_mod ibi)
|
... | ... | @@ -351,13 +356,14 @@ handleRunStatus step expr bindings final_ids status history0 = do |
351 | 356 | span = modBreaks_locs tick_brks ! ibi_tick_index ibi
|
352 | 357 | decl = intercalate "." $ modBreaks_decls tick_brks ! ibi_tick_index ibi
|
353 | 358 | |
359 | + -- Was the breakpoint explicitly enabled?
|
|
354 | 360 | b <- liftIO $ breakpointStatus interp (modBreaks_flags tick_brks) (ibi_tick_index ibi)
|
355 | 361 | |
356 | 362 | apStack_fhv <- liftIO $ mkFinalizedHValue interp apStack_ref
|
357 | 363 | resume_ctxt_fhv <- liftIO $ mkFinalizedHValue interp resume_ctxt
|
358 | 364 | |
359 | - -- This breakpoint is explicitly enabled; we want to stop
|
|
360 | - -- instead of just logging it.
|
|
365 | + -- This breakpoint is enabled or we mean to break here;
|
|
366 | + -- we want to stop instead of just logging it.
|
|
361 | 367 | if b || breakHere step span then do
|
362 | 368 | -- This function only returns control to ghci with 'ExecBreak' when it is really meant to break.
|
363 | 369 | -- Specifically, for :steplocal or :stepmodule, don't return control
|
... | ... | @@ -1247,7 +1253,7 @@ compileParsedExprRemote expr@(L loc _) = withSession $ \hsc_env -> do |
1247 | 1253 | _ -> panic "compileParsedExprRemote"
|
1248 | 1254 | |
1249 | 1255 | updateFixityEnv fix_env
|
1250 | - let eval_opts = initEvalOpts dflags False
|
|
1256 | + let eval_opts = initEvalOpts dflags EvalStepNone
|
|
1251 | 1257 | status <- liftIO $ evalStmt interp eval_opts (EvalThis hvals_io)
|
1252 | 1258 | case status of
|
1253 | 1259 | EvalComplete _ (EvalSuccess [hval]) -> return hval
|
... | ... | @@ -17,6 +17,7 @@ import GHC.Prelude |
17 | 17 | |
18 | 18 | import GHCi.RemoteTypes
|
19 | 19 | import GHCi.Message (EvalExpr, ResumeContext)
|
20 | +import GHC.Driver.Config (EvalStep(..))
|
|
20 | 21 | import GHC.Types.Id
|
21 | 22 | import GHC.Types.Name
|
22 | 23 | import GHC.Types.TyThing
|
... | ... | @@ -46,6 +47,9 @@ data SingleStep |
46 | 47 | -- | :step [expr]
|
47 | 48 | | SingleStep
|
48 | 49 | |
50 | + -- | :stepout [expr]
|
|
51 | + | StepOut
|
|
52 | + |
|
49 | 53 | -- | :steplocal [expr]
|
50 | 54 | | LocalStep
|
51 | 55 | { breakAt :: SrcSpan }
|
... | ... | @@ -55,10 +59,12 @@ data SingleStep |
55 | 59 | { breakAt :: SrcSpan }
|
56 | 60 | |
57 | 61 | -- | Whether this 'SingleStep' mode requires instructing the interpreter to
|
58 | --- step at every breakpoint.
|
|
59 | -enableGhcStepMode :: SingleStep -> Bool
|
|
60 | -enableGhcStepMode RunToCompletion = False
|
|
61 | -enableGhcStepMode _ = True
|
|
62 | +-- step at every breakpoint or after every return (see @'EvalStep'@).
|
|
63 | +enableGhcStepMode :: SingleStep -> EvalStep
|
|
64 | +enableGhcStepMode RunToCompletion = EvalStepNone
|
|
65 | +enableGhcStepMode StepOut = EvalStepOut
|
|
66 | +-- for the remaining step modes we need to stop at every single breakpoint.
|
|
67 | +enableGhcStepMode _ = EvalStepSingle
|
|
62 | 68 | |
63 | 69 | -- | Given a 'SingleStep' mode and the SrcSpan of a breakpoint we hit, return
|
64 | 70 | -- @True@ if based on the step-mode alone we should stop at this breakpoint.
|
... | ... | @@ -70,6 +76,7 @@ breakHere :: SingleStep -> SrcSpan -> Bool |
70 | 76 | breakHere step break_span = case step of
|
71 | 77 | RunToCompletion -> False
|
72 | 78 | RunAndLogSteps -> False
|
79 | + StepOut -> True -- hmm. this function may not be a good abstraction?
|
|
73 | 80 | SingleStep -> True
|
74 | 81 | LocalStep span -> break_span `isSubspanOf` span
|
75 | 82 | ModuleStep span -> srcSpanFileName_maybe span == srcSpanFileName_maybe break_span
|
... | ... | @@ -2980,6 +2980,35 @@ commonly used commands. |
2980 | 2980 | hit by an error (:ghc-flag:`-fbreak-on-error`) or an
|
2981 | 2981 | exception (:ghc-flag:`-fbreak-on-exception`).
|
2982 | 2982 | |
2983 | +.. ghci-cmd:: :stepout
|
|
2984 | + |
|
2985 | + Stop at the first breakpoint immediately after returning from the current
|
|
2986 | + function scope.
|
|
2987 | + |
|
2988 | + Known limitations: because a function tail-call does not push a stack
|
|
2989 | + frame, if step-out is used inside of a function that was tail-called,
|
|
2990 | + execution will not be returned to its caller, but rather its caller's
|
|
2991 | + first non-tail caller. On the other hand, it means the debugger
|
|
2992 | + follows the more realistic execution of the program.
|
|
2993 | + In the following example:
|
|
2994 | + |
|
2995 | + .. code-block:: none
|
|
2996 | + |
|
2997 | + f = do
|
|
2998 | + a
|
|
2999 | + b <--- (1) set breakpoint then step in here
|
|
3000 | + c
|
|
3001 | + b = do
|
|
3002 | + ...
|
|
3003 | + d <--- (2) step-into this tail call
|
|
3004 | + d = do
|
|
3005 | + ...
|
|
3006 | + something <--- (3) step-out here
|
|
3007 | + ...
|
|
3008 | + |
|
3009 | + Stepping-out will stop execution at the `c` invokation in `f`, rather than
|
|
3010 | + stopping at `b`.
|
|
3011 | + |
|
2983 | 3012 | .. ghci-cmd:: :stepmodule
|
2984 | 3013 | |
2985 | 3014 | Enable only breakpoints in the current module and resume evaluation
|
... | ... | @@ -247,6 +247,7 @@ ghciCommands = map mkCmd [ |
247 | 247 | ("sprint", keepGoing sprintCmd, completeExpression),
|
248 | 248 | ("step", keepGoing stepCmd, completeIdentifier),
|
249 | 249 | ("steplocal", keepGoing stepLocalCmd, completeIdentifier),
|
250 | + ("stepout", keepGoing stepOutCmd, completeIdentifier),
|
|
250 | 251 | ("stepmodule",keepGoing stepModuleCmd, completeIdentifier),
|
251 | 252 | ("type", keepGoingMulti' typeOfExpr, completeExpression),
|
252 | 253 | ("trace", keepGoing traceCmd, completeExpression),
|
... | ... | @@ -407,6 +408,7 @@ defFullHelpText = |
407 | 408 | " :step single-step after stopping at a breakpoint\n"++
|
408 | 409 | " :step <expr> single-step into <expr>\n"++
|
409 | 410 | " :steplocal single-step within the current top-level binding\n"++
|
411 | + " :stepout stop at the first breakpoint after returning from the current scope\n"++
|
|
410 | 412 | " :stepmodule single-step restricted to the current module\n"++
|
411 | 413 | " :trace trace after stopping at a breakpoint\n"++
|
412 | 414 | " :trace <expr> evaluate <expr> with tracing on (see :history)\n"++
|
... | ... | @@ -3793,6 +3795,12 @@ stepCmd arg = withSandboxOnly ":step" $ step arg |
3793 | 3795 | step [] = doContinue GHC.SingleStep
|
3794 | 3796 | step expression = runStmt expression GHC.SingleStep >> return ()
|
3795 | 3797 | |
3798 | +stepOutCmd :: GhciMonad m => String -> m ()
|
|
3799 | +stepOutCmd arg = withSandboxOnly ":stepout" $ step arg
|
|
3800 | + where
|
|
3801 | + step [] = doContinue GHC.StepOut
|
|
3802 | + step expression = stepCmd expression
|
|
3803 | + |
|
3796 | 3804 | stepLocalCmd :: GhciMonad m => String -> m ()
|
3797 | 3805 | stepLocalCmd arg = withSandboxOnly ":steplocal" $ step arg
|
3798 | 3806 | where
|
... | ... | @@ -374,6 +374,7 @@ putTHMessage m = case m of |
374 | 374 | data EvalOpts = EvalOpts
|
375 | 375 | { useSandboxThread :: Bool
|
376 | 376 | , singleStep :: Bool
|
377 | + , stepOut :: Bool
|
|
377 | 378 | , breakOnException :: Bool
|
378 | 379 | , breakOnError :: Bool
|
379 | 380 | }
|
... | ... | @@ -210,6 +210,7 @@ evalOptsSeq :: EvalOpts |
210 | 210 | evalOptsSeq = EvalOpts
|
211 | 211 | { useSandboxThread = True
|
212 | 212 | , singleStep = False
|
213 | + , stepOut = False
|
|
213 | 214 | , breakOnException = False
|
214 | 215 | , breakOnError = False
|
215 | 216 | }
|
... | ... | @@ -333,6 +334,7 @@ withBreakAction opts breakMVar statusMVar act |
333 | 334 | poke breakPointIOAction stablePtr
|
334 | 335 | when (breakOnException opts) $ poke exceptionFlag 1
|
335 | 336 | when (singleStep opts) $ setStepFlag
|
337 | + when (stepOut opts) $ poke stepOutFlag 1
|
|
336 | 338 | return stablePtr
|
337 | 339 | -- Breaking on exceptions is not enabled by default, since it
|
338 | 340 | -- might be a bit surprising. The exception flag is turned off
|
... | ... | @@ -363,6 +365,7 @@ withBreakAction opts breakMVar statusMVar act |
363 | 365 | resetBreakAction stablePtr = do
|
364 | 366 | poke breakPointIOAction noBreakStablePtr
|
365 | 367 | poke exceptionFlag 0
|
368 | + poke stepOutFlag 0
|
|
366 | 369 | resetStepFlag
|
367 | 370 | freeStablePtr stablePtr
|
368 | 371 | |
... | ... | @@ -398,6 +401,7 @@ abandonStmt hvref = do |
398 | 401 | |
399 | 402 | foreign import ccall "&rts_stop_next_breakpoint" stepFlag :: Ptr CInt
|
400 | 403 | foreign import ccall "&rts_stop_on_exception" exceptionFlag :: Ptr CInt
|
404 | +foreign import ccall "&rts_stop_after_return" stepOutFlag :: Ptr CInt
|
|
401 | 405 | |
402 | 406 | setStepFlag :: IO ()
|
403 | 407 | setStepFlag = poke stepFlag 1
|
... | ... | @@ -194,6 +194,24 @@ See also Note [Width of parameters] for some more motivation. |
194 | 194 | #define WITHIN_CHUNK_BOUNDS(n, s) \
|
195 | 195 | (RTS_LIKELY((StgWord*)(Sp_plusW(n)) < ((s)->stack + (s)->stack_size - sizeofW(StgUnderflowFrame))))
|
196 | 196 | |
197 | +// Note [Debugger Step-out]
|
|
198 | +// ~~~~~~~~~~~~~~~~~~~~~~~~
|
|
199 | +// When the global debugger step-out flag is set (`rts_stop_after_return`),
|
|
200 | +// the interpreter must yield execution right after the first RETURN.
|
|
201 | +//
|
|
202 | +// When stepping-out, we simply enable `rts_stop_next_breakpoint` when we hit a
|
|
203 | +// return instruction (in `do_return_pointer` and `do_return_nonpointer`).
|
|
204 | +// The step-out flag is cleared and must be re-enabled explicitly to step-out again.
|
|
205 | +//
|
|
206 | +// A limitation of this approach is that stepping-out of a function that was
|
|
207 | +// tail-called will skip its caller since no stack frame is pushed for a tail
|
|
208 | +// call (i.e. a tail call returns directly to its caller's first non-tail caller).
|
|
209 | +#define CHECK_BRK_AFTER_RET() \
|
|
210 | + if (rts_stop_after_return) \
|
|
211 | + { \
|
|
212 | + rts_stop_next_breakpoint = true; \
|
|
213 | + rts_stop_after_return = false; \
|
|
214 | + } \
|
|
197 | 215 | |
198 | 216 | /* Note [PUSH_L underflow]
|
199 | 217 | ~~~~~~~~~~~~~~~~~~~~~~~
|
... | ... | @@ -245,6 +263,7 @@ allocate_NONUPD (Capability *cap, int n_words) |
245 | 263 | |
246 | 264 | int rts_stop_next_breakpoint = 0;
|
247 | 265 | int rts_stop_on_exception = 0;
|
266 | +int rts_stop_after_return = 0;
|
|
248 | 267 | |
249 | 268 | #if defined(INTERP_STATS)
|
250 | 269 | |
... | ... | @@ -734,6 +753,8 @@ do_return_pointer: |
734 | 753 | |
735 | 754 | IF_DEBUG(sanity,checkStackChunk(Sp, cap->r.rCurrentTSO->stackobj->stack+cap->r.rCurrentTSO->stackobj->stack_size));
|
736 | 755 | |
756 | + CHECK_BRK_AFTER_RET();
|
|
757 | + |
|
737 | 758 | switch (get_itbl((StgClosure *)Sp)->type) {
|
738 | 759 | |
739 | 760 | case RET_SMALL: {
|
... | ... | @@ -883,6 +904,8 @@ do_return_nonpointer: |
883 | 904 | // get the offset of the header of the next stack frame
|
884 | 905 | offset = stack_frame_sizeW((StgClosure *)Sp);
|
885 | 906 | |
907 | + CHECK_BRK_AFTER_RET();
|
|
908 | + |
|
886 | 909 | switch (get_itbl((StgClosure*)(Sp_plusW(offset)))->type) {
|
887 | 910 | |
888 | 911 | case RET_BCO:
|
... | ... | @@ -908,6 +908,7 @@ extern char **environ; |
908 | 908 | SymI_NeedsDataProto(rts_breakpoint_io_action) \
|
909 | 909 | SymI_NeedsDataProto(rts_stop_next_breakpoint) \
|
910 | 910 | SymI_NeedsDataProto(rts_stop_on_exception) \
|
911 | + SymI_NeedsDataProto(rts_stop_after_return) \
|
|
911 | 912 | SymI_HasProto(stopTimer) \
|
912 | 913 | SymI_HasProto(n_capabilities) \
|
913 | 914 | SymI_HasProto(max_n_capabilities) \
|
... | ... | @@ -619,6 +619,7 @@ RTS_FUN_DECL(stg_castFloatToWord32zh); |
619 | 619 | // Interpreter.c
|
620 | 620 | extern StgWord rts_stop_next_breakpoint[];
|
621 | 621 | extern StgWord rts_stop_on_exception[];
|
622 | +extern StgWord rts_stop_after_return[];
|
|
622 | 623 | extern StgWord rts_breakpoint_io_action[];
|
623 | 624 | |
624 | 625 | // Schedule.c
|
1 | +module Main where
|
|
2 | + |
|
3 | +main :: IO ()
|
|
4 | +main = do
|
|
5 | + a <- foo
|
|
6 | + print a
|
|
7 | + |
|
8 | +foo :: IO Int
|
|
9 | +foo = do
|
|
10 | + let x = 3
|
|
11 | + y = 4
|
|
12 | + b <- bar (x + y)
|
|
13 | + return b
|
|
14 | + |
|
15 | +bar :: Int -> IO Int
|
|
16 | +bar z = return (z * 2)
|
|
17 | + |
1 | +:load T26042a.hs
|
|
2 | +-- simple use of stepout
|
|
3 | +:break bar
|
|
4 | +main
|
|
5 | +:list
|
|
6 | +:stepout
|
|
7 | +:list
|
|
8 | +:stepout
|
|
9 | +:list
|
|
10 | +-- from here on we're going to evaluate the thunks for `a` in `print a`
|
|
11 | +:stepout
|
|
12 | +:list
|
|
13 | +:stepout
|
|
14 | +:list
|
|
15 | +:stepout
|
|
16 | +:list
|
|
17 | +:stepout
|
|
18 | +:list
|
|
19 | +-- finish execution
|
|
20 | +:stepout |
1 | +Breakpoint 0 activated at T26042a.hs:16:9-22
|
|
2 | +Stopped in Main.bar, T26042a.hs:16:9-22
|
|
3 | +_result :: IO Int = _
|
|
4 | +z :: Int = _
|
|
5 | +15 bar :: Int -> IO Int
|
|
6 | +16 bar z = return (z * 2)
|
|
7 | + ^^^^^^^^^^^^^^
|
|
8 | +17
|
|
9 | +Stopped in Main.foo, T26042a.hs:13:3-10
|
|
10 | +_result :: IO Int = _
|
|
11 | +b :: Int = _
|
|
12 | +12 b <- bar (x + y)
|
|
13 | +13 return b
|
|
14 | + ^^^^^^^^
|
|
15 | +14
|
|
16 | +Stopped in Main.main, T26042a.hs:6:3-9
|
|
17 | +_result :: IO () = _
|
|
18 | +a :: Int = _
|
|
19 | +5 a <- foo
|
|
20 | +6 print a
|
|
21 | + ^^^^^^^
|
|
22 | +7
|
|
23 | +Stopped in Main.bar, T26042a.hs:16:17-21
|
|
24 | +_result :: Int = _
|
|
25 | +z :: Int = _
|
|
26 | +15 bar :: Int -> IO Int
|
|
27 | +16 bar z = return (z * 2)
|
|
28 | + ^^^^^
|
|
29 | +17
|
|
30 | +Stopped in Main.foo, T26042a.hs:12:13-17
|
|
31 | +_result :: Int = _
|
|
32 | +x :: Int = _
|
|
33 | +y :: Int = _
|
|
34 | +11 y = 4
|
|
35 | +12 b <- bar (x + y)
|
|
36 | + ^^^^^
|
|
37 | +13 return b
|
|
38 | +Stopped in Main.foo.x, T26042a.hs:10:11
|
|
39 | +_result :: Int = _
|
|
40 | +9 foo = do
|
|
41 | +10 let x = 3
|
|
42 | + ^
|
|
43 | +11 y = 4
|
|
44 | +Stopped in Main.foo.y, T26042a.hs:11:11
|
|
45 | +_result :: Int = _
|
|
46 | +10 let x = 3
|
|
47 | +11 y = 4
|
|
48 | + ^
|
|
49 | +12 b <- bar (x + y)
|
|
50 | +14 |
1 | +module Main where
|
|
2 | + |
|
3 | +main :: IO ()
|
|
4 | +main = do
|
|
5 | + a <- foo False undefined
|
|
6 | + print a
|
|
7 | + |
|
8 | +foo :: Bool -> Int -> IO Int
|
|
9 | +foo True i = return i
|
|
10 | +foo False _ = do
|
|
11 | + let x = 3
|
|
12 | + y = 4
|
|
13 | + n <- bar (x + y)
|
|
14 | + return n
|
|
15 | + |
|
16 | +bar :: Int -> IO Int
|
|
17 | +bar z = do
|
|
18 | + let t = z * 2
|
|
19 | + y <- foo True t
|
|
20 | + return y
|
|
21 | + |
|
22 | + |
1 | +:load T26042b.hs
|
|
2 | +-- break on the True branch of foo
|
|
3 | +:break 9
|
|
4 | +main
|
|
5 | +:list
|
|
6 | +-- stepout of foo True to caller (ie bar)
|
|
7 | +:stepout
|
|
8 | +:list
|
|
9 | +-- stepout of bar (to branch of foo False, where bar was called)
|
|
10 | +:stepout
|
|
11 | +:list
|
|
12 | +-- stepout to right after the call to foo False in main
|
|
13 | +:stepout
|
|
14 | +:list
|
|
15 | +-- done
|
|
16 | +:continue |
1 | +Breakpoint 0 activated at T26042b.hs:9:15-22
|
|
2 | +Stopped in Main.foo, T26042b.hs:9:15-22
|
|
3 | +_result :: IO Int = _
|
|
4 | +i :: Int = _
|
|
5 | +8 foo :: Bool -> Int -> IO Int
|
|
6 | +9 foo True i = return i
|
|
7 | + ^^^^^^^^
|
|
8 | +10 foo False _ = do
|
|
9 | +Stopped in Main.bar, T26042b.hs:20:3-10
|
|
10 | +_result :: IO Int = _
|
|
11 | +y :: Int = _
|
|
12 | +19 y <- foo True t
|
|
13 | +20 return y
|
|
14 | + ^^^^^^^^
|
|
15 | +21
|
|
16 | +Stopped in Main.foo, T26042b.hs:14:3-10
|
|
17 | +_result :: IO Int = _
|
|
18 | +n :: Int = _
|
|
19 | +13 n <- bar (x + y)
|
|
20 | +14 return n
|
|
21 | + ^^^^^^^^
|
|
22 | +15
|
|
23 | +Stopped in Main.main, T26042b.hs:6:3-9
|
|
24 | +_result :: IO () = _
|
|
25 | +a :: Int = _
|
|
26 | +5 a <- foo False undefined
|
|
27 | +6 print a
|
|
28 | + ^^^^^^^
|
|
29 | +7
|
|
30 | +14 |
1 | +module Main where
|
|
2 | + |
|
3 | +main :: IO ()
|
|
4 | +main = do
|
|
5 | + a <- foo False undefined
|
|
6 | + print a
|
|
7 | + |
|
8 | +foo :: Bool -> Int -> IO Int
|
|
9 | +foo True i = return i
|
|
10 | +foo False _ = do
|
|
11 | + let x = 3
|
|
12 | + y = 4
|
|
13 | + bar (x + y)
|
|
14 | + |
|
15 | +bar :: Int -> IO Int
|
|
16 | +bar z = do
|
|
17 | + let t = z * 2
|
|
18 | + foo True t
|
|
19 | + |
1 | +:load T26042c.hs
|
|
2 | +-- similar to T26042b, but uses tail calls
|
|
3 | +-- recall: for step-out, we skip the caller of tail calls
|
|
4 | +-- (because we don't push a stack frame for tail calls, so
|
|
5 | +-- there's no RET instruction to stop after)
|
|
6 | + |
|
7 | +-- break on foo True branch
|
|
8 | +:break 9
|
|
9 | +main
|
|
10 | +:list
|
|
11 | +-- step out of foo True and observe that we have skipped its call in bar,
|
|
12 | +-- and the call of bar in foo False.
|
|
13 | +-- we go straight to `main`.
|
|
14 | +:stepout
|
|
15 | +:list
|
|
16 | +-- stepping out from here will jump into the thunk because it's where we'll
|
|
17 | +-- go after returning.
|
|
18 | +:stepout
|
|
19 | +:list
|
|
20 | +-- and so on
|
|
21 | +:stepout
|
|
22 | +:list
|
|
23 | +-- finish
|
|
24 | +:continue |
1 | +Breakpoint 0 activated at T26042c.hs:9:15-22
|
|
2 | +Stopped in Main.foo, T26042c.hs:9:15-22
|
|
3 | +_result :: IO Int = _
|
|
4 | +i :: Int = _
|
|
5 | +8 foo :: Bool -> Int -> IO Int
|
|
6 | +9 foo True i = return i
|
|
7 | + ^^^^^^^^
|
|
8 | +10 foo False _ = do
|
|
9 | +Stopped in Main.main, T26042c.hs:6:3-9
|
|
10 | +_result :: IO () = _
|
|
11 | +a :: Int = _
|
|
12 | +5 a <- foo False undefined
|
|
13 | +6 print a
|
|
14 | + ^^^^^^^
|
|
15 | +7
|
|
16 | +Stopped in Main.bar.t, T26042c.hs:17:11-15
|
|
17 | +_result :: Int = _
|
|
18 | +z :: Int = _
|
|
19 | +16 bar z = do
|
|
20 | +17 let t = z * 2
|
|
21 | + ^^^^^
|
|
22 | +18 foo True t
|
|
23 | +Stopped in Main.foo, T26042c.hs:13:8-12
|
|
24 | +_result :: Int = _
|
|
25 | +x :: Int = _
|
|
26 | +y :: Int = _
|
|
27 | +12 y = 4
|
|
28 | +13 bar (x + y)
|
|
29 | + ^^^^^
|
|
30 | +14
|
|
31 | +14 |
... | ... | @@ -144,3 +144,6 @@ test('T24306', normal, ghci_script, ['T24306.script']) |
144 | 144 | test('T24712', normal, ghci_script, ['T24712.script'])
|
145 | 145 | test('T25109', normal, ghci_script, ['T25109.script'])
|
146 | 146 | test('T25932', extra_files(['T25932.hs']), ghci_script, ['T25932.script'])
|
147 | +test('T26042a', extra_files(['T26042a.hs']), ghci_script, ['T26042a.script'])
|
|
148 | +test('T26042b', extra_files(['T26042b.hs']), ghci_script, ['T26042b.script'])
|
|
149 | +test('T26042c', extra_files(['T26042c.hs']), ghci_script, ['T26042c.script']) |