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
22 getClosureData, -- :: a -> IO Closure
23 Closure ( tipe, infoTable, ptrs, nonPtrs ),
24 getClosureType, -- :: a -> IO ClosureType
25 isConstr, -- :: ClosureType -> Bool
26 isIndirection, -- :: ClosureType -> Bool
27 getInfoTablePtr, -- :: a -> Ptr StgInfoTable
44 #include "HsVersions.h"
46 import ByteCodeItbls ( StgInfoTable )
47 import qualified ByteCodeItbls as BCI( StgInfoTable(..) )
48 import ByteCodeLink ( HValue )
49 import HscTypes ( HscEnv )
53 import TcRnMonad ( TcM, initTcPrintErrors, ioToTcRn, recoverM, writeMutVar )
64 import {-#SOURCE#-} TcRnDriver ( tcRnRecoverDataCon )
70 import Constants ( wORD_SIZE )
76 import GHC.Arr ( Array(..) )
77 import GHC.Ptr ( Ptr(..), castPtr )
79 import GHC.Int ( Int32(..), Int64(..) )
80 import GHC.Word ( Word32(..), Word64(..) )
84 import Data.Array.Base
85 import Data.List ( partition )
86 import Foreign.Storable
88 ---------------------------------------------
89 -- * A representation of semi evaluated Terms
90 ---------------------------------------------
92 A few examples in this representation:
94 > Just 10 = Term Data.Maybe Data.Maybe.Just (Just 10) [Term Int I# (10) "10"]
96 > (('a',_,_),_,('b',_,_)) =
97 Term ((Char,b,c),d,(Char,e,f)) (,,) (('a',_,_),_,('b',_,_))
98 [ Term (Char, b, c) (,,) ('a',_,_) [Term Char C# "a", Thunk, Thunk]
100 , Term (Char, e, f) (,,) ('b',_,_) [Term Char C# "b", Thunk, Thunk]]
103 data Term = Term { ty :: Type
106 , subTerms :: [Term] }
111 | Suspension { ctype :: ClosureType
112 , mb_ty :: Maybe Type
114 , bound_to :: Maybe Name -- Useful for printing
119 isSuspension Suspension{} = True
120 isSuspension _ = False
124 termType t@(Suspension {}) = mb_ty t
125 termType t = Just$ ty t
127 isFullyEvaluatedTerm :: Term -> Bool
128 isFullyEvaluatedTerm Term {subTerms=tt} = all isFullyEvaluatedTerm tt
129 isFullyEvaluatedTerm Suspension {} = False
130 isFullyEvaluatedTerm Prim {} = True
132 instance Outputable (Term) where
133 ppr = head . customPrintTerm customPrintTermBase
135 -------------------------------------------------------------------------
136 -- Runtime Closure Datatype and functions for retrieving closure related stuff
137 -------------------------------------------------------------------------
138 data ClosureType = Constr
149 data Closure = Closure { tipe :: ClosureType
150 , infoTable :: StgInfoTable
151 , ptrs :: Array Int HValue
152 -- What would be the type here? HValue is ok? Should I build a Ptr?
153 , nonPtrs :: ByteArray#
156 instance Outputable ClosureType where
159 getInfoTablePtr :: a -> Ptr StgInfoTable
162 itbl_ptr -> castPtr ( Ptr itbl_ptr )
164 getClosureType :: a -> IO ClosureType
165 getClosureType = liftM (readCType . BCI.tipe ) . peek . getInfoTablePtr
167 #include "../includes/ClosureTypes.h"
174 getClosureData :: a -> IO Closure
175 getClosureData a = do
176 itbl <- peek (getInfoTablePtr a)
177 let tipe = readCType (BCI.tipe itbl)
178 case closurePayload# a of
180 let elems = BCI.ptrs itbl
181 ptrsList = Array 0 (fromIntegral$ elems) ptrs
182 in ptrsList `seq` return (Closure tipe itbl ptrsList nptrs)
184 readCType :: Integral a => a -> ClosureType
186 | i >= CONSTR && i <= CONSTR_NOCAF_STATIC = Constr
187 | i >= FUN && i <= FUN_STATIC = Fun
188 | i >= THUNK && i < THUNK_SELECTOR = Thunk (fromIntegral i)
189 | i == THUNK_SELECTOR = ThunkSelector
190 | i == BLACKHOLE = Blackhole
191 | i >= IND && i <= IND_STATIC = Indirection (fromIntegral i)
192 | fromIntegral i == aP_CODE = AP
193 | fromIntegral i == pAP_CODE = PAP
194 | otherwise = Other (fromIntegral i)
196 isConstr, isIndirection :: ClosureType -> Bool
197 isConstr Constr = True
200 isIndirection (Indirection _) = True
201 --isIndirection ThunkSelector = True
202 isIndirection _ = False
204 isFullyEvaluated :: a -> IO Bool
205 isFullyEvaluated a = do
206 closure <- getClosureData a
208 Constr -> do are_subs_evaluated <- amapM isFullyEvaluated (ptrs closure)
209 return$ and are_subs_evaluated
210 otherwise -> return False
211 where amapM f = sequence . amap' f
213 amap' f (Array i0 i arr#) = map (\(I# i#) -> case indexArray# arr# i# of
217 -- TODO: Fix it. Probably the otherwise case is failing, trace/debug it
219 unsafeDeepSeq :: a -> b -> b
220 unsafeDeepSeq = unsafeDeepSeq1 2
221 where unsafeDeepSeq1 0 a b = seq a $! b
222 unsafeDeepSeq1 i a b -- 1st case avoids infinite loops for non reducible thunks
223 | not (isConstr tipe) = seq a $! unsafeDeepSeq1 (i-1) a b
224 -- | unsafePerformIO (isFullyEvaluated a) = b
225 | otherwise = case unsafePerformIO (getClosureData a) of
226 closure -> foldl' (flip unsafeDeepSeq) b (ptrs closure)
227 where tipe = unsafePerformIO (getClosureType a)
229 isPointed :: Type -> Bool
230 isPointed t | Just (t, _) <- splitTyConApp_maybe t = not$ isUnliftedTypeKind (tyConKind t)
233 #define MKDECODER(offset,cons,builder) (offset, show$ cons (builder addr 0#))
235 extractUnboxed :: [Type] -> ByteArray# -> [String]
236 extractUnboxed tt ba = helper tt (byteArrayContents# ba)
237 where helper :: [Type] -> Addr# -> [String]
239 | Just ( tycon,_) <- splitTyConApp_maybe t
240 = let (offset, txt) = decode tycon addr
241 (I# word_offset) = offset*wORD_SIZE
242 in txt : helper tt (plusAddr# addr word_offset)
244 = -- ["extractUnboxed.helper: Urk. I got a " ++ showSDoc (ppr t)]
245 panic$ "extractUnboxed.helper: Urk. I got a " ++ showSDoc (ppr t)
247 decode :: TyCon -> Addr# -> (Int, String)
249 | t == charPrimTyCon = MKDECODER(1,C#,indexCharOffAddr#)
250 | t == intPrimTyCon = MKDECODER(1,I#,indexIntOffAddr#)
251 | t == wordPrimTyCon = MKDECODER(1,W#,indexWordOffAddr#)
252 | t == floatPrimTyCon = MKDECODER(1,F#,indexFloatOffAddr#)
253 | t == doublePrimTyCon = MKDECODER(2,D#,indexDoubleOffAddr#)
254 | t == int32PrimTyCon = MKDECODER(1,I32#,indexInt32OffAddr#)
255 | t == word32PrimTyCon = MKDECODER(1,W32#,indexWord32OffAddr#)
256 | t == int64PrimTyCon = MKDECODER(2,I64#,indexInt64OffAddr#)
257 | t == word64PrimTyCon = MKDECODER(2,W64#,indexWord64OffAddr#)
258 | t == addrPrimTyCon = MKDECODER(1,I#,(\x off-> addr2Int# (indexAddrOffAddr# x off))) --OPT Improve the presentation of addresses
259 | t == stablePtrPrimTyCon = (1, "<stablePtr>")
260 | t == stableNamePrimTyCon = (1, "<stableName>")
261 | t == statePrimTyCon = (1, "<statethread>")
262 | t == realWorldTyCon = (1, "<realworld>")
263 | t == threadIdPrimTyCon = (1, "<ThreadId>")
264 | t == weakPrimTyCon = (1, "<Weak>")
265 | t == arrayPrimTyCon = (1,"<array>")
266 | t == byteArrayPrimTyCon = (1,"<bytearray>")
267 | t == mutableArrayPrimTyCon = (1, "<mutableArray>")
268 | t == mutableByteArrayPrimTyCon = (1, "<mutableByteArray>")
269 | t == mutVarPrimTyCon= (1, "<mutVar>")
270 | t == mVarPrimTyCon = (1, "<mVar>")
271 | t == tVarPrimTyCon = (1, "<tVar>")
272 | otherwise = (1, showSDoc (char '<' <> ppr t <> char '>'))
273 -- We cannot know the right offset in the otherwise case, so 1 is just a wild dangerous guess!
274 -- TODO: Improve the offset handling in decode (make it machine dependant)
276 -----------------------------------
277 -- * Traversals for Terms
278 -----------------------------------
280 data TermFold a = TermFold { fTerm :: Type -> DataCon -> HValue -> [a] -> a
281 , fPrim :: Type -> String -> a
282 , fSuspension :: ClosureType -> Maybe Type -> HValue -> Maybe Name -> a
285 foldTerm :: TermFold a -> Term -> a
286 foldTerm tf (Term ty dc v tt) = fTerm tf ty dc v (map (foldTerm tf) tt)
287 foldTerm tf (Prim ty v ) = fPrim tf ty v
288 foldTerm tf (Suspension ct ty v b) = fSuspension tf ct ty v b
290 idTermFold :: TermFold Term
291 idTermFold = TermFold {
294 fSuspension = Suspension
296 idTermFoldM :: Monad m => TermFold (m Term)
297 idTermFoldM = TermFold {
298 fTerm = \ty dc v tt -> sequence tt >>= return . Term ty dc v,
299 fPrim = (return.). Prim,
300 fSuspension = (((return.).).). Suspension
303 ----------------------------------
304 -- Pretty printing of terms
305 ----------------------------------
307 parensCond True = parens
308 parensCond False = id
312 printTerm :: Term -> SDoc
313 printTerm Prim{value=value} = text value
314 printTerm t@Term{} = printTerm1 0 t
315 printTerm Suspension{bound_to=Nothing} = char '_' -- <> ppr ct <> char '_'
316 printTerm Suspension{mb_ty=Just ty, bound_to=Just n}
317 | Just _ <- splitFunTy_maybe ty = text "<function>"
318 | otherwise = parens$ ppr n <> text "::" <> ppr ty
320 printTerm1 p Term{dc=dc, subTerms=tt}
321 {- | dataConIsInfix dc, (t1:t2:tt') <- tt
322 = parens (printTerm1 True t1 <+> ppr dc <+> printTerm1 True ppr t2)
323 <+> hsep (map (printTerm1 True) tt)
326 | otherwise = parensCond (p > app_prec)
327 (ppr dc <+> sep (map (printTerm1 (app_prec+1)) tt))
329 where fixity = undefined
331 printTerm1 _ t = printTerm t
333 customPrintTerm :: Monad m => ((Int->Term->m SDoc)->[Term->m (Maybe SDoc)]) -> Term -> m SDoc
334 customPrintTerm custom = let
335 -- go :: Monad m => Int -> Term -> m SDoc
336 go prec t@Term{subTerms=tt, dc=dc} = do
337 mb_customDocs <- sequence$ sequence (custom go) t -- Inner sequence is List monad
338 case msum mb_customDocs of -- msum is in Maybe monad
339 Just doc -> return$ parensCond (prec>app_prec+1) doc
340 -- | dataConIsInfix dc, (t1:t2:tt') <- tt =
341 Nothing -> do pprSubterms <- mapM (go (app_prec+1)) tt
342 return$ parensCond (prec>app_prec+1)
343 (ppr dc <+> sep pprSubterms)
344 go _ t = return$ printTerm t
346 where fixity = undefined
348 customPrintTermBase :: Monad m => (Int->Term-> m SDoc)->[Term->m (Maybe SDoc)]
349 customPrintTermBase showP =
351 test isTupleDC (liftM (parens . hcat . punctuate comma) . mapM (showP 0) . subTerms)
352 , test (isDC consDataCon) (\Term{subTerms=[h,t]} -> doList h t)
353 , test (isDC intDataCon) (coerceShow$ \(a::Int)->a)
354 , test (isDC charDataCon) (coerceShow$ \(a::Char)->a)
355 -- , test (isDC wordDataCon) (coerceShow$ \(a::Word)->a)
356 , test (isDC floatDataCon) (coerceShow$ \(a::Float)->a)
357 , test (isDC doubleDataCon) (coerceShow$ \(a::Double)->a)
358 , test isIntegerDC (coerceShow$ \(a::Integer)->a)
360 where test pred f t = if pred t then liftM Just (f t) else return Nothing
361 isIntegerDC Term{dc=dc} =
362 dataConName dc `elem` [ smallIntegerDataConName
363 , largeIntegerDataConName]
364 isTupleDC Term{dc=dc} = dc `elem` snd (unzip (elems boxedTupleArr))
365 isDC a_dc Term{dc=dc} = a_dc == dc
366 coerceShow f = return . text . show . f . unsafeCoerce# . val
367 --TODO pprinting of list terms is not lazy
369 let elems = h : getListTerms t
370 isConsLast = isSuspension (last elems) &&
371 (mb_ty$ last elems) /= (termType h)
372 init <- mapM (showP 0) (init elems)
373 last0 <- showP 0 (last elems)
374 let last = case length elems of
376 _ | isConsLast -> text " | " <> last0
378 return$ brackets (hcat (punctuate comma init ++ [last]))
380 where Just a /= Just b = not (a `coreEqType` b)
382 getListTerms Term{subTerms=[h,t]} = h : getListTerms t
383 getListTerms t@Term{subTerms=[]} = []
384 getListTerms t@Suspension{} = [t]
385 getListTerms t = pprPanic "getListTerms" (ppr t)
387 -----------------------------------
388 -- Type Reconstruction
389 -----------------------------------
391 -- The Type Reconstruction monad
394 runTR :: HscEnv -> TR Term -> IO Term
396 mb_term <- initTcPrintErrors hsc_env iNTERACTIVE (c >>= zonkTerm)
398 Nothing -> panic "Can't unify"
399 Just term -> return term
402 trIO = liftTcM . ioToTcRn
404 addConstraint :: TcType -> TcType -> TR ()
405 addConstraint t1 t2 = congruenceNewtypes t1 t2 >>= uncurry unifyType
408 A parallel fold over two Type values,
409 compensating for missing newtypes on both sides.
410 This is necessary because newtypes are not present
411 in runtime, but since sometimes there is evidence
412 available we do our best to reconstruct them.
413 Evidence can come from DataCon signatures or
414 from compile-time type inference.
415 I am using the words congruence and rewriting
416 because what we are doing here is an approximation
417 of unification modulo a set of equations, which would
418 come from newtype definitions. These should be the
419 equality coercions seen in System Fc. Rewriting
420 is performed, taking those equations as rules,
421 before launching unification.
423 It doesn't make sense to rewrite everywhere,
424 or we would end up with all newtypes. So we rewrite
425 only in presence of evidence.
426 The lhs comes from the heap structure of ptrs,nptrs.
427 The rhs comes from a DataCon type signature.
428 Rewriting in the rhs is restricted to the result type.
430 Note that it is very tricky to make this 'rewriting'
431 work with the unification implemented by TcM, where
432 substitutions are 'inlined'. The order in which
433 constraints are unified is vital for this (or I am
436 congruenceNewtypes :: TcType -> TcType -> TcM (TcType,TcType)
437 congruenceNewtypes = go True
439 go rewriteRHS lhs rhs
440 -- TyVar lhs inductive case
441 | Just tv <- getTyVar_maybe lhs
442 = recoverM (return (lhs,rhs)) $ do
443 Indirect ty_v <- readMetaTyVar tv
444 (lhs', rhs') <- go rewriteRHS ty_v rhs
445 writeMutVar (metaTvRef tv) (Indirect lhs')
447 -- TyVar rhs inductive case
448 | Just tv <- getTyVar_maybe rhs
449 = recoverM (return (lhs,rhs)) $ do
450 Indirect ty_v <- readMetaTyVar tv
451 (lhs', rhs') <- go rewriteRHS lhs ty_v
452 writeMutVar (metaTvRef tv) (Indirect rhs')
454 -- FunTy inductive case
455 | Just (l1,l2) <- splitFunTy_maybe lhs
456 , Just (r1,r2) <- splitFunTy_maybe rhs
457 = do (l2',r2') <- go True l2 r2
458 (l1',r1') <- go False l1 r1
459 return (mkFunTy l1' l2', mkFunTy r1' r2')
460 -- TyconApp Inductive case; this is the interesting bit.
461 | Just (tycon_l, args_l) <- splitNewTyConApp_maybe lhs
462 , Just (tycon_r, args_r) <- splitNewTyConApp_maybe rhs = do
464 let (tycon_l',args_l') = if isNewTyCon tycon_r && not(isNewTyCon tycon_l)
465 then (tycon_r, rewrite tycon_r lhs)
466 else (tycon_l, args_l)
467 (tycon_r',args_r') = if rewriteRHS && isNewTyCon tycon_l && not(isNewTyCon tycon_r)
468 then (tycon_l, rewrite tycon_l rhs)
469 else (tycon_r, args_r)
470 (args_l'', args_r'') <- unzip `liftM` zipWithM (go rewriteRHS) args_l' args_r'
471 return (mkTyConApp tycon_l' args_l'', mkTyConApp tycon_r' args_r'')
473 | otherwise = return (lhs,rhs)
475 where rewrite newtyped_tc lame_tipe
476 | (tvs, tipe) <- newTyConRep newtyped_tc
477 = case tcUnifyTys (const BindMe) [tipe] [lame_tipe] of
478 Just subst -> substTys subst (map mkTyVarTy tvs)
479 otherwise -> panic "congruenceNewtypes: Can't unify a newtype"
481 newVar :: Kind -> TR TcTyVar
482 newVar = liftTcM . newFlexiTyVar
486 instScheme :: Type -> TR TcType
487 instScheme ty = liftTcM$ liftM trd (tcInstType (liftM fst3 . tcInstTyVars) ty)
488 where fst3 (x,y,z) = x
491 cvObtainTerm :: HscEnv -> Bool -> Maybe Type -> HValue -> IO Term
492 cvObtainTerm hsc_env force mb_ty a =
493 -- Obtain the term and tidy the type before returning it
494 cvObtainTerm1 hsc_env force mb_ty a >>= return . tidyTypes
496 tidyTypes = foldTerm idTermFold {
497 fTerm = \ty dc hval tt -> Term (tidy ty) dc hval tt,
498 fSuspension = \ct mb_ty hval n ->
499 Suspension ct (fmap tidy mb_ty) hval n
501 tidy ty = tidyType (emptyTidyOccEnv, tidyVarEnv ty) ty
504 mkVarEnv$ [ (v, setTyVarName v (tyVarName tv))
505 | (tv,v) <- zip alphaTyVars vars]
506 where vars = varSetElems$ tyVarsOfType ty
508 cvObtainTerm1 :: HscEnv -> Bool -> Maybe Type -> HValue -> IO Term
509 cvObtainTerm1 hsc_env force mb_ty hval = runTR hsc_env $ do
510 tv <- liftM mkTyVarTy (newVar argTypeKind)
511 when (isJust mb_ty) $
512 instScheme (sigmaType$ fromJust mb_ty) >>= addConstraint tv
516 ctype <- trIO$ getClosureType a
518 -- Thunks we may want to force
519 Thunk _ | force -> seq a $ go tv a
520 -- We always follow indirections
521 _ | isIndirection ctype -> do
522 clos <- trIO$ getClosureData a
523 (go tv $! (ptrs clos ! 0))
524 -- The interesting case
526 m_dc <- trIO$ tcRnRecoverDataCon hsc_env a
528 Nothing -> panic "Can't find the DataCon for a term"
530 clos <- trIO$ getClosureData a
531 let extra_args = length(dataConRepArgTys dc) - length(dataConOrigArgTys dc)
532 subTtypes = drop extra_args (dataConRepArgTys dc)
533 (subTtypesP, subTtypesNP) = partition isPointed subTtypes
534 n_subtermsP= length subTtypesP
535 subTermTvs <- mapM (liftM mkTyVarTy . newVar ) (map typeKind subTtypesP)
536 baseType <- instScheme (dataConRepType dc)
537 let myType = mkFunTys (reOrderTerms subTermTvs subTtypesNP subTtypes) tv
538 addConstraint myType baseType
539 subTermsP <- sequence [ extractSubterm i tv (ptrs clos)
540 | (i,tv) <- zip [extra_args..extra_args + n_subtermsP - 1]
542 let unboxeds = extractUnboxed subTtypesNP (nonPtrs clos)
543 subTermsNP = map (uncurry Prim) (zip subTtypesNP unboxeds)
544 subTerms = reOrderTerms subTermsP subTermsNP subTtypes
545 return (Term tv dc a subTerms)
546 -- The otherwise case: can be a Thunk,AP,PAP,etc.
548 return (Suspension ctype (Just tv) a Nothing)
550 -- Access the array of pointers and recurse down. Needs to be done with
551 -- care of no introducing a thunk! or go will fail to do its job
552 extractSubterm (I# i#) tv ptrs = case ptrs of
553 (Array _ _ ptrs#) -> case indexArray# ptrs# i# of
556 -- This is used to put together pointed and nonpointed subterms in the
558 reOrderTerms _ _ [] = []
559 reOrderTerms pointed unpointed (ty:tys)
560 | isPointed ty = head pointed : reOrderTerms (tail pointed) unpointed tys
561 | otherwise = head unpointed : reOrderTerms pointed (tail unpointed) tys
563 zonkTerm :: Term -> TcM Term
564 zonkTerm = foldTerm idTermFoldM {
565 fTerm = \ty dc v tt -> sequence tt >>= \tt ->
566 zonkTcType ty >>= \ty' ->
567 return (Term ty' dc v tt)
568 ,fSuspension = \ct ty v b -> fmapMMaybe zonkTcType ty >>= \ty ->
569 return (Suspension ct ty v b)}
572 -- Is this defined elsewhere?
573 -- Generalize the type: find all free tyvars and wrap in the appropiate ForAll.
574 sigmaType ty = mkForAllTys (varSetElems$ tyVarsOfType (dropForAlls ty)) ty
577 Example of Type Reconstruction
578 --------------------------------
579 Suppose we have an existential type such as
581 data Opaque = forall a. Opaque a
583 And we have a term built as:
585 t = Opaque (map Just [[1,1],[2,2]])
587 The type of t as far as the typechecker goes is t :: Opaque
588 If we seq the head of t, we obtain:
594 t - O ( (_3::b) : (_4::[b]) )
598 t - O ( (Just (_5::c)) : (_4::[b]) )
600 At this point, we know that b = (Maybe c)
604 t - O ( (Just ((_6::d) : (_7::[d]) )) : (_4::[b]) )
606 At this point, we know that c = [d]
610 t - O ( (Just (1 : (_7::[d]) )) : (_4::[b]) )
612 At this point, we know that d = Integer
614 The fully reconstructed expressions, with propagation, would be:
616 t - O ( (Just (_5::c)) : (_4::[Maybe c]) )
617 t - O ( (Just ((_6::d) : (_7::[d]) )) : (_4::[Maybe [d]]) )
618 t - O ( (Just (1 : (_7::[Integer]) )) : (_4::[Maybe [Integer]]) )
621 For reference, the type of the thing inside the opaque is
622 map Just [[1,1],[2,2]] :: [Maybe [Integer]]
624 NOTE: (Num t) contexts have been manually replaced by Integer for clarity
627 --------------------------------------------------------------------
628 -- The DataConEnv is used to store the addresses of datacons loaded
629 -- via the dynamic linker
630 --------------------------------------------------------------------
632 type DataConEnv = AddressEnv StgInfoTable
634 -- Note that this AddressEnv and DataConEnv I wrote trying to follow
635 -- conventions in ghc, but probably they make not much sense.
637 newtype AddressEnv a = AE {aenv:: FiniteMap (Ptr a) Name}
638 deriving (Outputable)
640 emptyAddressEnv = AE emptyFM
642 extendAddressEnvList :: AddressEnv a -> [(Ptr a, Name)] -> AddressEnv a
643 elemAddressEnv :: Ptr a -> AddressEnv a -> Bool
644 delFromAddressEnv :: AddressEnv a -> Ptr a -> AddressEnv a
645 nullAddressEnv :: AddressEnv a -> Bool
646 lookupAddressEnv :: AddressEnv a -> Ptr a -> Maybe Name
648 extendAddressEnvList (AE env) = AE . addListToFM env
649 elemAddressEnv ptr (AE env) = ptr `elemFM` env
650 delFromAddressEnv (AE env) = AE . delFromFM env
651 nullAddressEnv = isEmptyFM . aenv
652 lookupAddressEnv (AE env) = lookupFM env
655 instance Outputable (Ptr a) where