diff --git a/compiler/GHC.hs b/compiler/GHC.hs index aea9c46227d9163ab3f54b4531300cdf40d0c9cd..7b67c0546346ec8c17fd3d83f1493e0de6547298 100644 --- a/compiler/GHC.hs +++ b/compiler/GHC.hs @@ -157,14 +157,14 @@ module GHC ( -- ** The debugger SingleStep(..), Resume(..), - History(historyBreakInfo, historyEnclosingDecls), + History(historyBreakpointId, historyEnclosingDecls), GHC.getHistorySpan, getHistoryModule, abandon, abandonAll, getResumeContext, GHC.obtainTermFromId, GHC.obtainTermFromVal, reconstructType, modInfoModBreaks, ModBreaks(..), BreakIndex, - BreakInfo(..), + BreakpointId(..), InternalBreakpointId(..), GHC.Runtime.Eval.back, GHC.Runtime.Eval.forward, GHC.Runtime.Eval.setupBreakpoint, @@ -392,7 +392,7 @@ import GHC.Types.TyThing import GHC.Types.Name.Env import GHC.Types.Name.Ppr import GHC.Types.TypeEnv -import GHC.Types.BreakInfo +import GHC.Types.Breakpoint import GHC.Types.PkgQual import GHC.Types.Unique.FM diff --git a/compiler/GHC/ByteCode/Asm.hs b/compiler/GHC/ByteCode/Asm.hs index 66c88541e37e222204e1840b78fceca7301b77fa..691766b8bce46841188468d9747ff9546f14ac2c 100644 --- a/compiler/GHC/ByteCode/Asm.hs +++ b/compiler/GHC/ByteCode/Asm.hs @@ -514,11 +514,16 @@ assembleI platform i = case i of CCALL off m_addr i -> do np <- addr m_addr emit bci_CCALL [wOp off, Op np, SmallOp i] PRIMCALL -> emit bci_PRIMCALL [] - BRK_FUN arr index mod cc -> do p1 <- ptr (BCOPtrBreakArray arr) - m <- addr mod + BRK_FUN arr tick_mod tickx info_mod infox cc -> + do p1 <- ptr (BCOPtrBreakArray arr) + tick_addr <- addr tick_mod + info_addr <- addr info_mod np <- addr cc - emit bci_BRK_FUN [Op p1, SmallOp index, - Op m, Op np] + emit bci_BRK_FUN [ Op p1 + , Op tick_addr, Op info_addr + , SmallOp tickx, SmallOp infox + , Op np + ] where literal (LitLabel fs (Just sz) _) diff --git a/compiler/GHC/ByteCode/Instr.hs b/compiler/GHC/ByteCode/Instr.hs index f3c328c47eba78f79acf1eefd4d9330dbb0e792d..3855c0314b3e58f35209d8a83a01afce07597457 100644 --- a/compiler/GHC/ByteCode/Instr.hs +++ b/compiler/GHC/ByteCode/Instr.hs @@ -206,7 +206,11 @@ data BCInstr -- Note [unboxed tuple bytecodes and tuple_BCO] in GHC.StgToByteCode -- Breakpoints - | BRK_FUN (ForeignRef BreakArray) !Word16 (RemotePtr ModuleName) + | BRK_FUN (ForeignRef BreakArray) + (RemotePtr ModuleName) -- breakpoint tick module + !Word16 -- breakpoint tick index + (RemotePtr ModuleName) -- breakpoint info module + !Word16 -- breakpoint info index (RemotePtr CostCentre) -- ----------------------------------------------------------------------------- @@ -358,8 +362,11 @@ instance Outputable BCInstr where ppr ENTER = text "ENTER" ppr (RETURN pk) = text "RETURN " <+> ppr pk ppr (RETURN_TUPLE) = text "RETURN_TUPLE" - ppr (BRK_FUN _ index _ _) = text "BRK_FUN" <+> text "<breakarray>" - <+> ppr index <+> text "<module>" <+> text "<cc>" + ppr (BRK_FUN _ _tick_mod tickx _info_mod infox _) + = text "BRK_FUN" <+> text "<breakarray>" + <+> text "<tick_module>" <+> ppr tickx + <+> text "<info_module>" <+> ppr infox + <+> text "<cc>" diff --git a/compiler/GHC/Runtime/Eval.hs b/compiler/GHC/Runtime/Eval.hs index f6917598d7a70077981d78f95ff1a25ed3bf5ab6..9ec57121c7902cf08b4f77d7c3a46a81a27fcbda 100644 --- a/compiler/GHC/Runtime/Eval.hs +++ b/compiler/GHC/Runtime/Eval.hs @@ -107,7 +107,7 @@ import GHC.Types.Unique import GHC.Types.Unique.Supply import GHC.Types.Unique.DSet import GHC.Types.TyThing -import GHC.Types.BreakInfo +import GHC.Types.Breakpoint import GHC.Types.Unique.Map import GHC.Unit @@ -143,29 +143,27 @@ import Unsafe.Coerce ( unsafeCoerce ) getResumeContext :: GhcMonad m => m [Resume] getResumeContext = withSession (return . ic_resume . hsc_IC) -mkHistory :: HscEnv -> ForeignHValue -> BreakInfo -> History -mkHistory hsc_env hval bi = History hval bi (findEnclosingDecls hsc_env bi) +mkHistory :: HscEnv -> ForeignHValue -> InternalBreakpointId -> History +mkHistory hsc_env hval ibi = History hval ibi (findEnclosingDecls hsc_env ibi) getHistoryModule :: History -> Module -getHistoryModule = breakInfo_module . historyBreakInfo +getHistoryModule = ibi_tick_mod . historyBreakpointId getHistorySpan :: HscEnv -> History -> SrcSpan -getHistorySpan hsc_env History{..} = - let BreakInfo{..} = historyBreakInfo in - case lookupHugByModule breakInfo_module (hsc_HUG hsc_env) of - Just hmi -> modBreaks_locs (getModBreaks hmi) ! breakInfo_number +getHistorySpan hsc_env hist = + let ibi = historyBreakpointId hist in + case lookupHugByModule (ibi_tick_mod ibi) (hsc_HUG hsc_env) of + Just hmi -> modBreaks_locs (getModBreaks hmi) ! ibi_tick_index ibi _ -> panic "getHistorySpan" {- | Finds the enclosing top level function name -} -- ToDo: a better way to do this would be to keep hold of the decl_path computed -- by the coverage pass, which gives the list of lexically-enclosing bindings -- for each tick. -findEnclosingDecls :: HscEnv -> BreakInfo -> [String] -findEnclosingDecls hsc_env (BreakInfo modl ix) = - let hmi = expectJust "findEnclosingDecls" $ - lookupHugByModule modl (hsc_HUG hsc_env) - mb = getModBreaks hmi - in modBreaks_decls mb ! ix +findEnclosingDecls :: HscEnv -> InternalBreakpointId -> [String] +findEnclosingDecls hsc_env ibi = + let hmi = expectJust "findEnclosingDecls" $ lookupHugByModule (ibi_tick_mod ibi) (hsc_HUG hsc_env) + in modBreaks_decls (getModBreaks hmi) ! ibi_tick_index ibi -- | Update fixity environment in the current interactive context. updateFixityEnv :: GhcMonad m => FixityEnv -> m () @@ -324,27 +322,24 @@ handleRunStatus step expr bindings final_ids status history | otherwise = not_tracing where tracing - | EvalBreak apStack_ref maybe_break resume_ctxt _ccs <- status - , Just (EvalBreakpoint ix mod_name) <- maybe_break + | EvalBreak apStack_ref (Just eval_break) resume_ctxt _ccs <- status = do hsc_env <- getSession let interp = hscInterp hsc_env let dflags = hsc_dflags hsc_env - let hmi = expectJust "handleRunStatus" $ - lookupHpt (hsc_HPT hsc_env) (mkModuleName mod_name) - modl = mi_module (hm_iface hmi) + let ibi = evalBreakpointToId (hsc_HPT hsc_env) eval_break + let hmi = expectJust "handleRunStatus" $ lookupHpt (hsc_HPT hsc_env) (moduleName (ibi_tick_mod ibi)) breaks = getModBreaks hmi b <- liftIO $ - breakpointStatus interp (modBreaks_flags breaks) ix + breakpointStatus interp (modBreaks_flags breaks) (ibi_tick_index ibi) if b then not_tracing -- This breakpoint is explicitly enabled; we want to stop -- instead of just logging it. else do apStack_fhv <- liftIO $ mkFinalizedHValue interp apStack_ref - let bi = BreakInfo modl ix - !history' = mkHistory hsc_env apStack_fhv bi `consBL` history + let !history' = mkHistory hsc_env apStack_fhv ibi `consBL` history -- history is strict, otherwise our BoundedList is pointless. fhv <- liftIO $ mkFinalizedHValue interp resume_ctxt let eval_opts = initEvalOpts dflags True @@ -362,23 +357,27 @@ handleRunStatus step expr bindings final_ids status history let interp = hscInterp hsc_env resume_ctxt_fhv <- liftIO $ mkFinalizedHValue interp resume_ctxt apStack_fhv <- liftIO $ mkFinalizedHValue interp apStack_ref - let bp = evalBreakInfo (hsc_HPT hsc_env) <$> maybe_break + let ibi = evalBreakpointToId (hsc_HPT hsc_env) <$> maybe_break (hsc_env1, names, span, decl) <- liftIO $ - bindLocalsAtBreakpoint hsc_env apStack_fhv bp + bindLocalsAtBreakpoint hsc_env apStack_fhv ibi let resume = Resume - { resumeStmt = expr, resumeContext = resume_ctxt_fhv - , resumeBindings = bindings, resumeFinalIds = final_ids + { resumeStmt = expr + , resumeContext = resume_ctxt_fhv + , resumeBindings = bindings + , resumeFinalIds = final_ids , resumeApStack = apStack_fhv - , resumeBreakInfo = bp - , resumeSpan = span, resumeHistory = toListBL history + , resumeBreakpointId = ibi + , resumeSpan = span + , resumeHistory = toListBL history , resumeDecl = decl , resumeCCS = ccs - , resumeHistoryIx = 0 } + , resumeHistoryIx = 0 + } hsc_env2 = pushResume hsc_env1 resume setSession hsc_env2 - return (ExecBreak names bp) + return (ExecBreak names ibi) -- Completed successfully | EvalComplete allocs (EvalSuccess hvals) <- status @@ -428,16 +427,21 @@ resumeExec canLogSpan step mbCnt liftIO $ Loader.deleteFromLoadedEnv interp new_names case r of - Resume { resumeStmt = expr, resumeContext = fhv - , resumeBindings = bindings, resumeFinalIds = final_ids - , resumeApStack = apStack, resumeBreakInfo = mb_brkpt + Resume { resumeStmt = expr + , resumeContext = fhv + , resumeBindings = bindings + , resumeFinalIds = final_ids + , resumeApStack = apStack + , resumeBreakpointId = mb_brkpt , resumeSpan = span , resumeHistory = hist } -> withVirtualCWD $ do - when (isJust mb_brkpt && isJust mbCnt) $ do - setupBreakpoint hsc_env (fromJust mb_brkpt) (fromJust mbCnt) - -- When the user specified a break ignore count, set it - -- in the interpreter + -- When the user specified a break ignore count, set it + -- in the interpreter + case (mb_brkpt, mbCnt) of + (Just brkpt, Just cnt) -> setupBreakpoint hsc_env (toBreakpointId brkpt) cnt + _ -> return () + let eval_opts = initEvalOpts dflags (isStep step) status <- liftIO $ GHCi.resumeStmt interp eval_opts fhv let prevHistoryLst = fromListBL 50 hist @@ -449,16 +453,15 @@ resumeExec canLogSpan step mbCnt fromListBL 50 hist handleRunStatus step expr bindings final_ids status hist' -setupBreakpoint :: GhcMonad m => HscEnv -> BreakInfo -> Int -> m () -- #19157 -setupBreakpoint hsc_env brkInfo cnt = do - let modl :: Module = breakInfo_module brkInfo +setupBreakpoint :: GhcMonad m => HscEnv -> BreakpointId -> Int -> m () -- #19157 +setupBreakpoint hsc_env bi cnt = do + let modl = bi_tick_mod bi breaks hsc_env modl = getModBreaks $ expectJust "setupBreakpoint" $ lookupHpt (hsc_HPT hsc_env) (moduleName modl) - ix = breakInfo_number brkInfo modBreaks = breaks hsc_env modl breakarray = modBreaks_flags modBreaks interp = hscInterp hsc_env - _ <- liftIO $ GHCi.storeBreakpoint interp breakarray ix cnt + _ <- liftIO $ GHCi.storeBreakpoint interp breakarray (bi_tick_index bi) cnt pure () back :: GhcMonad m => Int -> m ([Name], Int, SrcSpan, String) @@ -501,11 +504,11 @@ moveHist fn = do if new_ix == 0 then case r of Resume { resumeApStack = apStack, - resumeBreakInfo = mb_brkpt } -> + resumeBreakpointId = mb_brkpt } -> update_ic apStack mb_brkpt else case history !! (new_ix - 1) of History{..} -> - update_ic historyApStack (Just historyBreakInfo) + update_ic historyApStack (Just historyBreakpointId) -- ----------------------------------------------------------------------------- @@ -517,7 +520,7 @@ result_fs = fsLit "_result" bindLocalsAtBreakpoint :: HscEnv -> ForeignHValue - -> Maybe BreakInfo + -> Maybe InternalBreakpointId -> IO (HscEnv, [Name], SrcSpan, String) -- Nothing case: we stopped when an exception was raised, not at a @@ -543,25 +546,28 @@ bindLocalsAtBreakpoint hsc_env apStack Nothing = do -- Just case: we stopped at a breakpoint, we have information about the location -- of the breakpoint and the free variables of the expression. -bindLocalsAtBreakpoint hsc_env apStack_fhv (Just BreakInfo{..}) = do +bindLocalsAtBreakpoint hsc_env apStack_fhv (Just ibi) = do let - hmi = expectJust "bindLocalsAtBreakpoint" $ - lookupHpt (hsc_HPT hsc_env) (moduleName breakInfo_module) interp = hscInterp hsc_env - breaks = getModBreaks hmi - info = expectJust "bindLocalsAtBreakpoint2" $ - IntMap.lookup breakInfo_number (modBreaks_breakInfo breaks) - occs = modBreaks_vars breaks ! breakInfo_number - span = modBreaks_locs breaks ! breakInfo_number - decl = intercalate "." $ modBreaks_decls breaks ! breakInfo_number + + info_mod = ibi_info_mod ibi + info_hmi = expectJust "bindLocalsAtBreakpoint" $ lookupHpt (hsc_HPT hsc_env) (moduleName info_mod) + info_brks = getModBreaks info_hmi + info = expectJust "bindLocalsAtBreakpoint2" $ IntMap.lookup (ibi_info_index ibi) (modBreaks_breakInfo info_brks) + + tick_mod = ibi_tick_mod ibi + tick_hmi = expectJust "bindLocalsAtBreakpoint" $ lookupHpt (hsc_HPT hsc_env) (moduleName tick_mod) + tick_brks = getModBreaks tick_hmi + occs = modBreaks_vars tick_brks ! ibi_tick_index ibi + span = modBreaks_locs tick_brks ! ibi_tick_index ibi + decl = intercalate "." $ modBreaks_decls tick_brks ! ibi_tick_index ibi -- Rehydrate to understand the breakpoint info relative to the current environment. -- This design is critical to preventing leaks (#22530) (mbVars, result_ty) <- initIfaceLoad hsc_env - $ initIfaceLcl breakInfo_module (text "debugger") NotBoot + $ initIfaceLcl info_mod (text "debugger") NotBoot $ hydrateCgBreakInfo info - let -- Filter out any unboxed ids by changing them to Nothings; diff --git a/compiler/GHC/Runtime/Eval/Types.hs b/compiler/GHC/Runtime/Eval/Types.hs index 85fd1c803752403537b7cc4ceab5a224ebaacefa..e3e1e02134573aadab4631d70035d0cf3db8ae29 100644 --- a/compiler/GHC/Runtime/Eval/Types.hs +++ b/compiler/GHC/Runtime/Eval/Types.hs @@ -19,7 +19,7 @@ import GHCi.Message (EvalExpr, ResumeContext) import GHC.Types.Id import GHC.Types.Name import GHC.Types.TyThing -import GHC.Types.BreakInfo +import GHC.Types.Breakpoint import GHC.Types.Name.Reader import GHC.Types.SrcLoc import GHC.Utils.Exception @@ -50,8 +50,8 @@ data ExecResult , execAllocation :: Word64 } | ExecBreak - { breakNames :: [Name] - , breakInfo :: Maybe BreakInfo + { breakNames :: [Name] + , breakPointId :: Maybe InternalBreakpointId } -- | Essentially a GlobalRdrEnv, but with additional cached values to allow @@ -73,11 +73,10 @@ data Resume = Resume , resumeFinalIds :: [Id] -- [Id] to bind on completion , resumeApStack :: ForeignHValue -- The object from which we can get -- value of the free variables. - , resumeBreakInfo :: Maybe BreakInfo - -- the breakpoint we stopped at - -- (module, index) + , resumeBreakpointId :: Maybe InternalBreakpointId + -- ^ the breakpoint we stopped at -- (Nothing <=> exception) - , resumeSpan :: SrcSpan -- just a copy of the SrcSpan + , resumeSpan :: SrcSpan -- just a copy of the SrcSpan -- from the ModBreaks, -- otherwise it's a pain to -- fetch the ModDetails & @@ -90,9 +89,8 @@ data Resume = Resume type ResumeBindings = ([TyThing], IcGlobalRdrEnv) -data History - = History { - historyApStack :: ForeignHValue, - historyBreakInfo :: BreakInfo, - historyEnclosingDecls :: [String] -- declarations enclosing the breakpoint - } +data History = History + { historyApStack :: ForeignHValue + , historyBreakpointId :: InternalBreakpointId -- ^ breakpoint identifier + , historyEnclosingDecls :: [String] -- ^ declarations enclosing the breakpoint + } diff --git a/compiler/GHC/Runtime/Interpreter.hs b/compiler/GHC/Runtime/Interpreter.hs index 5102ce7d98cd1134206c133b9919c187ff9e522a..7004a050cd00d17834e1703bea767a2607bbe57c 100644 --- a/compiler/GHC/Runtime/Interpreter.hs +++ b/compiler/GHC/Runtime/Interpreter.hs @@ -1,5 +1,4 @@ {-# LANGUAGE CPP #-} -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE LambdaCase #-} -- | Interacting with the iserv interpreter, whether it is running on an @@ -28,7 +27,7 @@ module GHC.Runtime.Interpreter , getClosure , getModBreaks , seqHValue - , evalBreakInfo + , evalBreakpointToId , interpreterDynamic , interpreterProfiled @@ -74,7 +73,7 @@ import GHCi.Message import GHCi.RemoteTypes import GHCi.ResolvedBCO import GHCi.BreakArray (BreakArray) -import GHC.Types.BreakInfo (BreakInfo(..)) +import GHC.Types.Breakpoint import GHC.ByteCode.Types import GHC.Linker.Types @@ -395,14 +394,15 @@ seqHValue interp unit_env ref = status <- interpCmd interp (Seq hval) handleSeqHValueStatus interp unit_env status -evalBreakInfo :: HomePackageTable -> EvalBreakpoint -> BreakInfo -evalBreakInfo hpt (EvalBreakpoint ix mod_name) = - BreakInfo modl ix - where - modl = mi_module $ - hm_iface $ - expectJust "evalBreakInfo" $ - lookupHpt hpt (mkModuleName mod_name) +evalBreakpointToId :: HomePackageTable -> EvalBreakpoint -> InternalBreakpointId +evalBreakpointToId hpt eval_break = + let load_mod x = mi_module $ hm_iface $ expectJust "evalBreakpointToId" $ lookupHpt hpt (mkModuleName x) + in InternalBreakpointId + { ibi_tick_mod = load_mod (eb_tick_mod eval_break) + , ibi_tick_index = eb_tick_index eval_break + , ibi_info_mod = load_mod (eb_info_mod eval_break) + , ibi_info_index = eb_info_index eval_break + } -- | Process the result of a Seq or ResumeSeq message. #2950 handleSeqHValueStatus :: Interp -> UnitEnv -> EvalStatus () -> IO (EvalResult ()) @@ -412,7 +412,7 @@ handleSeqHValueStatus interp unit_env eval_status = -- A breakpoint was hit; inform the user and tell them -- which breakpoint was hit. resume_ctxt_fhv <- liftIO $ mkFinalizedHValue interp resume_ctxt - let bp = evalBreakInfo (ue_hpt unit_env) <$> maybe_break + let bp = evalBreakpointToId (ue_hpt unit_env) <$> maybe_break sdocBpLoc = brackets . ppr . getSeqBpSpan putStrLn ("*** Ignoring breakpoint " ++ (showSDocUnsafe $ sdocBpLoc bp)) @@ -422,14 +422,15 @@ handleSeqHValueStatus interp unit_env eval_status = handleSeqHValueStatus interp unit_env status (EvalComplete _ r) -> return r where - getSeqBpSpan :: Maybe BreakInfo -> SrcSpan - -- Just case: Stopped at a breakpoint, extract SrcSpan information - -- from the breakpoint. - getSeqBpSpan (Just BreakInfo{..}) = - (modBreaks_locs (breaks breakInfo_module)) ! breakInfo_number - -- Nothing case - should not occur! - -- Reason: Setting of flags in libraries/ghci/GHCi/Run.hs:evalOptsSeq - getSeqBpSpan Nothing = mkGeneralSrcSpan (fsLit "<unknown>") + getSeqBpSpan :: Maybe InternalBreakpointId -> SrcSpan + getSeqBpSpan = \case + Just bi -> (modBreaks_locs (breaks (ibi_tick_mod bi))) ! ibi_tick_index bi + -- Just case: Stopped at a breakpoint, extract SrcSpan information + -- from the breakpoint. + Nothing -> mkGeneralSrcSpan (fsLit "<unknown>") + -- Nothing case - should not occur! + -- Reason: Setting of flags in libraries/ghci/GHCi/Run.hs:evalOptsSeq + -- breaks mod = getModBreaks $ expectJust "getSeqBpSpan" $ lookupHpt (ue_hpt unit_env) (moduleName mod) diff --git a/compiler/GHC/StgToByteCode.hs b/compiler/GHC/StgToByteCode.hs index e143588de2cc7747e10889e1fc27924c889d274c..d76762877c108439b0cb734d2641e1067e0a4f6d 100644 --- a/compiler/GHC/StgToByteCode.hs +++ b/compiler/GHC/StgToByteCode.hs @@ -384,27 +384,40 @@ schemeR_wrk fvs nm original_body (args, body) -- | Introduce break instructions for ticked expressions. -- If no breakpoint information is available, the instruction is omitted. schemeER_wrk :: StackDepth -> BCEnv -> CgStgExpr -> BcM BCInstrList -schemeER_wrk d p (StgTick (Breakpoint tick_ty tick_no fvs mod) rhs) = do +schemeER_wrk d p (StgTick (Breakpoint tick_ty tick_no fvs tick_mod) rhs) = do code <- schemeE d 0 p rhs hsc_env <- getHscEnv current_mod <- getCurrentModule - current_mod_breaks <- getCurrentModBreaks - case break_info hsc_env mod current_mod current_mod_breaks of + mb_current_mod_breaks <- getCurrentModBreaks + case mb_current_mod_breaks of + -- if we're not generating ModBreaks for this module for some reason, we + -- can't store breakpoint occurrence information. Nothing -> pure code - Just ModBreaks {modBreaks_flags = breaks, modBreaks_module = mod_ptr, modBreaks_ccs = cc_arr} -> do - platform <- profilePlatform <$> getProfile - let idOffSets = getVarOffSets platform d p fvs - ty_vars = tyCoVarsOfTypesWellScoped (tick_ty:map idType fvs) - toWord :: Maybe (Id, WordOff) -> Maybe (Id, Word) - toWord = fmap (\(i, wo) -> (i, fromIntegral wo)) - breakInfo = dehydrateCgBreakInfo ty_vars (map toWord idOffSets) tick_ty - newBreakInfo tick_no breakInfo - let cc | Just interp <- hsc_interp hsc_env - , interpreterProfiled interp - = cc_arr ! tick_no - | otherwise = toRemotePtr nullPtr - breakInstr = BRK_FUN breaks (fromIntegral tick_no) mod_ptr cc - return $ breakInstr `consOL` code + Just current_mod_breaks -> case break_info hsc_env tick_mod current_mod mb_current_mod_breaks of + Nothing -> pure code + Just ModBreaks {modBreaks_flags = breaks, modBreaks_module = tick_mod_ptr, modBreaks_ccs = cc_arr} -> do + platform <- profilePlatform <$> getProfile + let idOffSets = getVarOffSets platform d p fvs + ty_vars = tyCoVarsOfTypesWellScoped (tick_ty:map idType fvs) + toWord :: Maybe (Id, WordOff) -> Maybe (Id, Word) + toWord = fmap (\(i, wo) -> (i, fromIntegral wo)) + breakInfo = dehydrateCgBreakInfo ty_vars (map toWord idOffSets) tick_ty + + let info_mod_ptr = modBreaks_module current_mod_breaks + infox <- newBreakInfo breakInfo + + let cc | Just interp <- hsc_interp hsc_env + , interpreterProfiled interp + = cc_arr ! tick_no + | otherwise = toRemotePtr nullPtr + + let -- cast that checks that round-tripping through Word16 doesn't change the value + toW16 x = let r = fromIntegral x :: Word16 + in if fromIntegral r == x + then r + else pprPanic "schemeER_wrk: breakpoint tick/info index too large!" (ppr x) + breakInstr = BRK_FUN breaks tick_mod_ptr (toW16 tick_no) info_mod_ptr (toW16 infox) cc + return $ breakInstr `consOL` code schemeER_wrk d p rhs = schemeE d 0 p rhs -- | Determine the GHCi-allocated 'BreakArray' and module pointer for the module @@ -2189,7 +2202,12 @@ data BcM_State , ffis :: [FFIInfo] -- ffi info blocks, to free later -- Should be free()d when it is GCd , modBreaks :: Maybe ModBreaks -- info about breakpoints - , breakInfo :: IntMap CgBreakInfo + + , breakInfo :: IntMap CgBreakInfo -- ^ Info at breakpoint occurrence. + -- Indexed with breakpoint *info* index. + -- See Note [Breakpoint identifiers] + -- in GHC.Types.Breakpoint + , breakInfoIdx :: !Int -- ^ Next index for breakInfo array } newtype BcM r = BcM (BcM_State -> IO (BcM_State, r)) deriving (Functor) @@ -2203,7 +2221,7 @@ runBc :: HscEnv -> Module -> Maybe ModBreaks -> BcM r -> IO (BcM_State, r) runBc hsc_env this_mod modBreaks (BcM m) - = m (BcM_State hsc_env this_mod 0 [] modBreaks IntMap.empty) + = m (BcM_State hsc_env this_mod 0 [] modBreaks IntMap.empty 0) thenBc :: BcM a -> (a -> BcM b) -> BcM b thenBc (BcM expr) cont = BcM $ \st0 -> do @@ -2259,9 +2277,14 @@ getLabelsBc n = BcM $ \st -> let ctr = nextlabel st in return (st{nextlabel = ctr+n}, coerce [ctr .. ctr+n-1]) -newBreakInfo :: BreakIndex -> CgBreakInfo -> BcM () -newBreakInfo ix info = BcM $ \st -> - return (st{breakInfo = IntMap.insert ix info (breakInfo st)}, ()) +newBreakInfo :: CgBreakInfo -> BcM Int +newBreakInfo info = BcM $ \st -> + let ix = breakInfoIdx st + st' = st + { breakInfo = IntMap.insert ix info (breakInfo st) + , breakInfoIdx = ix + 1 + } + in return (st', ix) getCurrentModule :: BcM Module getCurrentModule = BcM $ \st -> return (st, thisModule st) diff --git a/compiler/GHC/Types/BreakInfo.hs b/compiler/GHC/Types/BreakInfo.hs deleted file mode 100644 index d08ccda0bf4278d4c1114eff55abb86790d91287..0000000000000000000000000000000000000000 --- a/compiler/GHC/Types/BreakInfo.hs +++ /dev/null @@ -1,12 +0,0 @@ --- | A module for the BreakInfo type. Used by both the GHC.Runtime.Eval and --- GHC.Runtime.Interpreter hierarchy, so put here to have a less deep module --- dependency tree -module GHC.Types.BreakInfo (BreakInfo(..)) where - -import GHC.Prelude -import GHC.Unit.Module - -data BreakInfo = BreakInfo - { breakInfo_module :: Module - , breakInfo_number :: Int - } diff --git a/compiler/GHC/Types/Breakpoint.hs b/compiler/GHC/Types/Breakpoint.hs new file mode 100644 index 0000000000000000000000000000000000000000..9e56148f9c77e4d9f9e9ea93c1b864fb0d4d095d --- /dev/null +++ b/compiler/GHC/Types/Breakpoint.hs @@ -0,0 +1,53 @@ +-- | Breakpoint related types +module GHC.Types.Breakpoint + ( BreakpointId (..) + , InternalBreakpointId (..) + , toBreakpointId + ) +where + +import GHC.Prelude +import GHC.Unit.Module + +-- | Breakpoint identifier. +-- +-- See Note [Breakpoint identifiers] +data BreakpointId = BreakpointId + { bi_tick_mod :: !Module -- ^ Breakpoint tick module + , bi_tick_index :: !Int -- ^ Breakpoint tick index + } + +-- | Internal breakpoint identifier +-- +-- See Note [Breakpoint identifiers] +data InternalBreakpointId = InternalBreakpointId + { ibi_tick_mod :: !Module -- ^ Breakpoint tick module + , ibi_tick_index :: !Int -- ^ Breakpoint tick index + , ibi_info_mod :: !Module -- ^ Breakpoint info module + , ibi_info_index :: !Int -- ^ Breakpoint info index + } + +toBreakpointId :: InternalBreakpointId -> BreakpointId +toBreakpointId ibi = BreakpointId + { bi_tick_mod = ibi_tick_mod ibi + , bi_tick_index = ibi_tick_index ibi + } + + +-- Note [Breakpoint identifiers] +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-- +-- Before optimization a breakpoint is identified uniquely with a tick module +-- and a tick index. See BreakpointId. A tick module contains an array, indexed +-- with the tick indexes, which indicates breakpoint status. +-- +-- When we generate ByteCode, we collect information for every breakpoint at +-- their *occurrence sites* (see CgBreakInfo in GHC.ByteCode.Types) and these info +-- are stored in the ModIface of the occurrence module. Because of inlining, we +-- can't reuse the tick index to uniquely identify an occurrence; because of +-- cross-module inlining, we can't assume that the occurrence module is the same +-- as the tick module (#24712). +-- +-- So every breakpoint occurrence gets assigned a module-unique *info index* and +-- we store it alongside the occurrence module (*info module*) in the +-- InternalBreakpointId datatype. diff --git a/compiler/ghc.cabal.in b/compiler/ghc.cabal.in index 921dd1ebfc7d358db9240e5360048d37679e1ba7..a08e1266d5e5905160d9350e9334fd7b91c5527c 100644 --- a/compiler/ghc.cabal.in +++ b/compiler/ghc.cabal.in @@ -844,7 +844,7 @@ Library GHC.Types.Annotations GHC.Types.Avail GHC.Types.Basic - GHC.Types.BreakInfo + GHC.Types.Breakpoint GHC.Types.CompleteMatch GHC.Types.CostCentre GHC.Types.CostCentre.State diff --git a/ghc/GHCi/UI.hs b/ghc/GHCi/UI.hs index 2037170ee99c47ab9fe4f91bed014ee715158979..99c597f4676264fc28b9fd5a8b5364c7d9e3c296 100644 --- a/ghc/GHCi/UI.hs +++ b/ghc/GHCi/UI.hs @@ -72,6 +72,7 @@ import GHC.Core.TyCo.Ppr import GHC.Types.SafeHaskell ( getSafeMode ) import GHC.Types.SourceError ( SourceError ) import GHC.Types.Name +import GHC.Types.Breakpoint import GHC.Types.Var ( varType ) import GHC.Iface.Syntax ( showToHeader ) import GHC.Builtin.Names @@ -1408,15 +1409,13 @@ runAllocs m = do _ -> Nothing toBreakIdAndLocation :: GhciMonad m - => Maybe GHC.BreakInfo -> m (Maybe (Int, BreakLocation)) + => Maybe GHC.InternalBreakpointId -> m (Maybe (Int, BreakLocation)) toBreakIdAndLocation Nothing = return Nothing toBreakIdAndLocation (Just inf) = do - let md = GHC.breakInfo_module inf - nm = GHC.breakInfo_number inf st <- getGHCiState return $ listToMaybe [ id_loc | id_loc@(_,loc) <- IntMap.assocs (breaks st), - breakModule loc == md, - breakTick loc == nm ] + breakModule loc == ibi_tick_mod inf, + breakTick loc == ibi_tick_index inf ] printStoppedAtBreakInfo :: GHC.GhcMonad m => Resume -> [Name] -> m () printStoppedAtBreakInfo res names = do @@ -1544,15 +1543,11 @@ getCallStackAtCurrentBreakpoint = do getCurrentBreakModule :: GHC.GhcMonad m => m (Maybe Module) getCurrentBreakModule = do resumes <- GHC.getResumeContext - case resumes of - [] -> return Nothing - (r:_) -> do - let ix = GHC.resumeHistoryIx r - if ix == 0 - then return (GHC.breakInfo_module `liftM` GHC.resumeBreakInfo r) - else do - let hist = GHC.resumeHistory r !! (ix-1) - return $ Just $ GHC.getHistoryModule hist + return $ case resumes of + [] -> Nothing + (r:_) -> case GHC.resumeHistoryIx r of + 0 -> ibi_tick_mod <$> GHC.resumeBreakpointId r + ix -> Just $ GHC.getHistoryModule $ GHC.resumeHistory r !! (ix-1) ----------------------------------------------------------------------------- -- @@ -3474,7 +3469,7 @@ pprStopped res = <> text (GHC.resumeDecl res)) <> char ',' <+> ppr (GHC.resumeSpan res) where - mb_mod_name = moduleName <$> GHC.breakInfo_module <$> GHC.resumeBreakInfo res + mb_mod_name = moduleName <$> ibi_tick_mod <$> GHC.resumeBreakpointId res showUnits :: GHC.GhcMonad m => m () showUnits = do @@ -4035,8 +4030,11 @@ ignoreCmd argLine = withSandboxOnly ":ignore" $ do case result of Left sdoc -> printForUser sdoc Right (loc, count) -> do - let breakInfo = GHC.BreakInfo (breakModule loc) (breakTick loc) - setupBreakpoint breakInfo count + let bi = GHC.BreakpointId + { bi_tick_mod = breakModule loc + , bi_tick_index = breakTick loc + } + setupBreakpoint bi count ignoreSwitch :: GhciMonad m => [String] -> m (Either SDoc (BreakLocation, Int)) ignoreSwitch [break, count] = do @@ -4053,7 +4051,7 @@ getIgnoreCount str = where sdocIgnore = text "Ignore count" <+> quotes (text str) -setupBreakpoint :: GhciMonad m => GHC.BreakInfo -> Int -> m() +setupBreakpoint :: GhciMonad m => GHC.BreakpointId -> Int -> m() setupBreakpoint loc count = do hsc_env <- GHC.getSession GHC.setupBreakpoint hsc_env loc count @@ -4542,7 +4540,7 @@ setBreakFlag :: GhciMonad m => Module -> Int -> Bool ->m () setBreakFlag md ix enaDisa = do let enaDisaToCount True = breakOn enaDisaToCount False = breakOff - setupBreakpoint (GHC.BreakInfo md ix) $ enaDisaToCount enaDisa + setupBreakpoint (GHC.BreakpointId md ix) $ enaDisaToCount enaDisa -- --------------------------------------------------------------------------- -- User code exception handling diff --git a/libraries/ghci/GHCi/Message.hs b/libraries/ghci/GHCi/Message.hs index 8cf03d618f48898076a3c1723d124ab805416e8a..2660285660f42eae860858776271b6ecca30c750 100644 --- a/libraries/ghci/GHCi/Message.hs +++ b/libraries/ghci/GHCi/Message.hs @@ -398,10 +398,12 @@ data EvalStatus_ a b instance Binary a => Binary (EvalStatus_ a b) -data EvalBreakpoint = - EvalBreakpoint - Int -- ^ break index - String -- ^ ModuleName +data EvalBreakpoint = EvalBreakpoint + { eb_tick_mod :: String -- ^ Breakpoint tick module + , eb_tick_index :: Int -- ^ Breakpoint tick index + , eb_info_mod :: String -- ^ Breakpoint info module + , eb_info_index :: Int -- ^ Breakpoint info index + } deriving (Generic, Show) instance Binary EvalBreakpoint diff --git a/libraries/ghci/GHCi/Run.hs b/libraries/ghci/GHCi/Run.hs index 18fd5e991be2b11c0cef403372402504bb5b0870..26492af2d9eb3a05c91ef5ed3647a30f3584065f 100644 --- a/libraries/ghci/GHCi/Run.hs +++ b/libraries/ghci/GHCi/Run.hs @@ -329,7 +329,7 @@ withBreakAction opts breakMVar statusMVar act -- as soon as it is hit, or in resetBreakAction below. onBreak :: BreakpointCallback - onBreak ix# mod_name# is_exception apStack = do + onBreak tick_mod# tickx# info_mod# infox# is_exception apStack = do tid <- myThreadId let resume = ResumeContext { resumeBreakMVar = breakMVar @@ -342,8 +342,9 @@ withBreakAction opts breakMVar statusMVar act if is_exception then pure Nothing else do - mod_name <- peekCString (Ptr mod_name#) - pure (Just (EvalBreakpoint (I# ix#) mod_name)) + tick_mod <- peekCString (Ptr tick_mod#) + info_mod <- peekCString (Ptr info_mod#) + pure (Just (EvalBreakpoint tick_mod (I# tickx#) info_mod (I# infox#))) putMVar statusMVar $ EvalBreak apStack_r breakpoint resume_r ccs takeMVar breakMVar @@ -392,8 +393,10 @@ resetStepFlag :: IO () resetStepFlag = poke stepFlag 0 type BreakpointCallback - = Int# -- the breakpoint index - -> Addr# -- pointer to the module name + = Addr# -- pointer to the breakpoint tick module name + -> Int# -- breakpoint tick index + -> Addr# -- pointer to the breakpoint info module name + -> Int# -- breakpoint info index -> Bool -- exception? -> HValue -- the AP_STACK, or exception -> IO () @@ -405,8 +408,8 @@ noBreakStablePtr :: StablePtr BreakpointCallback noBreakStablePtr = unsafePerformIO $ newStablePtr noBreakAction noBreakAction :: BreakpointCallback -noBreakAction _ _ False _ = putStrLn "*** Ignoring breakpoint" -noBreakAction _ _ True _ = return () -- exception: just continue +noBreakAction _ _ _ _ False _ = putStrLn "*** Ignoring breakpoint" +noBreakAction _ _ _ _ True _ = return () -- exception: just continue -- Malloc and copy the bytes. We don't have any way to monitor the -- lifetime of this memory, so it just leaks. diff --git a/rts/Exception.cmm b/rts/Exception.cmm index 0d19c4e6b0ef8a5b76046bc0421699b3393213f0..9852320c2776dacbe63dfccf6fac3508ac42489c 100644 --- a/rts/Exception.cmm +++ b/rts/Exception.cmm @@ -535,15 +535,19 @@ retry_pop_stack: // be per-thread. CInt[rts_stop_on_exception] = 0; ("ptr" ioAction) = ccall deRefStablePtr (W_[rts_breakpoint_io_action] "ptr"); - Sp = Sp - WDS(9); - Sp(8) = exception; - Sp(7) = stg_raise_ret_info; - Sp(6) = exception; - Sp(5) = ghczmprim_GHCziTypes_True_closure; // True <=> an exception - Sp(4) = stg_ap_ppv_info; - Sp(3) = 0; - Sp(2) = stg_ap_n_info; - Sp(1) = 0; + Sp = Sp - WDS(13); + Sp(12) = exception; + Sp(11) = stg_raise_ret_info; + Sp(10) = exception; + Sp(9) = ghczmprim_GHCziTypes_True_closure; // True <=> an exception + Sp(8) = stg_ap_ppv_info; + Sp(7) = 0; + Sp(6) = stg_ap_n_info; + Sp(5) = 0; + Sp(4) = stg_ap_n_info; + Sp(3) = 0; + Sp(2) = stg_ap_n_info; + Sp(1) = 0; R1 = ioAction; jump RET_LBL(stg_ap_n) [R1]; } diff --git a/rts/Interpreter.c b/rts/Interpreter.c index fe1cdd24d247663d5808f6ba8eea41cf21754735..9b1dc5162189c2740166226798ce5f4d89648190 100644 --- a/rts/Interpreter.c +++ b/rts/Interpreter.c @@ -1089,9 +1089,9 @@ run_BCO: /* check for a breakpoint on the beginning of a let binding */ case bci_BRK_FUN: { - int arg1_brk_array, arg2_array_index, arg3_module_name; + int arg1_brk_array, arg2_tick_mod, arg3_info_mod, arg4_tick_index, arg5_info_index; #if defined(PROFILING) - int arg4_cc; + int arg6_cc; #endif StgArrBytes *breakPoints; int returning_from_break; @@ -1106,10 +1106,12 @@ run_BCO: int size_words; arg1_brk_array = BCO_GET_LARGE_ARG; - arg2_array_index = BCO_NEXT; - arg3_module_name = BCO_GET_LARGE_ARG; + arg2_tick_mod = BCO_GET_LARGE_ARG; + arg3_info_mod = BCO_GET_LARGE_ARG; + arg4_tick_index = BCO_NEXT; + arg5_info_index = BCO_NEXT; #if defined(PROFILING) - arg4_cc = BCO_GET_LARGE_ARG; + arg6_cc = BCO_GET_LARGE_ARG; #else BCO_GET_LARGE_ARG; #endif @@ -1122,7 +1124,7 @@ run_BCO: #if defined(PROFILING) cap->r.rCCCS = pushCostCentre(cap->r.rCCCS, - (CostCentre*)BCO_LIT(arg4_cc)); + (CostCentre*)BCO_LIT(arg6_cc)); #endif // if we are returning from a break then skip this section @@ -1134,11 +1136,11 @@ run_BCO: // stop the current thread if either the // "rts_stop_next_breakpoint" flag is true OR if the // ignore count for this particular breakpoint is zero - StgInt ignore_count = ((StgInt*)breakPoints->payload)[arg2_array_index]; + StgInt ignore_count = ((StgInt*)breakPoints->payload)[arg4_tick_index]; if (rts_stop_next_breakpoint == false && ignore_count > 0) { // decrement and write back ignore count - ((StgInt*)breakPoints->payload)[arg2_array_index] = --ignore_count; + ((StgInt*)breakPoints->payload)[arg4_tick_index] = --ignore_count; } else if (rts_stop_next_breakpoint == true || ignore_count == 0) { @@ -1171,8 +1173,10 @@ run_BCO: // Arrange the stack to call the breakpoint IO action, and // continue execution of this BCO when the IO action returns. // - // ioAction :: Int# -- the breakpoint index - // -> Addr# -- the breakpoint module + // ioAction :: Addr# -- the breakpoint tick module + // -> Int# -- the breakpoint tick index + // -> Addr# -- the breakpoint info module + // -> Int# -- the breakpoint info index // -> Bool -- exception? // -> HValue -- the AP_STACK, or exception // -> IO () @@ -1180,15 +1184,19 @@ run_BCO: ioAction = (StgClosure *) deRefStablePtr ( rts_breakpoint_io_action); - Sp_subW(11); - SpW(10) = (W_)obj; - SpW(9) = (W_)&stg_apply_interp_info; - SpW(8) = (W_)new_aps; - SpW(7) = (W_)False_closure; // True <=> an exception - SpW(6) = (W_)&stg_ap_ppv_info; - SpW(5) = (W_)BCO_LIT(arg3_module_name); + Sp_subW(15); + SpW(14) = (W_)obj; + SpW(13) = (W_)&stg_apply_interp_info; + SpW(12) = (W_)new_aps; + SpW(11) = (W_)False_closure; // True <=> an exception + SpW(10) = (W_)&stg_ap_ppv_info; + SpW(9) = (W_)arg5_info_index; + SpW(8) = (W_)&stg_ap_n_info; + SpW(7) = (W_)BCO_LIT(arg3_info_mod); + SpW(6) = (W_)&stg_ap_n_info; + SpW(5) = (W_)arg4_tick_index; SpW(4) = (W_)&stg_ap_n_info; - SpW(3) = (W_)arg2_array_index; + SpW(3) = (W_)BCO_LIT(arg2_tick_mod); SpW(2) = (W_)&stg_ap_n_info; SpW(1) = (W_)ioAction; SpW(0) = (W_)&stg_enter_info; diff --git a/testsuite/tests/ghci.debugger/scripts/T24712.hs b/testsuite/tests/ghci.debugger/scripts/T24712.hs new file mode 100644 index 0000000000000000000000000000000000000000..73e0649860ba1c47652408f32cb99827f52371ad --- /dev/null +++ b/testsuite/tests/ghci.debugger/scripts/T24712.hs @@ -0,0 +1,2 @@ +main = foo 123 +foo n = print n diff --git a/testsuite/tests/ghci.debugger/scripts/T24712.script b/testsuite/tests/ghci.debugger/scripts/T24712.script new file mode 100644 index 0000000000000000000000000000000000000000..b520ada9506da4e3a07ef6ac54b3f61ebe1a2f5d --- /dev/null +++ b/testsuite/tests/ghci.debugger/scripts/T24712.script @@ -0,0 +1,3 @@ +:l T24712.hs +:b foo +main diff --git a/testsuite/tests/ghci.debugger/scripts/T24712.stdout b/testsuite/tests/ghci.debugger/scripts/T24712.stdout new file mode 100644 index 0000000000000000000000000000000000000000..b7930ac47c4a098c178fe24485d98ea6432750a3 --- /dev/null +++ b/testsuite/tests/ghci.debugger/scripts/T24712.stdout @@ -0,0 +1,4 @@ +Breakpoint 0 activated at T24712.hs:2:9-15 +Stopped in Main.foo, T24712.hs:2:9-15 +_result :: IO () = _ +n :: Integer = 123 diff --git a/testsuite/tests/ghci.debugger/scripts/all.T b/testsuite/tests/ghci.debugger/scripts/all.T index 010129e1a7cbdfc33552d204aefc68e3d24f1599..87ad8e7823c4779c9c0b12ef95c52c612f6a9a09 100644 --- a/testsuite/tests/ghci.debugger/scripts/all.T +++ b/testsuite/tests/ghci.debugger/scripts/all.T @@ -141,3 +141,4 @@ test('break030', ) test('T23057', [only_ghci, extra_hc_opts('-fno-break-points')], ghci_script, ['T23057.script']) test('T24306', normal, ghci_script, ['T24306.script']) +test('T24712', normal, ghci_script, ['T24712.script']) diff --git a/testsuite/tests/ghci.debugger/scripts/break021.stdout b/testsuite/tests/ghci.debugger/scripts/break021.stdout index 199b3cdf05b9cabe5139638eb23fd060bc31af4b..bf64680b1a181bb45d4feecbc93fcf4af00b6439 100644 --- a/testsuite/tests/ghci.debugger/scripts/break021.stdout +++ b/testsuite/tests/ghci.debugger/scripts/break021.stdout @@ -17,7 +17,7 @@ _result :: IO () = _ ^^^^^^^ 11 line2 0 Stopped in Main.line1, break020.hs:3:11-19 -_result :: m () = _ +_result :: IO () = _ 2 3 line1 _ = return () ^^^^^^^^^ @@ -29,7 +29,7 @@ _result :: IO () = _ ^^^^^^^ 12 in_another_decl 0 Stopped in Main.line2, break020.hs:4:11-19 -_result :: m () = _ +_result :: IO () = _ 3 line1 _ = return () 4 line2 _ = return () ^^^^^^^^^ @@ -41,7 +41,7 @@ _result :: IO () = _ ^^^^^^^^^^^^^^^^^ 13 in_another_module 0 Stopped in Main.in_another_decl, break020.hs:(6,21)-(7,30) -_result :: m () = _ +_result :: IO () = _ 5 vv 6 in_another_decl _ = do line1 0 @@ -49,25 +49,25 @@ _result :: m () = _ ^^ 8 Stopped in Main.in_another_decl, break020.hs:6:24-30 -_result :: m () = _ +_result :: IO () = _ 5 6 in_another_decl _ = do line1 0 ^^^^^^^ 7 line2 0 Stopped in Main.line1, break020.hs:3:11-19 -_result :: m () = _ +_result :: IO () = _ 2 3 line1 _ = return () ^^^^^^^^^ 4 line2 _ = return () Stopped in Main.in_another_decl, break020.hs:7:24-30 -_result :: m () = _ +_result :: IO () = _ 6 in_another_decl _ = do line1 0 7 line2 0 ^^^^^^^ 8 Stopped in Main.line2, break020.hs:4:11-19 -_result :: m () = _ +_result :: IO () = _ 3 line1 _ = return () 4 line2 _ = return () ^^^^^^^^^ @@ -85,7 +85,7 @@ _result :: IO () = _ ^^^^^^^ 15 return () Stopped in Main.line2, break020.hs:4:11-19 -_result :: m () = _ +_result :: IO () = _ 3 line1 _ = return () 4 line2 _ = return () ^^^^^^^^^