ghc-9.6.1: GHC/Cmm/ThreadSanitizer.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
-- | Annotate a CmmGraph with ThreadSanitizer instrumentation calls.
module GHC.Cmm.ThreadSanitizer (annotateTSAN) where
import GHC.Prelude
import GHC.StgToCmm.Utils (get_GlobalReg_addr)
import GHC.Platform
import GHC.Platform.Regs (activeStgRegs, callerSaves)
import GHC.Cmm
import GHC.Cmm.Utils
import GHC.Cmm.CLabel
import GHC.Cmm.Dataflow
import GHC.Cmm.Dataflow.Block
import GHC.Cmm.Dataflow.Graph
import GHC.Data.FastString
import GHC.Types.Basic
import GHC.Types.ForeignCall
import GHC.Types.Unique
import GHC.Types.Unique.Supply
import Data.Maybe (fromMaybe)
data Env = Env { platform :: Platform
, uniques :: [Unique]
}
annotateTSAN :: Platform -> CmmGraph -> UniqSM CmmGraph
annotateTSAN platform graph = do
env <- Env platform <$> getUniquesM
return $ modifyGraph (mapGraphBlocks (annotateBlock env)) graph
mapBlockList :: (forall e' x'. n e' x' -> Block n e' x')
-> Block n e x -> Block n e x
mapBlockList f (BlockCO n rest ) = f n `blockAppend` mapBlockList f rest
mapBlockList f (BlockCC n rest m) = f n `blockAppend` mapBlockList f rest `blockAppend` f m
mapBlockList f (BlockOC rest m) = mapBlockList f rest `blockAppend` f m
mapBlockList _ BNil = BNil
mapBlockList f (BMiddle blk) = f blk
mapBlockList f (BCat a b) = mapBlockList f a `blockAppend` mapBlockList f b
mapBlockList f (BSnoc a n) = mapBlockList f a `blockAppend` f n
mapBlockList f (BCons n a) = f n `blockAppend` mapBlockList f a
annotateBlock :: Env -> Block CmmNode e x -> Block CmmNode e x
annotateBlock env = mapBlockList (annotateNode env)
annotateNode :: Env -> CmmNode e x -> Block CmmNode e x
annotateNode env node =
case node of
CmmEntry{} -> BlockCO node BNil
CmmComment{} -> BMiddle node
CmmTick{} -> BMiddle node
CmmUnwind{} -> BMiddle node
CmmAssign{} -> annotateNodeOO env node
CmmStore lhs rhs align ->
let ty = cmmExprType (platform env) rhs
rhs_nodes = annotateLoads env (collectExprLoads rhs)
lhs_nodes = annotateLoads env (collectExprLoads lhs)
st = tsanStore env align ty lhs
in rhs_nodes `blockAppend` lhs_nodes `blockAppend` st `blockSnoc` node
CmmUnsafeForeignCall (PrimTarget op) formals args ->
let node' = fromMaybe (BMiddle node) (annotatePrim env op formals args)
arg_nodes = blockConcat $ map (annotateExpr env) args
in arg_nodes `blockAppend` node'
CmmUnsafeForeignCall{} -> annotateNodeOO env node
CmmBranch{} -> annotateNodeOC env node
CmmCondBranch{} -> annotateNodeOC env node
CmmSwitch{} -> annotateNodeOC env node
CmmCall{} -> annotateNodeOC env node
CmmForeignCall{} -> annotateNodeOC env node
annotateNodeOO :: Env -> CmmNode O O -> Block CmmNode O O
annotateNodeOO env node =
annotateLoads env (collectLoadsNode node) `blockSnoc` node
annotateNodeOC :: Env -> CmmNode O C -> Block CmmNode O C
annotateNodeOC env node =
annotateLoads env (collectLoadsNode node) `blockJoinTail` node
annotateExpr :: Env -> CmmExpr -> Block CmmNode O O
annotateExpr env expr =
annotateLoads env (collectExprLoads expr)
data Load = Load CmmType AlignmentSpec CmmExpr
annotateLoads :: Env -> [Load] -> Block CmmNode O O
annotateLoads env loads =
blockConcat
[ tsanLoad env align ty addr
| Load ty align addr <- loads
]
collectLoadsNode :: CmmNode e x -> [Load]
collectLoadsNode node =
foldExp (\exp rest -> collectExprLoads exp ++ rest) node []
-- | Collect all of the memory locations loaded from by a 'CmmExpr'.
collectExprLoads :: CmmExpr -> [Load]
collectExprLoads (CmmLit _) = []
collectExprLoads (CmmLoad e ty align) = [Load ty align e]
collectExprLoads (CmmReg _) = []
collectExprLoads (CmmMachOp _op args) = foldMap collectExprLoads args
collectExprLoads (CmmStackSlot _ _) = []
collectExprLoads (CmmRegOff _ _) = []
-- | Generate TSAN instrumentation for a 'CallishMachOp' occurrence.
annotatePrim :: Env
-> CallishMachOp -- ^ the applied operation
-> [CmmFormal] -- ^ results
-> [CmmActual] -- ^ arguments
-> Maybe (Block CmmNode O O)
-- ^ 'Just' a block of instrumentation, if applicable
annotatePrim env (MO_AtomicRMW w aop) [dest] [addr, val] = Just $ tsanAtomicRMW env MemOrderSeqCst aop w addr val dest
annotatePrim env (MO_AtomicRead w mord) [dest] [addr] = Just $ tsanAtomicLoad env mord w addr dest
annotatePrim env (MO_AtomicWrite w mord) [] [addr, val] = Just $ tsanAtomicStore env mord w val addr
annotatePrim env (MO_Xchg w) [dest] [addr, val] = Just $ tsanAtomicExchange env MemOrderSeqCst w val addr dest
annotatePrim env (MO_Cmpxchg w) [dest] [addr, expected, new]
= Just $ tsanAtomicCas env MemOrderSeqCst MemOrderSeqCst w addr expected new dest
annotatePrim _ _ _ _ = Nothing
mkUnsafeCall :: Env
-> ForeignTarget -- ^ function
-> [CmmFormal] -- ^ results
-> [CmmActual] -- ^ arguments
-> Block CmmNode O O
mkUnsafeCall env ftgt formals args =
save `blockAppend` -- save global registers
bind_args `blockSnoc` -- bind arguments to local registers
call `blockAppend` -- perform call
restore -- restore global registers
where
-- We are rather conservative here and just save/restore all GlobalRegs.
(save, restore) = saveRestoreCallerRegs (platform env)
-- We also must be careful not to mention caller-saved registers in
-- arguments as Cmm-Lint checks this. To accomplish this we instead bind
-- the arguments to local registers.
arg_regs :: [CmmReg]
arg_regs = zipWith arg_reg (uniques env) args
where
arg_reg :: Unique -> CmmExpr -> CmmReg
arg_reg u expr = CmmLocal $ LocalReg u (cmmExprType (platform env) expr)
bind_args :: Block CmmNode O O
bind_args = blockConcat $ zipWith (\r e -> BMiddle $ CmmAssign r e) arg_regs args
call = CmmUnsafeForeignCall ftgt formals (map CmmReg arg_regs)
saveRestoreCallerRegs :: Platform
-> (Block CmmNode O O, Block CmmNode O O)
saveRestoreCallerRegs platform =
(save, restore)
where
regs = filter (callerSaves platform) (activeStgRegs platform)
save = blockFromList (map saveReg regs)
saveReg reg =
CmmStore (get_GlobalReg_addr platform reg)
(CmmReg (CmmGlobal reg))
NaturallyAligned
restore = blockFromList (map restoreReg regs)
restoreReg reg =
CmmAssign (CmmGlobal reg)
(CmmLoad (get_GlobalReg_addr platform reg)
(globalRegType platform reg)
NaturallyAligned)
-- | Mirrors __tsan_memory_order
-- <https://github.com/llvm-mirror/compiler-rt/blob/master/include/sanitizer/tsan_interface_atomic.h#L32>
memoryOrderToTsanMemoryOrder :: Env -> MemoryOrdering -> CmmExpr
memoryOrderToTsanMemoryOrder env mord =
mkIntExpr (platform env) n
where
n = case mord of
MemOrderRelaxed -> 0
MemOrderAcquire -> 2
MemOrderRelease -> 3
MemOrderSeqCst -> 5
tsanTarget :: FastString -- ^ function name
-> [ForeignHint] -- ^ formals
-> [ForeignHint] -- ^ arguments
-> ForeignTarget
tsanTarget fn formals args =
ForeignTarget (CmmLit (CmmLabel lbl)) conv
where
conv = ForeignConvention CCallConv args formals CmmMayReturn
lbl = mkForeignLabel fn Nothing ForeignLabelInExternalPackage IsFunction
tsanStore :: Env
-> AlignmentSpec -> CmmType -> CmmExpr
-> Block CmmNode O O
tsanStore env align ty addr =
mkUnsafeCall env ftarget [] [addr]
where
ftarget = tsanTarget fn [] [AddrHint]
w = widthInBytes (typeWidth ty)
fn = case align of
Unaligned
| w > 1 -> fsLit $ "__tsan_unaligned_write" ++ show w
_ -> fsLit $ "__tsan_write" ++ show w
tsanLoad :: Env
-> AlignmentSpec -> CmmType -> CmmExpr
-> Block CmmNode O O
tsanLoad env align ty addr =
mkUnsafeCall env ftarget [] [addr]
where
ftarget = tsanTarget fn [] [AddrHint]
w = widthInBytes (typeWidth ty)
fn = case align of
Unaligned
| w > 1 -> fsLit $ "__tsan_unaligned_read" ++ show w
_ -> fsLit $ "__tsan_read" ++ show w
tsanAtomicStore :: Env
-> MemoryOrdering -> Width -> CmmExpr -> CmmExpr
-> Block CmmNode O O
tsanAtomicStore env mord w val addr =
mkUnsafeCall env ftarget [] [addr, val, mord']
where
mord' = memoryOrderToTsanMemoryOrder env mord
ftarget = tsanTarget fn [] [AddrHint, NoHint, NoHint]
fn = fsLit $ "__tsan_atomic" ++ show (widthInBits w) ++ "_store"
tsanAtomicLoad :: Env
-> MemoryOrdering -> Width -> CmmExpr -> LocalReg
-> Block CmmNode O O
tsanAtomicLoad env mord w addr dest =
mkUnsafeCall env ftarget [dest] [addr, mord']
where
mord' = memoryOrderToTsanMemoryOrder env mord
ftarget = tsanTarget fn [NoHint] [AddrHint, NoHint]
fn = fsLit $ "__tsan_atomic" ++ show (widthInBits w) ++ "_load"
tsanAtomicExchange :: Env
-> MemoryOrdering -> Width -> CmmExpr -> CmmExpr -> LocalReg
-> Block CmmNode O O
tsanAtomicExchange env mord w val addr dest =
mkUnsafeCall env ftarget [dest] [addr, val, mord']
where
mord' = memoryOrderToTsanMemoryOrder env mord
ftarget = tsanTarget fn [NoHint] [AddrHint, NoHint, NoHint]
fn = fsLit $ "__tsan_atomic" ++ show (widthInBits w) ++ "_exchange"
-- N.B. C11 CAS returns a boolean (to avoid the ABA problem) whereas Cmm's CAS
-- returns the expected value. We use define a shim in the RTS to provide
-- Cmm's semantics using the TSAN C11 primitive.
tsanAtomicCas :: Env
-> MemoryOrdering -- ^ success ordering
-> MemoryOrdering -- ^ failure ordering
-> Width
-> CmmExpr -- ^ address
-> CmmExpr -- ^ expected value
-> CmmExpr -- ^ new value
-> LocalReg -- ^ result destination
-> Block CmmNode O O
tsanAtomicCas env mord_success mord_failure w addr expected new dest =
mkUnsafeCall env ftarget [dest] [addr, expected, new, mord_success', mord_failure']
where
mord_success' = memoryOrderToTsanMemoryOrder env mord_success
mord_failure' = memoryOrderToTsanMemoryOrder env mord_failure
ftarget = tsanTarget fn [NoHint] [AddrHint, NoHint, NoHint, NoHint, NoHint]
fn = fsLit $ "ghc_tsan_atomic" ++ show (widthInBits w) ++ "_compare_exchange"
tsanAtomicRMW :: Env
-> MemoryOrdering -> AtomicMachOp -> Width -> CmmExpr -> CmmExpr -> LocalReg
-> Block CmmNode O O
tsanAtomicRMW env mord op w addr val dest =
mkUnsafeCall env ftarget [dest] [addr, val, mord']
where
mord' = memoryOrderToTsanMemoryOrder env mord
ftarget = tsanTarget fn [NoHint] [AddrHint, NoHint, NoHint]
op' = case op of
AMO_Add -> "fetch_add"
AMO_Sub -> "fetch_sub"
AMO_And -> "fetch_and"
AMO_Nand -> "fetch_nand"
AMO_Or -> "fetch_or"
AMO_Xor -> "fetch_xor"
fn = fsLit $ "__tsan_atomic" ++ show (widthInBits w) ++ "_" ++ op'