1 -----------------------------------------------------------------------------
3 -- GHC Interactive support for inspecting arbitrary closures at runtime
5 -- Pepe Iborra (supported by Google SoC) 2006
7 -----------------------------------------------------------------------------
9 module RtClosureInspect(
11 cvObtainTerm, -- :: HscEnv -> Bool -> Maybe Type -> HValue -> IO Term
30 #include "HsVersions.h"
32 import ByteCodeItbls ( StgInfoTable )
33 import qualified ByteCodeItbls as BCI( StgInfoTable(..) )
34 import ByteCodeLink ( HValue )
35 import HscTypes ( HscEnv )
39 import TcRnMonad ( TcM, initTcPrintErrors, ioToTcRn, recoverM, writeMutVar )
50 import {-#SOURCE#-} TcRnDriver ( tcRnRecoverDataCon )
62 import GHC.Arr ( Array(..) )
63 import GHC.Ptr ( Ptr(..), castPtr )
68 import Data.Array.Base
69 import Data.List ( partition, nub )
72 ---------------------------------------------
73 -- * A representation of semi evaluated Terms
74 ---------------------------------------------
76 A few examples in this representation:
78 > Just 10 = Term Data.Maybe Data.Maybe.Just (Just 10) [Term Int I# (10) "10"]
80 > (('a',_,_),_,('b',_,_)) =
81 Term ((Char,b,c),d,(Char,e,f)) (,,) (('a',_,_),_,('b',_,_))
82 [ Term (Char, b, c) (,,) ('a',_,_) [Term Char C# "a", Suspension, Suspension]
84 , Term (Char, e, f) (,,) ('b',_,_) [Term Char C# "b", Suspension, Suspension]]
87 data Term = Term { ty :: Type
90 , subTerms :: [Term] }
95 | Suspension { ctype :: ClosureType
98 , bound_to :: Maybe Name -- Useful for printing
103 isSuspension Suspension{} = True
104 isSuspension _ = False
108 termType t@(Suspension {}) = mb_ty t
109 termType t = Just$ ty t
111 isFullyEvaluatedTerm :: Term -> Bool
112 isFullyEvaluatedTerm Term {subTerms=tt} = all isFullyEvaluatedTerm tt
113 isFullyEvaluatedTerm Suspension {} = False
114 isFullyEvaluatedTerm Prim {} = True
116 instance Outputable (Term) where
117 ppr = head . cPprTerm cPprTermBase
119 -------------------------------------------------------------------------
120 -- Runtime Closure Datatype and functions for retrieving closure related stuff
121 -------------------------------------------------------------------------
122 data ClosureType = Constr
133 data Closure = Closure { tipe :: ClosureType
135 , infoTable :: StgInfoTable
136 , ptrs :: Array Int HValue
140 instance Outputable ClosureType where
143 #include "../includes/ClosureTypes.h"
150 getClosureData :: a -> IO Closure
152 case unpackClosure# a of
153 (# iptr, ptrs, nptrs #) -> do
154 itbl <- peek (Ptr iptr)
155 let tipe = readCType (BCI.tipe itbl)
156 elems = BCI.ptrs itbl
157 ptrsList = Array 0 (fromIntegral$ elems) ptrs
158 nptrs_data = [W# (indexWordArray# nptrs i)
159 | I# i <- [0.. fromIntegral (BCI.nptrs itbl)] ]
160 ptrsList `seq` return (Closure tipe (Ptr iptr) itbl ptrsList nptrs_data)
162 readCType :: Integral a => a -> ClosureType
164 | i >= CONSTR && i <= CONSTR_NOCAF_STATIC = Constr
165 | i >= FUN && i <= FUN_STATIC = Fun
166 | i >= THUNK && i < THUNK_SELECTOR = Thunk (fromIntegral i)
167 | i == THUNK_SELECTOR = ThunkSelector
168 | i == BLACKHOLE = Blackhole
169 | i >= IND && i <= IND_STATIC = Indirection (fromIntegral i)
170 | fromIntegral i == aP_CODE = AP
172 | fromIntegral i == pAP_CODE = PAP
173 | otherwise = Other (fromIntegral i)
175 isConstr, isIndirection :: ClosureType -> Bool
176 isConstr Constr = True
179 isIndirection (Indirection _) = True
180 --isIndirection ThunkSelector = True
181 isIndirection _ = False
183 isThunk (Thunk _) = True
184 isThunk ThunkSelector = True
188 isFullyEvaluated :: a -> IO Bool
189 isFullyEvaluated a = do
190 closure <- getClosureData a
192 Constr -> do are_subs_evaluated <- amapM isFullyEvaluated (ptrs closure)
193 return$ and are_subs_evaluated
194 otherwise -> return False
195 where amapM f = sequence . amap' f
197 amap' f (Array i0 i arr#) = map (\(I# i#) -> case indexArray# arr# i# of
201 -- TODO: Fix it. Probably the otherwise case is failing, trace/debug it
203 unsafeDeepSeq :: a -> b -> b
204 unsafeDeepSeq = unsafeDeepSeq1 2
205 where unsafeDeepSeq1 0 a b = seq a $! b
206 unsafeDeepSeq1 i a b -- 1st case avoids infinite loops for non reducible thunks
207 | not (isConstr tipe) = seq a $! unsafeDeepSeq1 (i-1) a b
208 -- | unsafePerformIO (isFullyEvaluated a) = b
209 | otherwise = case unsafePerformIO (getClosureData a) of
210 closure -> foldl' (flip unsafeDeepSeq) b (ptrs closure)
211 where tipe = unsafePerformIO (getClosureType a)
213 isPointed :: Type -> Bool
214 isPointed t | Just (t, _) <- splitTyConApp_maybe t = not$ isUnliftedTypeKind (tyConKind t)
217 extractUnboxed :: [Type] -> Closure -> [[Word]]
218 extractUnboxed tt clos = go tt (nonPtrs clos)
220 | Just (tycon,_) <- splitTyConApp_maybe t
221 = ASSERT (isPrimTyCon tycon) sizeofTyCon tycon
222 | otherwise = pprPanic "Expected a TcTyCon" (ppr t)
225 | (x, rest) <- splitAt (sizeofType t `div` wORD_SIZE) xx
228 sizeofTyCon = sizeofPrimRep . tyConPrimRep
230 -----------------------------------
231 -- * Traversals for Terms
232 -----------------------------------
234 data TermFold a = TermFold { fTerm :: Type -> DataCon -> HValue -> [a] -> a
235 , fPrim :: Type -> [Word] -> a
236 , fSuspension :: ClosureType -> Maybe Type -> HValue -> Maybe Name -> a
239 foldTerm :: TermFold a -> Term -> a
240 foldTerm tf (Term ty dc v tt) = fTerm tf ty dc v (map (foldTerm tf) tt)
241 foldTerm tf (Prim ty v ) = fPrim tf ty v
242 foldTerm tf (Suspension ct ty v b) = fSuspension tf ct ty v b
244 idTermFold :: TermFold Term
245 idTermFold = TermFold {
248 fSuspension = Suspension
250 idTermFoldM :: Monad m => TermFold (m Term)
251 idTermFoldM = TermFold {
252 fTerm = \ty dc v tt -> sequence tt >>= return . Term ty dc v,
253 fPrim = (return.). Prim,
254 fSuspension = (((return.).).). Suspension
257 mapTermType f = foldTerm idTermFold {
258 fTerm = \ty dc hval tt -> Term (f ty) dc hval tt,
259 fSuspension = \ct mb_ty hval n ->
260 Suspension ct (fmap f mb_ty) hval n }
262 termTyVars = foldTerm TermFold {
263 fTerm = \ty _ _ tt ->
264 tyVarsOfType ty `plusVarEnv` concatVarEnv tt,
265 fSuspension = \_ mb_ty _ _ ->
266 maybe emptyVarEnv tyVarsOfType mb_ty,
267 fPrim = \ _ _ -> emptyVarEnv }
268 where concatVarEnv = foldr plusVarEnv emptyVarEnv
269 ----------------------------------
270 -- Pretty printing of terms
271 ----------------------------------
276 pprTerm :: Int -> Term -> SDoc
277 pprTerm p Term{dc=dc, subTerms=tt}
278 {- | dataConIsInfix dc, (t1:t2:tt') <- tt
279 = parens (pprTerm1 True t1 <+> ppr dc <+> pprTerm1 True ppr t2)
280 <+> hsep (map (pprTerm1 True) tt)
283 | otherwise = cparen (p >= app_prec)
284 (ppr dc <+> sep (map (pprTerm app_prec) tt))
286 where fixity = undefined
288 pprTerm _ t = pprTerm1 t
290 pprTerm1 Prim{value=words, ty=ty} = text$ repPrim (tyConAppTyCon ty) words
291 pprTerm1 t@Term{} = pprTerm 0 t
292 pprTerm1 Suspension{bound_to=Nothing} = char '_' -- <> ppr ct <> char '_'
293 pprTerm1 Suspension{mb_ty=Just ty, bound_to=Just n}
294 | Just _ <- splitFunTy_maybe ty = ptext SLIT("<function>")
295 | otherwise = parens$ ppr n <> text "::" <> ppr ty
298 cPprTerm :: forall m. Monad m => ((Int->Term->m SDoc)->[Int->Term->m (Maybe SDoc)]) -> Term -> m SDoc
299 cPprTerm custom = go 0 where
300 go prec t@Term{subTerms=tt, dc=dc} = do
301 let mb_customDocs = map (($t) . ($prec)) (custom go) :: [m (Maybe SDoc)]
302 first_success <- firstJustM mb_customDocs
303 case first_success of
304 Just doc -> return$ cparen (prec>app_prec+1) doc
305 -- | dataConIsInfix dc, (t1:t2:tt') <- tt =
306 Nothing -> do pprSubterms <- mapM (go (app_prec+1)) tt
307 return$ cparen (prec >= app_prec)
308 (ppr dc <+> sep pprSubterms)
309 go _ t = return$ pprTerm1 t
310 firstJustM (mb:mbs) = mb >>= maybe (firstJustM mbs) (return . Just)
311 firstJustM [] = return Nothing
313 cPprTermBase :: Monad m => (Int->Term-> m SDoc)->[Int->Term->m (Maybe SDoc)]
316 ifTerm isTupleDC (\_ -> liftM (parens . hcat . punctuate comma)
317 . mapM (pprP (-1)) . subTerms)
318 , ifTerm (isDC consDataCon) (\ p Term{subTerms=[h,t]} -> doList p h t)
319 , ifTerm (isDC intDataCon) (coerceShow$ \(a::Int)->a)
320 , ifTerm (isDC charDataCon) (coerceShow$ \(a::Char)->a)
321 -- , ifTerm (isDC wordDataCon) (coerceShow$ \(a::Word)->a)
322 , ifTerm (isDC floatDataCon) (coerceShow$ \(a::Float)->a)
323 , ifTerm (isDC doubleDataCon) (coerceShow$ \(a::Double)->a)
324 , ifTerm isIntegerDC (coerceShow$ \(a::Integer)->a)
326 where ifTerm pred f p t = if pred t then liftM Just (f p t) else return Nothing
327 isIntegerDC Term{dc=dc} =
328 dataConName dc `elem` [ smallIntegerDataConName
329 , largeIntegerDataConName]
330 isTupleDC Term{dc=dc} = dc `elem` snd (unzip (elems boxedTupleArr))
331 isDC a_dc Term{dc=dc} = a_dc == dc
332 coerceShow f _ = return . text . show . f . unsafeCoerce# . val
333 --TODO pprinting of list terms is not lazy
335 let elems = h : getListTerms t
336 isConsLast = termType(last elems) /= termType h
337 print_elems <- mapM (pprP 5) elems
338 return$ if isConsLast
339 then cparen (p >= 5) . hsep . punctuate (space<>colon)
341 else brackets (hcat$ punctuate comma print_elems)
343 where Just a /= Just b = not (a `coreEqType` b)
345 getListTerms Term{subTerms=[h,t]} = h : getListTerms t
346 getListTerms t@Term{subTerms=[]} = []
347 getListTerms t@Suspension{} = [t]
348 getListTerms t = pprPanic "getListTerms" (ppr t)
350 repPrim :: TyCon -> [Word] -> String
351 repPrim t = rep where
353 | t == charPrimTyCon = show (build x :: Char)
354 | t == intPrimTyCon = show (build x :: Int)
355 | t == wordPrimTyCon = show (build x :: Word)
356 | t == floatPrimTyCon = show (build x :: Float)
357 | t == doublePrimTyCon = show (build x :: Double)
358 | t == int32PrimTyCon = show (build x :: Int32)
359 | t == word32PrimTyCon = show (build x :: Word32)
360 | t == int64PrimTyCon = show (build x :: Int64)
361 | t == word64PrimTyCon = show (build x :: Word64)
362 | t == addrPrimTyCon = show (nullPtr `plusPtr` build x)
363 | t == stablePtrPrimTyCon = "<stablePtr>"
364 | t == stableNamePrimTyCon = "<stableName>"
365 | t == statePrimTyCon = "<statethread>"
366 | t == realWorldTyCon = "<realworld>"
367 | t == threadIdPrimTyCon = "<ThreadId>"
368 | t == weakPrimTyCon = "<Weak>"
369 | t == arrayPrimTyCon = "<array>"
370 | t == byteArrayPrimTyCon = "<bytearray>"
371 | t == mutableArrayPrimTyCon = "<mutableArray>"
372 | t == mutableByteArrayPrimTyCon = "<mutableByteArray>"
373 | t == mutVarPrimTyCon= "<mutVar>"
374 | t == mVarPrimTyCon = "<mVar>"
375 | t == tVarPrimTyCon = "<tVar>"
376 | otherwise = showSDoc (char '<' <> ppr t <> char '>')
377 where build ww = unsafePerformIO $ withArray ww (peek . castPtr)
378 -----------------------------------
379 -- Type Reconstruction
380 -----------------------------------
382 -- The Type Reconstruction monad
385 runTR :: HscEnv -> TR Term -> IO Term
387 mb_term <- initTcPrintErrors hsc_env iNTERACTIVE c
389 Nothing -> panic "Can't unify"
390 Just term -> return term
393 trIO = liftTcM . ioToTcRn
395 addConstraint :: TcType -> TcType -> TR ()
396 addConstraint t1 t2 = congruenceNewtypes t1 t2 >>= uncurry unifyType
399 A parallel fold over two Type values,
400 compensating for missing newtypes on both sides.
401 This is necessary because newtypes are not present
402 in runtime, but since sometimes there is evidence
403 available we do our best to reconstruct them.
404 Evidence can come from DataCon signatures or
405 from compile-time type inference.
406 I am using the words congruence and rewriting
407 because what we are doing here is an approximation
408 of unification modulo a set of equations, which would
409 come from newtype definitions. These should be the
410 equality coercions seen in System Fc. Rewriting
411 is performed, taking those equations as rules,
412 before launching unification.
414 It doesn't make sense to rewrite everywhere,
415 or we would end up with all newtypes. So we rewrite
416 only in presence of evidence.
417 The lhs comes from the heap structure of ptrs,nptrs.
418 The rhs comes from a DataCon type signature.
419 Rewriting in the rhs is restricted to the result type.
421 Note that it is very tricky to make this 'rewriting'
422 work with the unification implemented by TcM, where
423 substitutions are 'inlined'. The order in which
424 constraints are unified is vital for this (or I am
427 congruenceNewtypes :: TcType -> TcType -> TcM (TcType,TcType)
428 congruenceNewtypes = go True
430 go rewriteRHS lhs rhs
431 -- TyVar lhs inductive case
432 | Just tv <- getTyVar_maybe lhs
433 = recoverM (return (lhs,rhs)) $ do
434 Indirect ty_v <- readMetaTyVar tv
435 (lhs', rhs') <- go rewriteRHS ty_v rhs
436 writeMutVar (metaTvRef tv) (Indirect lhs')
438 -- TyVar rhs inductive case
439 | Just tv <- getTyVar_maybe rhs
440 = recoverM (return (lhs,rhs)) $ do
441 Indirect ty_v <- readMetaTyVar tv
442 (lhs', rhs') <- go rewriteRHS lhs ty_v
443 writeMutVar (metaTvRef tv) (Indirect rhs')
445 -- FunTy inductive case
446 | Just (l1,l2) <- splitFunTy_maybe lhs
447 , Just (r1,r2) <- splitFunTy_maybe rhs
448 = do (l2',r2') <- go True l2 r2
449 (l1',r1') <- go False l1 r1
450 return (mkFunTy l1' l2', mkFunTy r1' r2')
451 -- TyconApp Inductive case; this is the interesting bit.
452 | Just (tycon_l, args_l) <- splitNewTyConApp_maybe lhs
453 , Just (tycon_r, args_r) <- splitNewTyConApp_maybe rhs = do
455 let (tycon_l',args_l') = if isNewTyCon tycon_r && not(isNewTyCon tycon_l)
456 then (tycon_r, rewrite tycon_r lhs)
457 else (tycon_l, args_l)
458 (tycon_r',args_r') = if rewriteRHS && isNewTyCon tycon_l && not(isNewTyCon tycon_r)
459 then (tycon_l, rewrite tycon_l rhs)
460 else (tycon_r, args_r)
461 (args_l'', args_r'') <- unzip `liftM` zipWithM (go rewriteRHS) args_l' args_r'
462 return (mkTyConApp tycon_l' args_l'', mkTyConApp tycon_r' args_r'')
464 | otherwise = return (lhs,rhs)
466 where rewrite newtyped_tc lame_tipe
467 | (tvs, tipe) <- newTyConRep newtyped_tc
468 = case tcUnifyTys (const BindMe) [tipe] [lame_tipe] of
469 Just subst -> substTys subst (map mkTyVarTy tvs)
470 otherwise -> panic "congruenceNewtypes: Can't unify a newtype"
472 newVar :: Kind -> TR TcTyVar
473 newVar = liftTcM . newFlexiTyVar
477 -- | Returns the instantiated type scheme ty', and the substitution sigma
478 -- such that sigma(ty') = ty
479 instScheme :: Type -> TR (TcType, TvSubst)
480 instScheme ty | (tvs, rho) <- tcSplitForAllTys ty = liftTcM$ do
481 (tvs',theta,ty') <- tcInstType (mapM tcInstTyVar) ty
482 return (ty', zipTopTvSubst tvs' (mkTyVarTys tvs))
484 cvObtainTerm :: HscEnv -> Bool -> Maybe Type -> HValue -> IO Term
485 cvObtainTerm hsc_env force mb_ty hval = runTR hsc_env $ do
486 tv <- liftM mkTyVarTy (newVar argTypeKind)
488 Nothing -> go tv tv hval >>= zonkTerm
489 Just ty | isMonomorphic ty -> go ty ty hval >>= zonkTerm
491 (ty',rev_subst) <- instScheme (sigmaType ty)
493 term <- go tv tv hval >>= zonkTerm
494 --restore original Tyvars
495 return$ mapTermType (substTy rev_subst) term
498 let monomorphic = not(isTyVarTy tv) -- This is a convention. The ancestor tests for
499 -- monomorphism and passes a type instead of a tv
500 clos <- trIO $ getClosureData a
502 -- Thunks we may want to force
503 -- NB. this won't attempt to force a BLACKHOLE. Even with :force, we never
504 -- force blackholes, because it would almost certainly result in deadlock,
505 -- and showing the '_' is more useful.
506 t | isThunk t && force -> seq a $ go tv ty a
507 -- We always follow indirections
508 Indirection _ -> go tv ty $! (ptrs clos ! 0)
509 -- The interesting case
511 m_dc <- trIO$ tcRnRecoverDataCon hsc_env (infoPtr clos)
513 Nothing -> panic "Can't find the DataCon for a term"
515 let extra_args = length(dataConRepArgTys dc) - length(dataConOrigArgTys dc)
516 subTtypes = matchSubTypes dc ty
517 (subTtypesP, subTtypesNP) = partition isPointed subTtypes
518 subTermTvs <- sequence
519 [ if isMonomorphic t then return t else (mkTyVarTy `fmap` newVar k)
520 | (t,k) <- zip subTtypesP (map typeKind subTtypesP)]
521 -- It is vital for newtype reconstruction that the unification step is done
522 -- right here, _before_ the subterms are RTTI reconstructed.
523 when (not monomorphic) $ do
524 let myType = mkFunTys (reOrderTerms subTermTvs subTtypesNP subTtypes) tv
525 instScheme(dataConRepType dc) >>= addConstraint myType . fst
526 subTermsP <- sequence $ drop extra_args -- all extra arguments are pointed
527 [ appArr (go tv t) (ptrs clos) i
528 | (i,tv,t) <- zip3 [0..] subTermTvs subTtypesP]
529 let unboxeds = extractUnboxed subTtypesNP clos
530 subTermsNP = map (uncurry Prim) (zip subTtypesNP unboxeds)
531 subTerms = reOrderTerms subTermsP subTermsNP (drop extra_args subTtypes)
532 return (Term tv dc a subTerms)
533 -- The otherwise case: can be a Thunk,AP,PAP,etc.
535 return (Suspension (tipe clos) (Just tv) a Nothing)
537 -- Access the array of pointers and recurse down. Needs to be done with
538 -- care of no introducing a thunk! or go will fail to do its job
539 appArr f arr (I# i#) = case arr of
540 (Array _ _ ptrs#) -> case indexArray# ptrs# i# of
544 | Just (_,ty_args) <- splitTyConApp_maybe (repType ty)
545 , null (dataConExTyVars dc) --TODO Handle the case of extra existential tyvars
546 = dataConInstArgTys dc ty_args
548 | otherwise = dataConRepArgTys dc
550 -- This is used to put together pointed and nonpointed subterms in the
552 reOrderTerms _ _ [] = []
553 reOrderTerms pointed unpointed (ty:tys)
554 | isPointed ty = ASSERT2(not(null pointed)
555 , ptext SLIT("reOrderTerms") $$ (ppr pointed $$ ppr unpointed))
556 head pointed : reOrderTerms (tail pointed) unpointed tys
557 | otherwise = ASSERT2(not(null unpointed)
558 , ptext SLIT("reOrderTerms") $$ (ppr pointed $$ ppr unpointed))
559 head unpointed : reOrderTerms pointed (tail unpointed) tys
561 isMonomorphic ty | isForAllTy ty = False
562 isMonomorphic ty = (isEmptyVarSet . tyVarsOfType) ty
564 zonkTerm :: Term -> TcM Term
565 zonkTerm = foldTerm idTermFoldM {
566 fTerm = \ty dc v tt -> sequence tt >>= \tt ->
567 zonkTcType ty >>= \ty' ->
568 return (Term ty' dc v tt)
569 ,fSuspension = \ct ty v b -> fmapMMaybe zonkTcType ty >>= \ty ->
570 return (Suspension ct ty v b)}
573 -- Is this defined elsewhere?
574 -- Generalize the type: find all free tyvars and wrap in the appropiate ForAll.
575 sigmaType ty = mkForAllTys (varSetElems$ tyVarsOfType (dropForAlls ty)) ty
578 Example of Type Reconstruction
579 --------------------------------
580 Suppose we have an existential type such as
582 data Opaque = forall a. Opaque a
584 And we have a term built as:
586 t = Opaque (map Just [[1,1],[2,2]])
588 The type of t as far as the typechecker goes is t :: Opaque
589 If we seq the head of t, we obtain:
595 t - O ( (_3::b) : (_4::[b]) )
599 t - O ( (Just (_5::c)) : (_4::[b]) )
601 At this point, we know that b = (Maybe c)
605 t - O ( (Just ((_6::d) : (_7::[d]) )) : (_4::[b]) )
607 At this point, we know that c = [d]
611 t - O ( (Just (1 : (_7::[d]) )) : (_4::[b]) )
613 At this point, we know that d = Integer
615 The fully reconstructed expressions, with propagation, would be:
617 t - O ( (Just (_5::c)) : (_4::[Maybe c]) )
618 t - O ( (Just ((_6::d) : (_7::[d]) )) : (_4::[Maybe [d]]) )
619 t - O ( (Just (1 : (_7::[Integer]) )) : (_4::[Maybe [Integer]]) )
622 For reference, the type of the thing inside the opaque is
623 map Just [[1,1],[2,2]] :: [Maybe [Integer]]
625 NOTE: (Num t) contexts have been manually replaced by Integer for clarity