| ... |
... |
@@ -5,7 +5,7 @@ |
|
5
|
5
|
{-
|
|
6
|
6
|
(c) Galois, 2006
|
|
7
|
7
|
(c) University of Glasgow, 2007
|
|
8
|
|
-(c) Florian Ragwitz, 2025
|
|
|
8
|
+(c) Florian Ragwitz, 2025-2026
|
|
9
|
9
|
-}
|
|
10
|
10
|
|
|
11
|
11
|
module GHC.HsToCore.Ticks
|
| ... |
... |
@@ -24,6 +24,8 @@ import GHC.Unit |
|
24
|
24
|
|
|
25
|
25
|
import GHC.Core.Type
|
|
26
|
26
|
import GHC.Core.TyCon
|
|
|
27
|
+import GHC.Core.InstEnv
|
|
|
28
|
+import GHC.Core.Class
|
|
27
|
29
|
|
|
28
|
30
|
import GHC.Data.Maybe
|
|
29
|
31
|
import GHC.Data.FastString
|
| ... |
... |
@@ -48,6 +50,7 @@ import GHC.Types.CostCentre.State |
|
48
|
50
|
import GHC.Types.Tickish
|
|
49
|
51
|
import GHC.Types.ProfAuto
|
|
50
|
52
|
import GHC.Tc.Types.ErrCtxt
|
|
|
53
|
+import GHC.Tc.Utils.TcType
|
|
51
|
54
|
|
|
52
|
55
|
import Control.Monad
|
|
53
|
56
|
import Data.List (isSuffixOf, intersperse)
|
| ... |
... |
@@ -56,6 +59,7 @@ import Data.Foldable (toList) |
|
56
|
59
|
import Trace.Hpc.Mix
|
|
57
|
60
|
|
|
58
|
61
|
import Data.Bifunctor (second)
|
|
|
62
|
+import Data.Functor
|
|
59
|
63
|
import Data.List.NonEmpty (NonEmpty (..))
|
|
60
|
64
|
import Data.Set (Set)
|
|
61
|
65
|
import qualified Data.Set as Set
|
| ... |
... |
@@ -100,11 +104,15 @@ addTicksToBinds |
|
100
|
104
|
-- isExportedId doesn't work yet (the desugarer
|
|
101
|
105
|
-- hasn't set it), so we have to work from this set.
|
|
102
|
106
|
-> [TyCon] -- ^ Type constructors in this module
|
|
|
107
|
+ -> [ClsInst] -- ^ Class instances in this module
|
|
|
108
|
+ -> IdEnv DFunId -- ^ Mapping from class method Ids to the DFunIds
|
|
|
109
|
+ -- of the class instance they belong to. See Note
|
|
|
110
|
+ -- [Instance Method Coverage].
|
|
103
|
111
|
-> LHsBinds GhcTc
|
|
104
|
112
|
-> IO (LHsBinds GhcTc, Maybe (FilePath, SizedSeq Tick, SizedSeq Tick))
|
|
105
|
113
|
|
|
106
|
114
|
addTicksToBinds logger cfg
|
|
107
|
|
- mod mod_loc exports tyCons binds
|
|
|
115
|
+ mod mod_loc exports tyCons insts inst_meths binds
|
|
108
|
116
|
| let passes = ticks_passes cfg
|
|
109
|
117
|
, not (null passes)
|
|
110
|
118
|
, Just orig_file <- ml_hs_file mod_loc = do
|
| ... |
... |
@@ -129,8 +137,13 @@ addTicksToBinds logger cfg |
|
129
|
137
|
, this_mod = mod
|
|
130
|
138
|
, tickishType = tickish
|
|
131
|
139
|
, recSelBinds = emptyVarEnv
|
|
|
140
|
+ , instMeths = inst_meths
|
|
|
141
|
+ , instTicks = emptyVarEnv
|
|
132
|
142
|
}
|
|
133
|
|
- (binds',_,st') = unTM (addTickLHsBinds binds) env st
|
|
|
143
|
+ addTick = do
|
|
|
144
|
+ instTicks <- allocInstTicks insts
|
|
|
145
|
+ withEnv (\e -> e{ instTicks }) $ addTickLHsBinds binds
|
|
|
146
|
+ (binds',_,st') = unTM addTick env st
|
|
134
|
147
|
in (binds', st')
|
|
135
|
148
|
|
|
136
|
149
|
(binds1,st) = foldr tickPass (binds, initTTState) passes
|
| ... |
... |
@@ -231,7 +244,7 @@ addTickLHsBind :: LHsBind GhcTc -> TM (LHsBind GhcTc) |
|
231
|
244
|
addTickLHsBind (L pos (XHsBindsLR bind@(AbsBinds { abs_binds = binds
|
|
232
|
245
|
, abs_exports = abs_exports
|
|
233
|
246
|
}))) =
|
|
234
|
|
- withEnv (add_rec_sels . add_inlines . add_exports) $ do
|
|
|
247
|
+ withEnv (add_inst_meths . add_rec_sels . add_inlines . add_exports) $ do
|
|
235
|
248
|
binds' <- addTickLHsBinds binds
|
|
236
|
249
|
return $ L pos $ XHsBindsLR $ bind { abs_binds = binds' }
|
|
237
|
250
|
where
|
| ... |
... |
@@ -259,19 +272,24 @@ addTickLHsBind (L pos (XHsBindsLR bind@(AbsBinds { abs_binds = binds |
|
259
|
272
|
| ABE{ abe_poly, abe_mono } <- abs_exports
|
|
260
|
273
|
, RecSelId{} <- [idDetails abe_poly] ] }
|
|
261
|
274
|
|
|
|
275
|
+ add_inst_meths env =
|
|
|
276
|
+ env{ instMeths = instMeths env `extendVarEnvList`
|
|
|
277
|
+ [ (abe_mono, dfun)
|
|
|
278
|
+ | ABE{ abe_poly, abe_mono } <- abs_exports
|
|
|
279
|
+ , Just dfun <- [lookupVarEnv (instMeths env) abe_poly] ] }
|
|
|
280
|
+
|
|
262
|
281
|
addTickLHsBind (L pos (funBind@(FunBind { fun_id = L _ id, fun_matches = matches }))) = do
|
|
263
|
282
|
let name = getOccString id
|
|
264
|
283
|
decl_path <- getPathEntry
|
|
265
|
284
|
density <- getDensity
|
|
|
285
|
+ env <- getEnv
|
|
266
|
286
|
|
|
267
|
|
- inline_ids <- liftM inlines getEnv
|
|
268
|
287
|
-- See Note [inline sccs]
|
|
269
|
288
|
let inline = isInlinePragma (idInlinePragma id)
|
|
270
|
|
- || id `elemVarSet` inline_ids
|
|
|
289
|
+ || id `elemVarSet` inlines env
|
|
271
|
290
|
|
|
272
|
291
|
-- See Note [inline sccs]
|
|
273
|
|
- tickish <- tickishType `liftM` getEnv
|
|
274
|
|
- case tickish of { ProfNotes | inline -> return (L pos funBind); _ -> do
|
|
|
292
|
+ case tickishType env of { ProfNotes | inline -> return (L pos funBind); _ -> do
|
|
275
|
293
|
|
|
276
|
294
|
-- See Note [Record-selector ticks]
|
|
277
|
295
|
selTicks <- recSelTick id
|
| ... |
... |
@@ -294,16 +312,26 @@ addTickLHsBind (L pos (funBind@(FunBind { fun_id = L _ id, fun_matches = matches |
|
294
|
312
|
toplev = null decl_path
|
|
295
|
313
|
exported = idName id `elemNameSet` exported_names
|
|
296
|
314
|
|
|
297
|
|
- tick <- if not blackListed &&
|
|
298
|
|
- shouldTickBind density toplev exported simple inline
|
|
|
315
|
+ -- See Note [Instance Method Coverage].
|
|
|
316
|
+ let generatedInstMeth = density == TickForCoverage
|
|
|
317
|
+ && id `elemVarEnv` instMeths env
|
|
|
318
|
+ && isGenerated (mg_origin (mg_ext matches))
|
|
|
319
|
+
|
|
|
320
|
+ tick <- if not generatedInstMeth
|
|
|
321
|
+ && not blackListed
|
|
|
322
|
+ && shouldTickBind density toplev exported simple inline
|
|
299
|
323
|
then
|
|
300
|
324
|
bindTick density name (locA pos) fvs
|
|
301
|
325
|
else
|
|
302
|
326
|
return Nothing
|
|
303
|
327
|
|
|
|
328
|
+ -- See Note [Instance Method Coverage].
|
|
|
329
|
+ instTick <- instMethTick id
|
|
|
330
|
+
|
|
304
|
331
|
let mbCons = maybe Prelude.id (:)
|
|
305
|
332
|
return $ L pos $ funBind { fun_matches = mg
|
|
306
|
|
- , fun_ext = second (tick `mbCons`) (fun_ext funBind) }
|
|
|
333
|
+ , fun_ext = second ((instTick `mbCons`) . (tick `mbCons`))
|
|
|
334
|
+ (fun_ext funBind) }
|
|
307
|
335
|
} }
|
|
308
|
336
|
where
|
|
309
|
337
|
-- See Note [Record-selector ticks]
|
| ... |
... |
@@ -338,6 +366,32 @@ addTickLHsBind (L pos (funBind@(FunBind { fun_id = L _ id, fun_matches = matches |
|
338
|
366
|
-- This scheme could be extended further in the future, making coverage for
|
|
339
|
367
|
-- positional constructor fields mean that the field was accessed at run-time.
|
|
340
|
368
|
|
|
|
369
|
+-- Note [Instance Method Coverage]
|
|
|
370
|
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
371
|
+-- Users expect source-level semantics for coverage reports (see #17155,
|
|
|
372
|
+-- #15932). That is, code they've written themselves should be marked as covered
|
|
|
373
|
+-- iff it is evaluated at run-time. Given that the implementation of class
|
|
|
374
|
+-- instances creates top-level bindings for all methods of an instance,
|
|
|
375
|
+-- inherited and hand-written, we need to go out of our way to create top-level
|
|
|
376
|
+-- tick boxes only for methods which correspond to user-written code.
|
|
|
377
|
+--
|
|
|
378
|
+-- To do so, 'addTicksToBinds' receives a mapping from class instance 'Id's to
|
|
|
379
|
+-- the 'DFunId's of the dictionary backing the associated instance. When
|
|
|
380
|
+-- creating tick boxes for top-level bindings, we can then decide if a given
|
|
|
381
|
+-- 'FunBind' corresponds to an inherited/generated instance method of a class
|
|
|
382
|
+-- and skip creating the tick box for it.
|
|
|
383
|
+--
|
|
|
384
|
+-- Additionally, we also want to inform the user whether or not their class
|
|
|
385
|
+-- instance as a whole has been used at run-time. To do so, we create a single
|
|
|
386
|
+-- tick box at the instance head and tick that box when any method of that
|
|
|
387
|
+-- instance is evaluated.
|
|
|
388
|
+--
|
|
|
389
|
+-- As marker/tag-like class instances without any methods would always appear
|
|
|
390
|
+-- as uncovered using the above mechanism, we exclude them when creating
|
|
|
391
|
+-- instance tick boxes. This could potentially be improved in the future by
|
|
|
392
|
+-- tracking if any functions requiring an instance of an empty class have been
|
|
|
393
|
+-- evaluated.
|
|
|
394
|
+
|
|
341
|
395
|
-- TODO: Revisit this
|
|
342
|
396
|
addTickLHsBind (L pos (pat@(PatBind { pat_lhs = lhs
|
|
343
|
397
|
, pat_rhs = rhs
|
| ... |
... |
@@ -1098,6 +1152,8 @@ data TickTransEnv = TTE { fileName :: FastString |
|
1098
|
1152
|
, this_mod :: Module
|
|
1099
|
1153
|
, tickishType :: TickishType
|
|
1100
|
1154
|
, recSelBinds :: IdEnv DVarSet
|
|
|
1155
|
+ , instMeths :: IdEnv DFunId
|
|
|
1156
|
+ , instTicks :: IdEnv CoreTickish
|
|
1101
|
1157
|
}
|
|
1102
|
1158
|
|
|
1103
|
1159
|
-- deriving Show
|
| ... |
... |
@@ -1238,6 +1294,21 @@ isBlackListed (RealSrcSpan pos _) = TM $ \ env st -> (Set.member pos (blackList |
|
1238
|
1294
|
isBlackListed GeneratedSrcSpan{} = return False
|
|
1239
|
1295
|
isBlackListed UnhelpfulSpan{} = return False
|
|
1240
|
1296
|
|
|
|
1297
|
+-- See Note [Instance Method Coverage].
|
|
|
1298
|
+allocInstTicks :: [ClsInst] -> TM (IdEnv CoreTickish)
|
|
|
1299
|
+allocInstTicks insts =
|
|
|
1300
|
+ ifDensity TickForCoverage (mkVarEnv . catMaybes <$> mapM alloc insts) (pure emptyVarEnv)
|
|
|
1301
|
+ where
|
|
|
1302
|
+ alloc ClsInst{ is_dfun, is_cls }
|
|
|
1303
|
+ | null (classMethods is_cls) = pure Nothing
|
|
|
1304
|
+ | otherwise = fmap (is_dfun,) <$> allocATickBox (TopLevelBox [s])
|
|
|
1305
|
+ False True (getSrcSpan is_dfun) noFVs
|
|
|
1306
|
+ where s = showSDocOneLine defaultSDocContext (pprSigmaType (idType is_dfun))
|
|
|
1307
|
+
|
|
|
1308
|
+-- See Note [Instance Method Coverage].
|
|
|
1309
|
+instMethTick :: Id -> TM (Maybe CoreTickish)
|
|
|
1310
|
+instMethTick id = getEnv <&> \e -> lookupVarEnv (instMeths e) id >>= lookupVarEnv (instTicks e)
|
|
|
1311
|
+
|
|
1241
|
1312
|
-- the tick application inherits the source position of its
|
|
1242
|
1313
|
-- expression argument to support nested box allocations
|
|
1243
|
1314
|
allocTickBox :: BoxLabel -> Bool -> Bool -> SrcSpan -> TM (HsExpr GhcTc)
|