Sebastian Graf pushed to branch wip/sg/enter-taggable-invariant at Glasgow Haskell Compiler / GHC
Commits:
-
550057da
by Simon Jakobi at 2026-07-31T19:15:49+02:00
-
a15d734e
by Sebastian Graf at 2026-07-31T19:15:49+02:00
-
447b76be
by Simon Jakobi at 2026-07-31T19:16:09+02:00
-
f844fada
by Simon Jakobi at 2026-07-31T19:16:09+02:00
-
0f1742f6
by Simon Jakobi at 2026-07-31T19:16:09+02:00
-
d9c6cc7e
by Simon Jakobi at 2026-07-31T19:16:09+02:00
-
00cefb86
by Simon Jakobi at 2026-07-31T19:16:30+02:00
-
b9c37398
by Simon Jakobi at 2026-07-31T19:16:30+02:00
20 changed files:
- + changelog.d/enter-taggable-invariant-23173
- compiler/GHC/Iface/Make.hs
- compiler/GHC/StgToCmm.hs
- compiler/GHC/StgToCmm/Utils.hs
- libraries/ghc-internal/include/RtsIfaceSymbols.h
- libraries/ghci/GHCi/ObjLink.hs
- rts/Prelude.h
- rts/RtsMessages.c
- rts/RtsSymbols.c
- rts/StgMiscClosures.cmm
- rts/include/rts/Messages.h
- rts/include/rts/RtsToHsIface.h
- rts/include/stg/MiscClosures.h
- rts/sm/Sanity.c
- rts/wasm/JSFFI.c
- testsuite/tests/codeGen/should_compile/T21710a.stderr
- + testsuite/tests/codeGen/should_run/T23173a.hs
- + testsuite/tests/codeGen/should_run/T23173a.stdout
- + testsuite/tests/codeGen/should_run/T23173a_A.hs
- testsuite/tests/codeGen/should_run/all.T
Changes:
| 1 | +section: codegen
|
|
| 2 | +synopsis: Pointers to boxed unlifted primitives (``ByteArray#``, ``Array#``,
|
|
| 3 | + ``MVar#``, ...) are now tagged, and entering a taggable normal form is
|
|
| 4 | + reported.
|
|
| 5 | +issues: #23173
|
|
| 6 | +mrs: !16259
|
|
| 7 | + |
|
| 8 | +description: {
|
|
| 9 | + References to boxed unlifted primitive values such as ``ByteArray#``,
|
|
| 10 | + ``Array#`` or ``MVar#`` now carry pointer tag 1, like single-constructor
|
|
| 11 | + data types. Together with this, GHC now enforces the invariant that the
|
|
| 12 | + entry code of a taggable normal form is unreachable: entering such a
|
|
| 13 | + closure prints a one-shot warning at runtime, or aborts the program when
|
|
| 14 | + the new RTS flag ``--fatal-enter-taggable`` is given.
|
|
| 15 | + |
|
| 16 | + ``foreign import prim`` callees receive unlifted boxed arguments untagged
|
|
| 17 | + and return unlifted boxed results with their pointer tag (1 for primitive
|
|
| 18 | + objects). C code that obtains an unlifted boxed value, e.g. an ``MVar#``,
|
|
| 19 | + through a ``StablePtr`` strips the tag before dereferencing the pointer.
|
|
| 20 | +} |
| ... | ... | @@ -68,7 +68,6 @@ import GHC.Types.TyThing |
| 68 | 68 | import GHC.Types.CompleteMatch
|
| 69 | 69 | import GHC.Types.Name.Cache
|
| 70 | 70 | |
| 71 | -import GHC.Utils.Outputable
|
|
| 72 | 71 | import GHC.Utils.Panic
|
| 73 | 72 | import GHC.Utils.Logger
|
| 74 | 73 | import GHC.Utils.Binary
|
| ... | ... | @@ -139,7 +138,12 @@ mkFullIface hsc_env partial_iface mb_stg_infos mb_cmm_infos stubs foreign_files |
| 139 | 138 | -- value must carry the value's pointer tag, which needs its LambdaFormInfo),
|
| 140 | 139 | -- not an inlining pragma. Attach it regardless of -fomit-interface-pragmas
|
| 141 | 140 | -- so imported value references are tagged at every optimisation level.
|
| 142 | - let decls = updateDecl (mi_decls partial_iface) mb_stg_infos mb_cmm_infos
|
|
| 141 | + -- (At -O0 the code generator only conveys the correctness-relevant
|
|
| 142 | + -- LFInfos; see generatedInfo in GHC.StgToCmm.) CAF-info and tag sigs
|
|
| 143 | + -- remain ordinary pragmas, omitted under -fomit-interface-pragmas.
|
|
| 144 | + let omit_prags = gopt Opt_OmitInterfacePragmas (hsc_dflags hsc_env)
|
|
| 145 | + mb_stg_infos' = if omit_prags then Nothing else mb_stg_infos
|
|
| 146 | + decls = updateDecl (mi_decls partial_iface) mb_stg_infos' omit_prags mb_cmm_infos
|
|
| 143 | 147 | |
| 144 | 148 | -- See Note [Foreign stubs and TH bytecode linking]
|
| 145 | 149 | mi_simplified_core <- for (mi_simplified_core partial_iface) $ \simpl_core -> do
|
| ... | ... | @@ -189,13 +193,15 @@ shareIface nc compressionLevel mi = do |
| 189 | 193 | initBinMemSize :: Int
|
| 190 | 194 | initBinMemSize = 1024 * 1024 -- 1 MB
|
| 191 | 195 | |
| 192 | -updateDecl :: [IfaceDecl] -> Maybe StgCgInfos -> Maybe CmmCgInfos -> [IfaceDecl]
|
|
| 193 | -updateDecl decls Nothing Nothing = decls
|
|
| 194 | -updateDecl decls m_stg_infos m_cmm_infos
|
|
| 196 | +updateDecl :: [IfaceDecl] -> Maybe StgCgInfos -> Bool -> Maybe CmmCgInfos -> [IfaceDecl]
|
|
| 197 | +updateDecl decls Nothing _ Nothing = decls
|
|
| 198 | +updateDecl decls m_stg_infos omit_prags m_cmm_infos
|
|
| 195 | 199 | = map update_decl decls
|
| 196 | 200 | where
|
| 197 | 201 | (non_cafs,lf_infos) = maybe (mempty, mempty)
|
| 198 | - (\cmm_info -> (ncs_nameSet (cgNonCafs cmm_info), cgLFInfos cmm_info))
|
|
| 202 | + (\cmm_info -> ( if omit_prags then mempty
|
|
| 203 | + else ncs_nameSet (cgNonCafs cmm_info)
|
|
| 204 | + , cgLFInfos cmm_info ))
|
|
| 199 | 205 | m_cmm_infos
|
| 200 | 206 | tag_sigs = fromMaybe mempty m_stg_infos
|
| 201 | 207 | |
| ... | ... | @@ -203,9 +209,8 @@ updateDecl decls m_stg_infos m_cmm_infos |
| 203 | 209 | | let not_caffy = elemNameSet nm non_cafs
|
| 204 | 210 | , let mb_lf_info = lookupNameEnv lf_infos nm
|
| 205 | 211 | , let sig = lookupNameEnv tag_sigs nm
|
| 206 | - -- NB: with LFInfo now attached at every optimisation level, a missing
|
|
| 207 | - -- LFInfo is unremarkable (e.g. at -O0), so we do not trace it here.
|
|
| 208 | - , warnPprTrace False "updateDecl" (text "Name without LFInfo:" <+> ppr nm) True
|
|
| 212 | + -- A missing LFInfo is unremarkable: at -O0 only the
|
|
| 213 | + -- correctness-relevant LFInfos are conveyed (see GHC.StgToCmm).
|
|
| 209 | 214 | -- Only allocate a new IfaceId if we're going to update the infos
|
| 210 | 215 | , isJust mb_lf_info || not_caffy || isJust sig
|
| 211 | 216 | = IfaceId nm ty details $
|
| ... | ... | @@ -21,9 +21,11 @@ import GHC.StgToCmm.Utils |
| 21 | 21 | import GHC.StgToCmm.Closure
|
| 22 | 22 | import GHC.StgToCmm.Config
|
| 23 | 23 | import GHC.StgToCmm.Ticky
|
| 24 | -import GHC.StgToCmm.Types (ModuleLFInfos)
|
|
| 24 | +import GHC.StgToCmm.Types (ModuleLFInfos, LambdaFormInfo(..))
|
|
| 25 | 25 | import GHC.StgToCmm.CgUtils (CgStream)
|
| 26 | 26 | |
| 27 | +import GHC.Platform.Profile (profileIsProfiling)
|
|
| 28 | + |
|
| 27 | 29 | import GHC.Cmm
|
| 28 | 30 | import GHC.Cmm.Utils
|
| 29 | 31 | import GHC.Cmm.CLabel
|
| ... | ... | @@ -137,11 +139,21 @@ codeGen logger tmpfs cfg (InfoTableProvMap denv _ _) tycons |
| 137 | 139 | !lf = cg_lf info
|
| 138 | 140 | |
| 139 | 141 | -- LFInfo is part of the STG-ABI (a reference to an imported value
|
| 140 | - -- must carry its pointer tag), not an inlining pragma, so collect
|
|
| 141 | - -- it for every binding regardless of -fomit-interface-pragmas.
|
|
| 142 | - -- (Only LFInfo is conveyed here, never unfoldings.)
|
|
| 142 | + -- must carry its pointer tag), not an inlining pragma, so even
|
|
| 143 | + -- under -fomit-interface-pragmas we must convey the LFInfos that
|
|
| 144 | + -- pointer-tagging correctness depends on: values without entry
|
|
| 145 | + -- code that may be entered (LFCon under the tag-test in
|
|
| 146 | + -- emitEnter; LFScalar/LFPrim never). Functions and thunks are
|
|
| 147 | + -- safely enterable, so their LFInfo remains a mere optimisation
|
|
| 148 | + -- and is omitted at -O0 to keep interfaces small.
|
|
| 149 | + keep_lf_info lf = case lf of
|
|
| 150 | + LFCon{} -> True
|
|
| 151 | + LFScalar -> True
|
|
| 152 | + LFPrim -> True
|
|
| 153 | + _ -> not (stgToCmmOmitIfPragmas cfg)
|
|
| 143 | 154 | !generatedInfo
|
| 144 | - = mkNameEnv (Prelude.map extractInfo (nonDetEltsUFM cg_id_infos))
|
|
| 155 | + = mkNameEnv [ i | i@(_, lf) <- Prelude.map extractInfo (nonDetEltsUFM cg_id_infos)
|
|
| 156 | + , keep_lf_info lf ]
|
|
| 145 | 157 | |
| 146 | 158 | ; rn_mapping <- liftIO (readIORef uniqRnRef)
|
| 147 | 159 | ; liftIO $ debugTraceMsg logger 3 (text "DetRnM mapping:" <+> ppr rn_mapping)
|
| ... | ... | @@ -370,15 +382,21 @@ cgDataCon mn data_con |
| 370 | 382 | ; tickyReturnOldCon (length arg_reps)
|
| 371 | 383 | -- A taggable (small-family) normal form should never be entered:
|
| 372 | 384 | -- every reference to it carries the constructor's pointer tag, so
|
| 373 | - -- reaching this entry code is an invariant violation. We report it
|
|
| 374 | - -- (aborting under +RTS --fatal-enter-taggable, otherwise warning once)
|
|
| 375 | - -- and then self-return the value tagged with the constructor tag.
|
|
| 385 | + -- reaching this entry code is an invariant violation. We jump to
|
|
| 386 | + -- a shared RTS stub that reports it (aborting under +RTS
|
|
| 387 | + -- --fatal-enter-taggable, otherwise warning once) and self-returns
|
|
| 388 | + -- the value tagged with the constructor tag; sharing the stub keeps
|
|
| 389 | + -- the per-constructor entry code to a single tail-jump.
|
|
| 376 | 390 | -- Larger families have no spare tag, so their values are entered
|
| 377 | 391 | -- as normal and the entry returns them tagged with the
|
| 378 | 392 | -- family-saturating tag.
|
| 379 | - ; when taggable $
|
|
| 380 | - emitCheckEnteredTaggable (showPprUnsafe data_con)
|
|
| 381 | - ; void $ emitReturn
|
|
| 382 | - [cmmOffsetB platform node (fromDynTag (tagForCon platform data_con))]
|
|
| 393 | + -- When profiling, entering tagged constructors is sanctioned:
|
|
| 394 | + -- LDV profiling relies on it to mark closures as used (ENTER()
|
|
| 395 | + -- in rts/include/Cmm.h does not shortcut on the tag), so the
|
|
| 396 | + -- check would fire on every constructor use.
|
|
| 397 | + ; if taggable && not (profileIsProfiling profile)
|
|
| 398 | + then emitJumpEnteredTaggable node
|
|
| 399 | + else void $ emitReturn
|
|
| 400 | + [cmmOffsetB platform node (fromDynTag (tagForCon platform data_con))]
|
|
| 383 | 401 | }
|
| 384 | 402 | } |
| ... | ... | @@ -10,7 +10,7 @@ |
| 10 | 10 | module GHC.StgToCmm.Utils (
|
| 11 | 11 | emitDataLits, emitRODataLits,
|
| 12 | 12 | emitDataCon,
|
| 13 | - emitRtsCall, emitRtsCallWithResult, emitRtsCallGen, emitCheckEnteredTaggable,
|
|
| 13 | + emitRtsCall, emitRtsCallWithResult, emitRtsCallGen, emitJumpEnteredTaggable,
|
|
| 14 | 14 | emitBarf,
|
| 15 | 15 | assignTemp, newTemp,
|
| 16 | 16 | |
| ... | ... | @@ -193,11 +193,16 @@ emitBarf msg = do |
| 193 | 193 | -- Call from a taggable normal form's entry code (which the pointer-tagging
|
| 194 | 194 | -- invariant makes unreachable). It aborts under +RTS --fatal-enter-taggable and
|
| 195 | 195 | -- otherwise warns once; the entry then self-returns the tagged value.
|
| 196 | -emitCheckEnteredTaggable :: String -> FCode ()
|
|
| 197 | -emitCheckEnteredTaggable con = do
|
|
| 198 | - strLbl <- newStringCLit con
|
|
| 199 | - emitRtsCall rtsUnitId (fsLit "checkEnteredTaggable")
|
|
| 200 | - [(CmmLit strLbl, AddrHint)] False
|
|
| 196 | +-- Tail-jump to the RTS's shared entry code for taggable normal forms
|
|
| 197 | +-- (stg_enteredTaggable in rts/StgMiscClosures.cmm), which reports the
|
|
| 198 | +-- invariant violation and self-returns the value tagged with its
|
|
| 199 | +-- constructor tag (both derived from the info table).
|
|
| 200 | +emitJumpEnteredTaggable :: CmmExpr -> FCode ()
|
|
| 201 | +emitJumpEnteredTaggable node = do
|
|
| 202 | + profile <- getProfile
|
|
| 203 | + updfr_off <- getUpdFrameOff
|
|
| 204 | + let lbl = mkCmmCodeLabel rtsUnitId (fsLit "stg_enteredTaggable")
|
|
| 205 | + emit (mkJump profile NativeNodeCall (CmmLit (CmmLabel lbl)) [node] updfr_off)
|
|
| 201 | 206 | |
| 202 | 207 | emitRtsCall :: UnitId -> FastString -> [(CmmExpr,ForeignHint)] -> Bool -> FCode ()
|
| 203 | 208 | emitRtsCall pkg fun = emitRtsCallGen [] (mkCmmCodeLabel pkg fun) CmmMayReturn
|
| ... | ... | @@ -59,6 +59,7 @@ CLOSURE(GHCziInternalziExceptionziType, underflowException_closure) |
| 59 | 59 | CLOSURE(GHCziInternalziExceptionziType, overflowException_closure)
|
| 60 | 60 | INFO_TBL(GHCziInternalziCString, unpackCStringzh_info)
|
| 61 | 61 | INFO_TBL(GHCziInternalziCString, unpackCStringUtf8zh_info)
|
| 62 | +INFO_TBL(GHCziInternalziHeapziClosures, Box_con_info)
|
|
| 62 | 63 | #if defined(wasm32_HOST_ARCH) && defined(__PIC__)
|
| 63 | 64 | CLOSURE(GHCziInternalziWasmziPrimziImports, raiseJSException_closure)
|
| 64 | 65 | INFO_TBL(GHCziInternalziWasmziPrimziTypes, JSVal_con_info)
|
| ... | ... | @@ -302,6 +302,10 @@ isWindowsHost = False |
| 302 | 302 | #endif
|
| 303 | 303 | |
| 304 | 304 | #if defined(wasm32_HOST_ARCH)
|
| 305 | +-- The wasm dynamic linker resolves symbols out of process, so the RTS
|
|
| 306 | +-- helper below is unavailable; looked-up constructor closures stay
|
|
| 307 | +-- untagged and forcing one triggers the (non-fatal) enter-taggable
|
|
| 308 | +-- warning.
|
|
| 305 | 309 | tagClosurePtr :: Ptr a -> Ptr a
|
| 306 | 310 | tagClosurePtr = id
|
| 307 | 311 | #else
|
| ... | ... | @@ -84,3 +84,4 @@ extern StgClosure ZCMain_main_closure; |
| 84 | 84 | #define FunPtr_con_info ghc_hs_iface->FunPtr_con_info
|
| 85 | 85 | #define StablePtr_static_info ghc_hs_iface->StablePtr_static_info
|
| 86 | 86 | #define StablePtr_con_info ghc_hs_iface->StablePtr_con_info
|
| 87 | +#define Box_con_info ghc_hs_iface->Box_con_info |
| ... | ... | @@ -88,9 +88,9 @@ checkEnteredTaggable(const char *con) |
| 88 | 88 | ssbarf("entered a taggable normal form: %s", con);
|
| 89 | 89 | // ssbarf does not return
|
| 90 | 90 | }
|
| 91 | - static int warned = 0;
|
|
| 92 | - if (!warned) {
|
|
| 93 | - warned = 1;
|
|
| 91 | + static StgWord warned = 0;
|
|
| 92 | + if (!RELAXED_LOAD(&warned)) {
|
|
| 93 | + RELAXED_STORE(&warned, 1);
|
|
| 94 | 94 | debugBelch("warning: entered a taggable normal form: %s\n"
|
| 95 | 95 | "(further occurrences suppressed; rerun with "
|
| 96 | 96 | "+RTS --fatal-enter-taggable to abort)\n",
|
| ... | ... | @@ -98,6 +98,16 @@ checkEnteredTaggable(const char *con) |
| 98 | 98 | }
|
| 99 | 99 | }
|
| 100 | 100 | |
| 101 | +// Backing for stg_enteredTaggable (StgMiscClosures.cmm), the shared entry
|
|
| 102 | +// code of taggable normal forms: report the violation and hand back the
|
|
| 103 | +// pointer retagged with its constructor tag so the entry can self-return.
|
|
| 104 | +StgClosure *
|
|
| 105 | +enteredTaggableClosure(StgClosure *p)
|
|
| 106 | +{
|
|
| 107 | + checkEnteredTaggable(GET_CON_DESC(get_con_itbl(p)));
|
|
| 108 | + return tagConstr(p);
|
|
| 109 | +}
|
|
| 110 | + |
|
| 101 | 111 | void
|
| 102 | 112 | _assertFail(const char*filename, unsigned int linenum)
|
| 103 | 113 | {
|
| ... | ... | @@ -542,7 +542,7 @@ extern char **environ; |
| 542 | 542 | SymI_HasProto(barf) \
|
| 543 | 543 | SymI_HasProto(sbarf) \
|
| 544 | 544 | SymI_HasProto(ssbarf) \
|
| 545 | - SymI_HasProto(checkEnteredTaggable) \
|
|
| 545 | + SymI_HasProto(stg_enteredTaggable) \
|
|
| 546 | 546 | SymI_HasProto(tagClosureIfConstr) \
|
| 547 | 547 | SymI_HasProto(startEventLogging) \
|
| 548 | 548 | SymI_HasProto(endEventLogging) \
|
| ... | ... | @@ -103,6 +103,19 @@ INFO_TABLE_RET (stg_restore_cccs_eval, RET_SMALL, W_ info_ptr, W_ cccs) |
| 103 | 103 | jump stg_ap_0_fast(ret);
|
| 104 | 104 | }
|
| 105 | 105 | |
| 106 | +/* Shared entry code for taggable normal forms, which the pointer-tagging
|
|
| 107 | + invariant makes unreachable: every taggable data constructor's entry code
|
|
| 108 | + tail-jumps here (see cgDataCon in GHC.StgToCmm) instead of carrying its own
|
|
| 109 | + report call. Reports the violation (aborting under +RTS
|
|
| 110 | + --fatal-enter-taggable, otherwise warning once) and self-returns the value
|
|
| 111 | + tagged with its constructor tag; name and tag come from the info table. */
|
|
| 112 | +stg_enteredTaggable (P_ node)
|
|
| 113 | +{
|
|
| 114 | + P_ tagged;
|
|
| 115 | + (tagged) = ccall enteredTaggableClosure(node "ptr");
|
|
| 116 | + return (tagged);
|
|
| 117 | +}
|
|
| 118 | + |
|
| 106 | 119 | /* ----------------------------------------------------------------------------
|
| 107 | 120 | Support for the bytecode interpreter.
|
| 108 | 121 | ------------------------------------------------------------------------- */
|
| ... | ... | @@ -49,11 +49,15 @@ void pbarf(const char *fmt, void *p) |
| 49 | 49 | void ssbarf(const char *fmt, const char *s)
|
| 50 | 50 | STG_NORETURN;
|
| 51 | 51 | |
| 52 | -/* Called from a taggable normal form's entry code (which the pointer-tagging
|
|
| 53 | - invariant makes unreachable). Aborts under +RTS --fatal-enter-taggable, otherwise
|
|
| 54 | - warns once and lets the entry self-return the tagged value. */
|
|
| 52 | +/* Report that a taggable normal form was entered (its entry code is
|
|
| 53 | + unreachable under the pointer-tagging invariant). Aborts under +RTS
|
|
| 54 | + --fatal-enter-taggable, otherwise warns once. */
|
|
| 55 | 55 | void checkEnteredTaggable(const char *con);
|
| 56 | 56 | |
| 57 | +/* Backing for stg_enteredTaggable: report the violation and return the
|
|
| 58 | + closure pointer retagged with its constructor tag. */
|
|
| 59 | +StgClosure *enteredTaggableClosure(StgClosure *p);
|
|
| 60 | + |
|
| 57 | 61 | // declared in Rts.h:
|
| 58 | 62 | // extern void _assertFail(const char *filename, unsigned int linenum)
|
| 59 | 63 | // STG_NORETURN;
|
| ... | ... | @@ -60,6 +60,7 @@ typedef struct { |
| 60 | 60 | StgClosure *overflowException_closure; // GHC.Internal.Exception.Type.overflowException_closure
|
| 61 | 61 | const StgInfoTable *unpackCStringzh_info; // GHC.Internal.CString.unpackCStringzh_info
|
| 62 | 62 | const StgInfoTable *unpackCStringUtf8zh_info; // GHC.Internal.CString.unpackCStringUtf8zh_info
|
| 63 | + const StgInfoTable *Box_con_info; // GHC.Internal.Heap.Closures.Box_con_info
|
|
| 63 | 64 | #if defined(wasm32_HOST_ARCH)
|
| 64 | 65 | StgClosure *raiseJSException_closure; // GHC.Internal.Wasm.Prim.Imports.raiseJSException_closure
|
| 65 | 66 | const StgInfoTable *JSVal_con_info; // GHC.Internal.Wasm.Prim.Types.JSVal_con_info
|
| ... | ... | @@ -477,6 +477,7 @@ RTS_FUN_DECL(stg_raiseIOzh); |
| 477 | 477 | RTS_FUN_DECL(stg_paniczh);
|
| 478 | 478 | RTS_FUN_DECL(stg_keepAlivezh);
|
| 479 | 479 | RTS_FUN_DECL(stg_absentErrorzh);
|
| 480 | +RTS_FUN_DECL(stg_enteredTaggable);
|
|
| 480 | 481 | |
| 481 | 482 | RTS_FUN_DECL(stg_newPromptTagzh);
|
| 482 | 483 | RTS_FUN_DECL(stg_promptzh);
|
| ... | ... | @@ -25,6 +25,7 @@ |
| 25 | 25 | #include "Sanity.h"
|
| 26 | 26 | #include "Schedule.h"
|
| 27 | 27 | #include "Apply.h"
|
| 28 | +#include "Prelude.h"
|
|
| 28 | 29 | #include "Printer.h"
|
| 29 | 30 | #include "Arena.h"
|
| 30 | 31 | #include "RetainerProfile.h"
|
| ... | ... | @@ -42,6 +43,7 @@ int isHeapAlloced ( StgPtr p); |
| 42 | 43 | static void checkSmallBitmap ( StgPtr payload, StgWord bitmap, uint32_t );
|
| 43 | 44 | static void checkLargeBitmap ( StgPtr payload, StgLargeBitmap*, uint32_t );
|
| 44 | 45 | static void checkClosureShallow ( const StgClosure * );
|
| 46 | +static void checkPtrTag ( const StgClosure *, bool );
|
|
| 45 | 47 | |
| 46 | 48 | static void checkCompactObjects (bdescr *bd);
|
| 47 | 49 | |
| ... | ... | @@ -72,6 +74,7 @@ checkSmallBitmap( StgPtr payload, StgWord bitmap, uint32_t size ) |
| 72 | 74 | for(i = 0; i < size; i++, bitmap >>= 1 ) {
|
| 73 | 75 | if ((bitmap & 1) == 0) {
|
| 74 | 76 | checkClosureShallow((StgClosure *)payload[i]);
|
| 77 | + checkPtrTag((StgClosure *)payload[i], false);
|
|
| 75 | 78 | }
|
| 76 | 79 | }
|
| 77 | 80 | }
|
| ... | ... | @@ -89,11 +92,126 @@ checkLargeBitmap( StgPtr payload, StgLargeBitmap* large_bitmap, uint32_t size ) |
| 89 | 92 | for(; i < size && j < BITS_IN(W_); j++, i++, bitmap >>= 1 ) {
|
| 90 | 93 | if ((bitmap & 1) == 0) {
|
| 91 | 94 | checkClosureShallow((StgClosure *)payload[i]);
|
| 95 | + checkPtrTag((StgClosure *)payload[i], false);
|
|
| 92 | 96 | }
|
| 93 | 97 | }
|
| 94 | 98 | }
|
| 95 | 99 | }
|
| 96 | 100 | |
| 101 | +/* Note [Sanity-checking pointer tags]
|
|
| 102 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 103 | + * checkPtrTag asserts the pointer-tagging invariant (#23173) at rest: a
|
|
| 104 | + * pointer to a constructor carries its constructor tag (see tagConstr in
|
|
| 105 | + * ClosureMacros.h and get_iptr_tag in sm/Compact.c), and a pointer to a boxed
|
|
| 106 | + * unlifted primitive (MVar#, MutVar#, the arrays, ...) carries tag 1 (see
|
|
| 107 | + * Note [Pointer tagging of unlifted boxed primitives] in GHC.StgToCmm.Prim).
|
|
| 108 | + * The invariant is otherwise enforced only by crashing entry code, which
|
|
| 109 | + * catches a stripped tag only if the pointer is subsequently entered; this
|
|
| 110 | + * check catches tag-stripping pointer-rewriting paths (Evac, Compact,
|
|
| 111 | + * NonMovingShortcut, ...) mechanically on every sanity-checked GC.
|
|
| 112 | + *
|
|
| 113 | + * It is called only on user-level fields (stack bitmap slots, PAP/AP
|
|
| 114 | + * payloads, constructor/fun/thunk payloads, array elements, MutVar/TVar/MVar
|
|
| 115 | + * values, IND indirectees), because the RTS also holds internal untagged
|
|
| 116 | + * links. The rules exempt:
|
|
| 117 | + *
|
|
| 118 | + * - static constructors: RTS sentinels (stg_END_TSO_QUEUE_closure, ...) are
|
|
| 119 | + * CONSTR_NOCAFs that C code stores untagged, e.g. as an empty MVar's
|
|
| 120 | + * value, so only heap-allocated constructors are checked;
|
|
| 121 | + *
|
|
| 122 | + * - large-family constructors (con_tag >= TAG_MASK): tag is capped at
|
|
| 123 | + * TAG_MASK, so no exact requirement is asserted;
|
|
| 124 | + *
|
|
| 125 | + * - WEAK, TSO, STACK, BLOCKING_QUEUE, PRIM, MUT_PRIM: user-level references
|
|
| 126 | + * (Weak#, ThreadId#, ...) to these are tagged, but legitimate untagged
|
|
| 127 | + * RTS-internal links (weak_ptr_list, run queues, tso->_link, STM
|
|
| 128 | + * structures) reach the same traversals;
|
|
| 129 | + *
|
|
| 130 | + * - C_FINALIZER_LIST nodes: although their info table is a CONSTR, they
|
|
| 131 | + * are RTS-internal. All references to them — StgWeak.cfinalizers and the
|
|
| 132 | + * nodes' link fields — are untagged links built by stg_addCFinalizerToWeakzh
|
|
| 133 | + * (PrimOps.cmm) and walked raw by runCFinalizers (Weak.c); user code never
|
|
| 134 | + * holds a reference to one. (The compacting GC preserves untaggedness:
|
|
| 135 | + * unthread re-applies get_iptr_tag only to originally-tagged references.)
|
|
| 136 | + *
|
|
| 137 | + * - fields of ghc-heap's Box (GHC.Internal.Heap.Closures): `data Box = Box
|
|
| 138 | + * Any` wraps a pointer word captured verbatim by heap/stack introspection
|
|
| 139 | + * (unpackClosure#, ghc-heap's stack decoding), so it carries whatever tag
|
|
| 140 | + * the source bits had — possibly none. Box is recognized via
|
|
| 141 | + * ghc_hs_iface->Box_con_info, NULL-guarded since sanity checks can run
|
|
| 142 | + * before ghc-internal registers the interface;
|
|
| 143 | + *
|
|
| 144 | + * - BLACKHOLE indirectees (no call site on that field): tag 0 there means
|
|
| 145 | + * "not yet updated". Plain IND indirectees are checked;
|
|
| 146 | + *
|
|
| 147 | + * - bitmap-walked slots (stack frames, PAP/AP payloads; heap_field =
|
|
| 148 | + * false): hand-written Cmm legitimately stores untagged pointers there.
|
|
| 149 | + * Codegen untags unlifted boxed primop arguments at the Cmm call
|
|
| 150 | + * boundary, and generic RTS frames save those already-untagged arguments
|
|
| 151 | + * on the stack (the stg_block_{take,read,put}mvar frames and the
|
|
| 152 | + * stg_gc_prim_* heap-check-retry frames in HeapStackCheck.cmm); Cmm code
|
|
| 153 | + * also keeps deliberately untagged working pointers live across calls
|
|
| 154 | + * (e.g. stg_compactAddWorkerzh's "p"), landing them in return-frame
|
|
| 155 | + * slots. Such slots hence get no constructor rule, and the unlifted-
|
|
| 156 | + * primitive rule is relaxed to tag 0-or-1 (still catching corrupt tags).
|
|
| 157 | + * The strict rules apply to heap fields, where all the tag-stripping GC
|
|
| 158 | + * bugs lived.
|
|
| 159 | + */
|
|
| 160 | +static void
|
|
| 161 | +checkPtrTag( const StgClosure *q, bool heap_field )
|
|
| 162 | +{
|
|
| 163 | + const StgClosure *p = UNTAG_CONST_CLOSURE(q);
|
|
| 164 | + const StgInfoTable *raw_info = ACQUIRE_LOAD(&p->header.info);
|
|
| 165 | + if (IS_FORWARDING_PTR(raw_info)) return;
|
|
| 166 | + const StgInfoTable *info = INFO_PTR_TO_STRUCT(raw_info);
|
|
| 167 | + |
|
| 168 | + switch (info->type) {
|
|
| 169 | + case CONSTR:
|
|
| 170 | + case CONSTR_1_0:
|
|
| 171 | + case CONSTR_0_1:
|
|
| 172 | + case CONSTR_2_0:
|
|
| 173 | + case CONSTR_1_1:
|
|
| 174 | + case CONSTR_0_2:
|
|
| 175 | + case CONSTR_NOCAF:
|
|
| 176 | + {
|
|
| 177 | + // RTS-internal untagged links; see the C_FINALIZER_LIST bullet in
|
|
| 178 | + // Note [Sanity-checking pointer tags].
|
|
| 179 | + if (raw_info == &stg_C_FINALIZER_LIST_info) {
|
|
| 180 | + break;
|
|
| 181 | + }
|
|
| 182 | + StgWord con_tag = (StgWord)info->srt + 1;
|
|
| 183 | + if (heap_field && con_tag <= TAG_MASK && HEAP_ALLOCED((StgPtr)p)) {
|
|
| 184 | + ASSERT(GET_CLOSURE_TAG(q) == con_tag);
|
|
| 185 | + }
|
|
| 186 | + break;
|
|
| 187 | + }
|
|
| 188 | + |
|
| 189 | + case ARR_WORDS:
|
|
| 190 | + case MUT_ARR_PTRS_CLEAN:
|
|
| 191 | + case MUT_ARR_PTRS_DIRTY:
|
|
| 192 | + case MUT_ARR_PTRS_FROZEN_CLEAN:
|
|
| 193 | + case MUT_ARR_PTRS_FROZEN_DIRTY:
|
|
| 194 | + case SMALL_MUT_ARR_PTRS_CLEAN:
|
|
| 195 | + case SMALL_MUT_ARR_PTRS_DIRTY:
|
|
| 196 | + case SMALL_MUT_ARR_PTRS_FROZEN_CLEAN:
|
|
| 197 | + case SMALL_MUT_ARR_PTRS_FROZEN_DIRTY:
|
|
| 198 | + case MUT_VAR_CLEAN:
|
|
| 199 | + case MUT_VAR_DIRTY:
|
|
| 200 | + case MVAR_CLEAN:
|
|
| 201 | + case MVAR_DIRTY:
|
|
| 202 | + case TVAR:
|
|
| 203 | + if (heap_field) {
|
|
| 204 | + ASSERT(GET_CLOSURE_TAG(q) == 1);
|
|
| 205 | + } else {
|
|
| 206 | + ASSERT(GET_CLOSURE_TAG(q) <= 1);
|
|
| 207 | + }
|
|
| 208 | + break;
|
|
| 209 | + |
|
| 210 | + default:
|
|
| 211 | + break;
|
|
| 212 | + }
|
|
| 213 | +}
|
|
| 214 | + |
|
| 97 | 215 | /*
|
| 98 | 216 | * check that it looks like a valid closure - without checking its payload
|
| 99 | 217 | * used to avoid recursion between checking PAPs and checking stack
|
| ... | ... | @@ -102,6 +220,8 @@ checkLargeBitmap( StgPtr payload, StgLargeBitmap* large_bitmap, uint32_t size ) |
| 102 | 220 | static void
|
| 103 | 221 | checkClosureShallow( const StgClosure* p )
|
| 104 | 222 | {
|
| 223 | + // No checkPtrTag here: checkCompactObjects calls this on raw
|
|
| 224 | + // (necessarily untagged) object addresses, not on stored pointers.
|
|
| 105 | 225 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(UNTAG_CONST_CLOSURE(p)));
|
| 106 | 226 | }
|
| 107 | 227 | |
| ... | ... | @@ -129,10 +249,12 @@ checkStackFrame( StgPtr c ) |
| 129 | 249 | case STOP_FRAME:
|
| 130 | 250 | case RET_SMALL:
|
| 131 | 251 | case ANN_FRAME:
|
| 252 | + {
|
|
| 132 | 253 | size = BITMAP_SIZE(info->i.layout.bitmap);
|
| 133 | 254 | checkSmallBitmap((StgPtr)c + 1,
|
| 134 | 255 | BITMAP_BITS(info->i.layout.bitmap), size);
|
| 135 | 256 | return 1 + size;
|
| 257 | + }
|
|
| 136 | 258 | |
| 137 | 259 | case RET_BCO: {
|
| 138 | 260 | StgBCO *bco;
|
| ... | ... | @@ -377,6 +499,8 @@ checkClosure( const StgClosure* p ) |
| 377 | 499 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(mvar->head));
|
| 378 | 500 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(mvar->tail));
|
| 379 | 501 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(mvar->value));
|
| 502 | + // head/tail are RTS-internal TSO queue links; only value is user-level
|
|
| 503 | + checkPtrTag(mvar->value, true);
|
|
| 380 | 504 | return sizeofW(StgMVar);
|
| 381 | 505 | }
|
| 382 | 506 | |
| ... | ... | @@ -390,6 +514,7 @@ checkClosure( const StgClosure* p ) |
| 390 | 514 | uint32_t i;
|
| 391 | 515 | for (i = 0; i < info->layout.payload.ptrs; i++) {
|
| 392 | 516 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(((StgThunk *)p)->payload[i]));
|
| 517 | + checkPtrTag(((StgThunk *)p)->payload[i], true);
|
|
| 393 | 518 | }
|
| 394 | 519 | return thunk_sizeW_fromITBL(info);
|
| 395 | 520 | }
|
| ... | ... | @@ -407,14 +532,33 @@ checkClosure( const StgClosure* p ) |
| 407 | 532 | case CONSTR_1_1:
|
| 408 | 533 | case CONSTR_0_2:
|
| 409 | 534 | case CONSTR_2_0:
|
| 410 | - case BLACKHOLE:
|
|
| 411 | - case PRIM:
|
|
| 412 | - case MUT_PRIM:
|
|
| 413 | 535 | case MUT_VAR_CLEAN:
|
| 414 | 536 | case MUT_VAR_DIRTY:
|
| 415 | 537 | case TVAR:
|
| 416 | 538 | case THUNK_STATIC:
|
| 417 | 539 | case FUN_STATIC:
|
| 540 | + {
|
|
| 541 | + // ghc-heap's Box holds a raw captured pointer word; see the Box
|
|
| 542 | + // bullet in Note [Sanity-checking pointer tags].
|
|
| 543 | + bool box = ghc_hs_iface != NULL
|
|
| 544 | + && ACQUIRE_LOAD(&p->header.info) == Box_con_info;
|
|
| 545 | + uint32_t i;
|
|
| 546 | + for (i = 0; i < info->layout.payload.ptrs; i++) {
|
|
| 547 | + ASSERT(LOOKS_LIKE_CLOSURE_PTR(p->payload[i]));
|
|
| 548 | + if (!box) {
|
|
| 549 | + checkPtrTag(p->payload[i], true);
|
|
| 550 | + }
|
|
| 551 | + }
|
|
| 552 | + return sizeW_fromITBL(info);
|
|
| 553 | + }
|
|
| 554 | + |
|
| 555 | + // As above, but without checkPtrTag: a BLACKHOLE indirectee legitimately
|
|
| 556 | + // carries tag 0 ("not yet updated"), and PRIM/MUT_PRIM/COMPACT_NFDATA
|
|
| 557 | + // payloads are RTS-internal links.
|
|
| 558 | + // See Note [Sanity-checking pointer tags].
|
|
| 559 | + case BLACKHOLE:
|
|
| 560 | + case PRIM:
|
|
| 561 | + case MUT_PRIM:
|
|
| 418 | 562 | case COMPACT_NFDATA:
|
| 419 | 563 | {
|
| 420 | 564 | uint32_t i;
|
| ... | ... | @@ -480,6 +624,7 @@ checkClosure( const StgClosure* p ) |
| 480 | 624 | */
|
| 481 | 625 | StgInd *ind = (StgInd *)p;
|
| 482 | 626 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(ind->indirectee));
|
| 627 | + checkPtrTag(ind->indirectee, true);
|
|
| 483 | 628 | return sizeofW(StgInd);
|
| 484 | 629 | }
|
| 485 | 630 | |
| ... | ... | @@ -529,6 +674,7 @@ checkClosure( const StgClosure* p ) |
| 529 | 674 | uint32_t i;
|
| 530 | 675 | for (i = 0; i < a->ptrs; i++) {
|
| 531 | 676 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(a->payload[i]));
|
| 677 | + checkPtrTag(a->payload[i], true);
|
|
| 532 | 678 | }
|
| 533 | 679 | return mut_arr_ptrs_sizeW(a);
|
| 534 | 680 | }
|
| ... | ... | @@ -541,6 +687,7 @@ checkClosure( const StgClosure* p ) |
| 541 | 687 | StgSmallMutArrPtrs *a = (StgSmallMutArrPtrs *)p;
|
| 542 | 688 | for (uint32_t i = 0; i < a->ptrs; i++) {
|
| 543 | 689 | ASSERT(LOOKS_LIKE_CLOSURE_PTR(a->payload[i]));
|
| 690 | + checkPtrTag(a->payload[i], true);
|
|
| 544 | 691 | }
|
| 545 | 692 | return small_mut_arr_ptrs_sizeW(a);
|
| 546 | 693 | }
|
| ... | ... | @@ -297,7 +297,9 @@ __attribute__((export_name("rts_promiseThrowTo"))) |
| 297 | 297 | void rts_promiseThrowTo(HsStablePtr, HsJSVal);
|
| 298 | 298 | void rts_promiseThrowTo(HsStablePtr sp, HsJSVal js_err) {
|
| 299 | 299 | Capability *cap = &MainCapability;
|
| 300 | - StgWeak *w = (StgWeak *)deRefStablePtr(sp);
|
|
| 300 | + // Weak# pointers carry tag 1; see Note [Pointer tagging of unlifted boxed
|
|
| 301 | + // primitives] in GHC.StgToCmm.Prim. (The key field is stored untagged.)
|
|
| 302 | + StgWeak *w = (StgWeak *)UNTAG_CLOSURE((StgClosure *)deRefStablePtr(sp));
|
|
| 301 | 303 | if (w->header.info == &stg_DEAD_WEAK_info) {
|
| 302 | 304 | return;
|
| 303 | 305 | }
|
| ... | ... | @@ -53,35 +53,34 @@ |
| 53 | 53 | }
|
| 54 | 54 | {offset
|
| 55 | 55 | cqw: // global
|
| 56 | - if ((Sp + -8) < SpLim) (likely: False) goto cqx; else goto cqy; // CmmCondBranch
|
|
| 56 | + if ((Sp + -8) < SpLim) (likely: False) goto cqx; else goto cqy;
|
|
| 57 | 57 | cqx: // global
|
| 58 | - R1 = M.foo_closure; // CmmAssign
|
|
| 59 | - call (stg_gc_fun)(R2, R1) args: 8, res: 0, upd: 8; // CmmCall
|
|
| 58 | + R1 = M.foo_closure;
|
|
| 59 | + call (stg_gc_fun)(R2, R1) args: 8, res: 0, upd: 8;
|
|
| 60 | 60 | cqy: // global
|
| 61 | - I64[Sp - 8] = cqo; // CmmStore
|
|
| 62 | - R1 = R2; // CmmAssign
|
|
| 63 | - Sp = Sp - 8; // CmmAssign
|
|
| 64 | - if (R1 & 7 != 0) goto cqo; else goto cqp; // CmmCondBranch
|
|
| 61 | + I64[Sp - 8] = cqo;
|
|
| 62 | + R1 = R2;
|
|
| 63 | + Sp = Sp - 8;
|
|
| 64 | + if (R1 & 7 != 0) goto cqo; else goto cqp;
|
|
| 65 | 65 | cqp: // global
|
| 66 | - call (I64![R1])(R1) returns to cqo, args: 8, res: 8, upd: 8; // CmmCall
|
|
| 66 | + call (I64![R1])(R1) returns to cqo, args: 8, res: 8, upd: 8;
|
|
| 67 | 67 | cqo: // global
|
| 68 | - _cqv::P64 = R1 & 7; // CmmAssign
|
|
| 69 | - if (_cqv::P64 != 1) goto n0; else goto cqt; // CmmCondBranch
|
|
| 68 | + _cqv::P64 = R1 & 7;
|
|
| 69 | + if (_cqv::P64 != 1) goto n0; else goto cqt;
|
|
| 70 | 70 | n0: // global
|
| 71 | - if (_cqv::P64 != 2) goto cqs; else goto cqu; // CmmCondBranch
|
|
| 71 | + if (_cqv::P64 != 2) goto cqs; else goto cqu;
|
|
| 72 | 72 | cqs: // global
|
| 73 | - // dataToTagSmall#
|
|
| 74 | - R1 = R1 & 7 - 1; // CmmAssign
|
|
| 75 | - Sp = Sp + 8; // CmmAssign
|
|
| 76 | - call (P64[Sp])(R1) args: 8, res: 0, upd: 8; // CmmCall
|
|
| 73 | + R1 = R1 & 7 - 1;
|
|
| 74 | + Sp = Sp + 8;
|
|
| 75 | + call (P64[Sp])(R1) args: 8, res: 0, upd: 8;
|
|
| 77 | 76 | cqu: // global
|
| 78 | - R1 = 42; // CmmAssign
|
|
| 79 | - Sp = Sp + 8; // CmmAssign
|
|
| 80 | - call (P64[Sp])(R1) args: 8, res: 0, upd: 8; // CmmCall
|
|
| 77 | + R1 = 42;
|
|
| 78 | + Sp = Sp + 8;
|
|
| 79 | + call (P64[Sp])(R1) args: 8, res: 0, upd: 8;
|
|
| 81 | 80 | cqt: // global
|
| 82 | - R1 = 2; // CmmAssign
|
|
| 83 | - Sp = Sp + 8; // CmmAssign
|
|
| 84 | - call (P64[Sp])(R1) args: 8, res: 0, upd: 8; // CmmCall
|
|
| 81 | + R1 = 2;
|
|
| 82 | + Sp = Sp + 8;
|
|
| 83 | + call (P64[Sp])(R1) args: 8, res: 0, upd: 8;
|
|
| 85 | 84 | }
|
| 86 | 85 | },
|
| 87 | 86 | section ""data" . M.foo_closure" {
|
| ... | ... | @@ -92,27 +91,7 @@ |
| 92 | 91 | |
| 93 | 92 | |
| 94 | 93 | ==================== Output Cmm ====================
|
| 95 | -[section ""cstring" . cqJ_str" {
|
|
| 96 | - cqJ_str:
|
|
| 97 | - I8[] "A"
|
|
| 98 | - },
|
|
| 99 | - section ""cstring" . cqL_str" {
|
|
| 100 | - cqL_str:
|
|
| 101 | - I8[] "B"
|
|
| 102 | - },
|
|
| 103 | - section ""cstring" . cqN_str" {
|
|
| 104 | - cqN_str:
|
|
| 105 | - I8[] "C"
|
|
| 106 | - },
|
|
| 107 | - section ""cstring" . cqP_str" {
|
|
| 108 | - cqP_str:
|
|
| 109 | - I8[] "D"
|
|
| 110 | - },
|
|
| 111 | - section ""cstring" . cqR_str" {
|
|
| 112 | - cqR_str:
|
|
| 113 | - I8[] "E"
|
|
| 114 | - },
|
|
| 115 | - section ""relreadonly" . M.E_closure_tbl" {
|
|
| 94 | +[section ""relreadonly" . M.E_closure_tbl" {
|
|
| 116 | 95 | M.E_closure_tbl:
|
| 117 | 96 | const M.A_closure+1;
|
| 118 | 97 | const M.B_closure+2;
|
| ... | ... | @@ -121,73 +100,63 @@ |
| 121 | 100 | const M.E_closure+5;
|
| 122 | 101 | },
|
| 123 | 102 | M.A_con_entry() { // []
|
| 124 | - { info_tbls: [(cqK,
|
|
| 103 | + { info_tbls: [(cqJ,
|
|
| 125 | 104 | label: M.A_con_info
|
| 126 | 105 | rep: HeapRep 1 nonptrs { Con {tag: 0 descr:"main:M.A"} }
|
| 127 | 106 | srt: Nothing)]
|
| 128 | 107 | stack_info: arg_space: 8
|
| 129 | 108 | }
|
| 130 | 109 | {offset
|
| 131 | - cqK: // global
|
|
| 132 | - call "ccall" arg hints: [PtrHint] result hints: [] checkEnteredTaggable(cqJ_str); // CmmUnsafeForeignCall
|
|
| 133 | - R1 = R1 + 1; // CmmAssign
|
|
| 134 | - call (P64[Sp])(R1) args: 8, res: 0, upd: 8; // CmmCall
|
|
| 110 | + cqJ: // global
|
|
| 111 | + call stg_enteredTaggable(R1) args: 8, res: 0, upd: 8;
|
|
| 135 | 112 | }
|
| 136 | 113 | },
|
| 137 | 114 | M.B_con_entry() { // []
|
| 138 | - { info_tbls: [(cqM,
|
|
| 115 | + { info_tbls: [(cqK,
|
|
| 139 | 116 | label: M.B_con_info
|
| 140 | 117 | rep: HeapRep 1 nonptrs { Con {tag: 1 descr:"main:M.B"} }
|
| 141 | 118 | srt: Nothing)]
|
| 142 | 119 | stack_info: arg_space: 8
|
| 143 | 120 | }
|
| 144 | 121 | {offset
|
| 145 | - cqM: // global
|
|
| 146 | - call "ccall" arg hints: [PtrHint] result hints: [] checkEnteredTaggable(cqL_str); // CmmUnsafeForeignCall
|
|
| 147 | - R1 = R1 + 2; // CmmAssign
|
|
| 148 | - call (P64[Sp])(R1) args: 8, res: 0, upd: 8; // CmmCall
|
|
| 122 | + cqK: // global
|
|
| 123 | + call stg_enteredTaggable(R1) args: 8, res: 0, upd: 8;
|
|
| 149 | 124 | }
|
| 150 | 125 | },
|
| 151 | 126 | M.C_con_entry() { // []
|
| 152 | - { info_tbls: [(cqO,
|
|
| 127 | + { info_tbls: [(cqL,
|
|
| 153 | 128 | label: M.C_con_info
|
| 154 | 129 | rep: HeapRep 1 nonptrs { Con {tag: 2 descr:"main:M.C"} }
|
| 155 | 130 | srt: Nothing)]
|
| 156 | 131 | stack_info: arg_space: 8
|
| 157 | 132 | }
|
| 158 | 133 | {offset
|
| 159 | - cqO: // global
|
|
| 160 | - call "ccall" arg hints: [PtrHint] result hints: [] checkEnteredTaggable(cqN_str); // CmmUnsafeForeignCall
|
|
| 161 | - R1 = R1 + 3; // CmmAssign
|
|
| 162 | - call (P64[Sp])(R1) args: 8, res: 0, upd: 8; // CmmCall
|
|
| 134 | + cqL: // global
|
|
| 135 | + call stg_enteredTaggable(R1) args: 8, res: 0, upd: 8;
|
|
| 163 | 136 | }
|
| 164 | 137 | },
|
| 165 | 138 | M.D_con_entry() { // []
|
| 166 | - { info_tbls: [(cqQ,
|
|
| 139 | + { info_tbls: [(cqM,
|
|
| 167 | 140 | label: M.D_con_info
|
| 168 | 141 | rep: HeapRep 1 nonptrs { Con {tag: 3 descr:"main:M.D"} }
|
| 169 | 142 | srt: Nothing)]
|
| 170 | 143 | stack_info: arg_space: 8
|
| 171 | 144 | }
|
| 172 | 145 | {offset
|
| 173 | - cqQ: // global
|
|
| 174 | - call "ccall" arg hints: [PtrHint] result hints: [] checkEnteredTaggable(cqP_str); // CmmUnsafeForeignCall
|
|
| 175 | - R1 = R1 + 4; // CmmAssign
|
|
| 176 | - call (P64[Sp])(R1) args: 8, res: 0, upd: 8; // CmmCall
|
|
| 146 | + cqM: // global
|
|
| 147 | + call stg_enteredTaggable(R1) args: 8, res: 0, upd: 8;
|
|
| 177 | 148 | }
|
| 178 | 149 | },
|
| 179 | 150 | M.E_con_entry() { // []
|
| 180 | - { info_tbls: [(cqS,
|
|
| 151 | + { info_tbls: [(cqN,
|
|
| 181 | 152 | label: M.E_con_info
|
| 182 | 153 | rep: HeapRep 1 nonptrs { Con {tag: 4 descr:"main:M.E"} }
|
| 183 | 154 | srt: Nothing)]
|
| 184 | 155 | stack_info: arg_space: 8
|
| 185 | 156 | }
|
| 186 | 157 | {offset
|
| 187 | - cqS: // global
|
|
| 188 | - call "ccall" arg hints: [PtrHint] result hints: [] checkEnteredTaggable(cqR_str); // CmmUnsafeForeignCall
|
|
| 189 | - R1 = R1 + 5; // CmmAssign
|
|
| 190 | - call (P64[Sp])(R1) args: 8, res: 0, upd: 8; // CmmCall
|
|
| 158 | + cqN: // global
|
|
| 159 | + call stg_enteredTaggable(R1) args: 8, res: 0, upd: 8;
|
|
| 191 | 160 | }
|
| 192 | 161 | }]
|
| 193 | 162 |
| 1 | +module Main where
|
|
| 2 | + |
|
| 3 | +import T23173a_A
|
|
| 4 | + |
|
| 5 | +-- Cases on an imported evaluated constructor at -O0. With LFCon conveyed in
|
|
| 6 | +-- the interface the reference is tagged and never entered; without it the
|
|
| 7 | +-- scrutinee is entered and --fatal-enter-taggable aborts.
|
|
| 8 | +main :: IO ()
|
|
| 9 | +main = case x of
|
|
| 10 | + Just b -> print b
|
|
| 11 | + Nothing -> putStrLn "nothing" |
| 1 | +True |
| 1 | +module T23173a_A where
|
|
| 2 | + |
|
| 3 | +-- A statically evaluated constructor value. Its interface must carry LFCon
|
|
| 4 | +-- even at -O0 (where -fomit-interface-pragmas is on), so importers tag
|
|
| 5 | +-- references to it. See Note [Pointer tagging of unlifted boxed primitives]
|
|
| 6 | +-- in GHC.StgToCmm.Prim and mkFullIface in GHC.Iface.Make.
|
|
| 7 | +x :: Maybe Bool
|
|
| 8 | +x = Just True |
| ... | ... | @@ -172,6 +172,8 @@ test('T12622', normal, multimod_compile_and_run, ['T12622', '-O']) |
| 172 | 172 | # present even at -O0) and survives hs-boot indirections. Compiled at -O0,
|
| 173 | 173 | # where a dropped tag manifests.
|
| 174 | 174 | test('T24136', normal, multimod_compile_and_run, ['T24136', ''])
|
| 175 | +test('T23173a', extra_run_opts('+RTS --fatal-enter-taggable -RTS'),
|
|
| 176 | + multimod_compile_and_run, ['T23173a', ''])
|
|
| 175 | 177 | test('T12757', normal, compile_and_run, [''])
|
| 176 | 178 | test('T12855', normal, compile_and_run, [''])
|
| 177 | 179 | test('T9577', [ unless(arch('x86_64') or arch('i386'),skip),
|