Magnus pushed to branch wip/mangoiv/ghc-9.12-bp at Glasgow Haskell Compiler / GHC
Commits:
-
258f3c10
by sheaf at 2026-05-21T12:36:28+02:00
-
7eb7f6ed
by Luite Stegeman at 2026-05-21T13:17:47+02:00
30 changed files:
- + changelog.d/T27124.md
- + changelog.d/fix-finalizers-27072
- compiler/GHC/CoreToStg/Prep.hs
- compiler/GHC/Driver/CodeOutput.hs
- compiler/GHC/HsToCore/Pmc/Solver/Types.hs
- compiler/GHC/Linker/Static.hs
- compiler/GHC/Tc/Errors.hs
- compiler/GHC/Types/ForeignStubs.hs
- rts/Linker.c
- rts/LinkerInternals.h
- + testsuite/tests/codeGen/should_run/T27072d.hs
- + testsuite/tests/codeGen/should_run/T27072d.stdout
- + testsuite/tests/codeGen/should_run/T27072d_c.c
- + testsuite/tests/codeGen/should_run/T27072d_check.c
- + testsuite/tests/codeGen/should_run/T27072w.hs
- + testsuite/tests/codeGen/should_run/T27072w.stdout
- + testsuite/tests/codeGen/should_run/T27072w_c.c
- testsuite/tests/codeGen/should_run/all.T
- + testsuite/tests/overloadedstrings/should_fail/T25926.hs
- + testsuite/tests/overloadedstrings/should_fail/T25926.stderr
- + testsuite/tests/overloadedstrings/should_fail/T27124.hs
- + testsuite/tests/overloadedstrings/should_fail/T27124.stderr
- + testsuite/tests/overloadedstrings/should_fail/all.T
- + testsuite/tests/overloadedstrings/should_run/T27124a.hs
- testsuite/tests/overloadedstrings/should_run/all.T
- + testsuite/tests/rts/linker/T27072/Lib.c
- + testsuite/tests/rts/linker/T27072/Makefile
- + testsuite/tests/rts/linker/T27072/T27072.stdout
- + testsuite/tests/rts/linker/T27072/all.T
- + testsuite/tests/rts/linker/T27072/main.c
Changes:
| 1 | +section: compiler
|
|
| 2 | +issues: #25926 #27124
|
|
| 3 | +mrs: !15895
|
|
| 4 | +synopsis:
|
|
| 5 | + Fix "failed to detect OverLit" panic in the pattern-match checker.
|
|
| 6 | +description:
|
|
| 7 | + Fixed an issue in which overloaded literals (e.g. numeric literals, overloaded
|
|
| 8 | + strings with -XOverloadedStrings, overloaded lists, etc) could cause a GHC
|
|
| 9 | + crash when using -fdefer-type-errors, with an error message of the form
|
|
| 10 | + "failed to detect OverLit". |
| 1 | +section: codegen
|
|
| 2 | +synopsis: Fix module finalizers on multiple platforms
|
|
| 3 | +description: {
|
|
| 4 | + GHC-generated module finalizers (e.g. ``hs_spt_remove`` for the Static
|
|
| 5 | + Pointer Table) now run correctly on ELF platforms, darwin, wasm32 and
|
|
| 6 | + Windows. Also fixes running finalizers when unloading objects with the
|
|
| 7 | + RTS linker.
|
|
| 8 | +}
|
|
| 9 | +issues: #27072
|
|
| 10 | +mrs: !15762 |
| ... | ... | @@ -1125,6 +1125,9 @@ cpeApp top_env expr |
| 1125 | 1125 | || f `hasKey` nospecIdKey -- Replace (nospec a) with a
|
| 1126 | 1126 | -- See Note [nospecId magic] in GHC.Types.Id.Make
|
| 1127 | 1127 | |
| 1128 | + -- NB: keep this in sync with GHC.HsToCore.Pmc.Solver.Types.coreExprAsPmLit,
|
|
| 1129 | + -- as that also needs to see through these magic Ids.
|
|
| 1130 | + |
|
| 1128 | 1131 | -- Consider the code:
|
| 1129 | 1132 | --
|
| 1130 | 1133 | -- lazy (f x) y
|
| ... | ... | @@ -124,6 +124,7 @@ codeOutput logger tmpfs llvm_config dflags unit_state this_mod filenm location g |
| 124 | 124 | { a <- linted_cmm_stream
|
| 125 | 125 | ; let stubs = genForeignStubs a
|
| 126 | 126 | ; emitInitializerDecls this_mod stubs
|
| 127 | + ; emitFinalizerDecls this_mod stubs
|
|
| 127 | 128 | ; return (stubs, a) }
|
| 128 | 129 | |
| 129 | 130 | ; let dus1 = newTagDUniqSupply 'n' dus0
|
| ... | ... | @@ -138,19 +139,23 @@ codeOutput logger tmpfs llvm_config dflags unit_state this_mod filenm location g |
| 138 | 139 | }
|
| 139 | 140 | |
| 140 | 141 | -- | See Note [Initializers and finalizers in Cmm] in GHC.Cmm.InitFini for details.
|
| 141 | -emitInitializerDecls :: Module -> ForeignStubs -> CgStream RawCmmGroup ()
|
|
| 142 | -emitInitializerDecls this_mod (ForeignStubs _ cstub)
|
|
| 143 | - | initializers <- getInitializers cstub
|
|
| 144 | - , not $ null initializers =
|
|
| 145 | - let init_array = CmmData sect statics
|
|
| 146 | - lbl = mkInitializerArrayLabel this_mod
|
|
| 147 | - sect = Section InitArray lbl
|
|
| 142 | +emitInitializerDecls, emitFinalizerDecls :: Module -> ForeignStubs -> CgStream RawCmmGroup ()
|
|
| 143 | +emitInitializerDecls = emitInitFiniArrayDecls InitArray mkInitializerArrayLabel getInitializers
|
|
| 144 | +emitFinalizerDecls = emitInitFiniArrayDecls FiniArray mkFinalizerArrayLabel getFinalizers
|
|
| 145 | + |
|
| 146 | +emitInitFiniArrayDecls :: SectionType -> (Module -> CLabel) -> (CStub -> [CLabel])
|
|
| 147 | + -> Module -> ForeignStubs -> CgStream RawCmmGroup ()
|
|
| 148 | +emitInitFiniArrayDecls sect_type mk_lbl get_labels this_mod (ForeignStubs _ cstub)
|
|
| 149 | + | labels <- get_labels cstub
|
|
| 150 | + , not $ null labels =
|
|
| 151 | + let lbl = mk_lbl this_mod
|
|
| 152 | + sect = Section sect_type lbl
|
|
| 148 | 153 | statics = CmmStaticsRaw lbl
|
| 149 | 154 | [ CmmStaticLit $ CmmLabel fn_name
|
| 150 | - | fn_name <- initializers
|
|
| 155 | + | fn_name <- labels
|
|
| 151 | 156 | ]
|
| 152 | - in Stream.yield [init_array]
|
|
| 153 | -emitInitializerDecls _ _ = return ()
|
|
| 157 | + in Stream.yield [CmmData sect statics]
|
|
| 158 | +emitInitFiniArrayDecls _ _ _ _ _ = return ()
|
|
| 154 | 159 | |
| 155 | 160 | doOutput :: String -> (Handle -> IO a) -> IO a
|
| 156 | 161 | doOutput filenm io_action = bracket (openFile filenm WriteMode) hClose io_action
|
| ... | ... | @@ -626,6 +626,15 @@ coreExprAsPmLit :: CoreExpr -> Maybe PmLit |
| 626 | 626 | coreExprAsPmLit (Tick _t e) = coreExprAsPmLit e
|
| 627 | 627 | coreExprAsPmLit (Lit l) = literalToPmLit (literalType l) l
|
| 628 | 628 | coreExprAsPmLit e = case collectArgs e of
|
| 629 | + |
|
| 630 | + -- Look through nospec, noinline and lazy, which are only eliminated by Core Prep.
|
|
| 631 | + -- See Note [coreExprAsPmLit and nospec]
|
|
| 632 | + (Var x, Type _ : inner : rest_args)
|
|
| 633 | + | x `hasKey` nospecIdKey
|
|
| 634 | + || x `hasKey` noinlineIdKey
|
|
| 635 | + || x `hasKey` lazyIdKey
|
|
| 636 | + -> coreExprAsPmLit (mkApps inner rest_args)
|
|
| 637 | + |
|
| 629 | 638 | (Var x, [Lit l])
|
| 630 | 639 | | Just dc <- isDataConWorkId_maybe x
|
| 631 | 640 | , dc `elem` [intDataCon, wordDataCon, charDataCon, floatDataCon, doubleDataCon]
|
| ... | ... | @@ -768,6 +777,34 @@ with large exponents case. This will return a `PmLitOverRat` literal. |
| 768 | 777 | Which is then passed to overloadPmLit which simply returns it as-is since
|
| 769 | 778 | it's already overloaded.
|
| 770 | 779 | |
| 780 | +Note [coreExprAsPmLit and nospec]
|
|
| 781 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 782 | +For coverage checking, we need to analyse overloaded literal patterns to figure
|
|
| 783 | +out which literals they correspond to; this is what 'coreExprAsPmLit' does.
|
|
| 784 | +For example, the literal pattern "fromString" (with -XOverloadedStrings)
|
|
| 785 | +will turn into an equality check against the **expression**
|
|
| 786 | + |
|
| 787 | + fromString @T $dFromString "hello"#
|
|
| 788 | + |
|
| 789 | +and 'coreExprAsPmLit' recovers the string by taking apart this application.
|
|
| 790 | + |
|
| 791 | +However, when $dFromString is non-canonical (e.g. when an INCOHERENT
|
|
| 792 | +instance was discarded during resolution of the typeclass constraint, or when
|
|
| 793 | +the dictionary comes from 'withDict'), the desugarer wraps 'fromString' in
|
|
| 794 | +'nospec' (as per Note [nospecId magic] in GHC.Types.Id.Make and
|
|
| 795 | +Note [Desugaring non-canonical evidence] in GHC.HsToCore.Expr):
|
|
| 796 | + |
|
| 797 | + nospec @(IsString a => String -> Maybe a) fromString @T $dFromString "hello"#
|
|
| 798 | + |
|
| 799 | +(For a full example, see test case T27124a.)
|
|
| 800 | + |
|
| 801 | +The 'nospec' mechanism only exists for the specialiser; it should be transparent
|
|
| 802 | +to everything else. 'coreExprAsPmLit' must thus look through the 'nospec'
|
|
| 803 | +application in order obtain the string "hello". If it doesn't, we can't do
|
|
| 804 | +pattern match checking (in fact GHC.HsToCore.Pmc.Desugar.desugarPat is liable
|
|
| 805 | +to crash!).
|
|
| 806 | + |
|
| 807 | +The same reasoning applies to `noinline` and `lazy`.
|
|
| 771 | 808 | -}
|
| 772 | 809 | |
| 773 | 810 | instance Outputable PmLitValue where
|
| ... | ... | @@ -241,7 +241,20 @@ linkBinary' staticLink logger tmpfs dflags unit_env o_files dep_units = do |
| 241 | 241 | then ["-Wl,--gc-sections"]
|
| 242 | 242 | else [])
|
| 243 | 243 | |
| 244 | - ++ o_files
|
|
| 244 | + -- On Windows, module .o files may be archives (see
|
|
| 245 | + -- Note [Object merging] in GHC.Driver.Pipeline.Execute).
|
|
| 246 | + -- Use --whole-archive to ensure all archive members are
|
|
| 247 | + -- included, especially those containing .ctors/.dtors
|
|
| 248 | + -- initializer/finalizer sections. See Note [Initializers and
|
|
| 249 | + -- finalizers in Cmm] in GHC.Cmm.InitFini.
|
|
| 250 | + ++ (if platformOS platform == OSMinGW32
|
|
| 251 | + then ["-Wl,--whole-archive"]
|
|
| 252 | + else [])
|
|
| 253 | + ++ o_files
|
|
| 254 | + ++ (if platformOS platform == OSMinGW32
|
|
| 255 | + then ["-Wl,--no-whole-archive"]
|
|
| 256 | + else [])
|
|
| 257 | + |
|
| 245 | 258 | ++ lib_path_opts)
|
| 246 | 259 | ++ extra_ld_inputs
|
| 247 | 260 | ++ map GHC.SysTools.Option (
|
| ... | ... | @@ -1217,11 +1217,11 @@ addDeferredBinding ctxt err (EI { ei_evdest = Just dest, ei_pred = item_ty |
| 1217 | 1217 | |
| 1218 | 1218 | ; case dest of
|
| 1219 | 1219 | EvVarDest evar
|
| 1220 | - -> addTcEvBind ev_binds_var $ mkWantedEvBind evar EvNonCanonical err_tm
|
|
| 1220 | + -> addTcEvBind ev_binds_var $ mkWantedEvBind evar EvCanonical err_tm
|
|
| 1221 | 1221 | HoleDest hole
|
| 1222 | 1222 | -> do { -- See Note [Deferred errors for coercion holes]
|
| 1223 | 1223 | let co_var = coHoleCoVar hole
|
| 1224 | - ; addTcEvBind ev_binds_var $ mkWantedEvBind co_var EvNonCanonical err_tm
|
|
| 1224 | + ; addTcEvBind ev_binds_var $ mkWantedEvBind co_var EvCanonical err_tm
|
|
| 1225 | 1225 | ; fillCoercionHole hole (mkCoVarCo co_var) } }
|
| 1226 | 1226 | addDeferredBinding _ _ _ = return () -- Do not set any evidence for Given
|
| 1227 | 1227 |
| ... | ... | @@ -60,11 +60,85 @@ initializerCStub platform clbl declarations body = |
| 60 | 60 | -- | @finalizerCStub fn_nm decls body@ is a 'CStub' containing C finalizer
|
| 61 | 61 | -- function (e.g. an entry of the @.fini_array@ section) named
|
| 62 | 62 | -- @fn_nm@ with the given body and the given set of declarations.
|
| 63 | +--
|
|
| 64 | +-- See Note [Finalizers via __cxa_atexit]
|
|
| 63 | 65 | finalizerCStub :: Platform -> CLabel -> SDoc -> SDoc -> CStub
|
| 64 | -finalizerCStub platform clbl declarations body =
|
|
| 65 | - functionCStub platform clbl declarations body
|
|
| 66 | +finalizerCStub platform clbl declarations body
|
|
| 67 | + | ArchWasm32 <- platformArch platform
|
|
| 68 | + = -- See Note [Finalizers via __cxa_atexit]
|
|
| 69 | + cxaAtexitFinalizerCStub platform clbl declarations body
|
|
| 70 | +finalizerCStub platform clbl declarations body
|
|
| 71 | + | OSDarwin <- platformOS platform
|
|
| 72 | + = -- See Note [Finalizers via __cxa_atexit]
|
|
| 73 | + cxaAtexitFinalizerCStub platform clbl declarations body
|
|
| 74 | +finalizerCStub platform clbl declarations body
|
|
| 75 | + = functionCStub platform clbl declarations body
|
|
| 66 | 76 | `mappend` CStub empty [] [clbl]
|
| 67 | 77 | |
| 78 | +-- | Generate a @__cxa_atexit@-based finalizer.
|
|
| 79 | +-- See Note [Finalizers via __cxa_atexit]
|
|
| 80 | +cxaAtexitFinalizerCStub :: Platform -> CLabel -> SDoc -> SDoc -> CStub
|
|
| 81 | +cxaAtexitFinalizerCStub platform clbl declarations body =
|
|
| 82 | + let clbl_pretty = pprCLabel platform clbl
|
|
| 83 | + fini_name = hcat [clbl_pretty, text "$fini"]
|
|
| 84 | + wrapper_name = hcat [clbl_pretty, text "$fini_atexit"]
|
|
| 85 | + c_code = vcat
|
|
| 86 | + [ declarations
|
|
| 87 | + , text "int __cxa_atexit(void (*)(void *), void *, void *);"
|
|
| 88 | + , hcat [text "static void ", fini_name, text "(void)"]
|
|
| 89 | + , braces body
|
|
| 90 | + , hcat [text "static void ", wrapper_name, text "(void *arg __attribute__((unused)))"]
|
|
| 91 | + , braces (hcat [fini_name, text "();"])
|
|
| 92 | + , hsep [text "void", clbl_pretty, text "(void)"]
|
|
| 93 | + , braces (hcat [text "__cxa_atexit(", wrapper_name, text ", 0, 0);"])
|
|
| 94 | + ]
|
|
| 95 | + in CStub c_code [clbl] []
|
|
| 96 | + |
|
| 97 | +{-
|
|
| 98 | +Note [Finalizers via __cxa_atexit]
|
|
| 99 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 100 | +On some platforms, placing a function pointer in the .fini_array /
|
|
| 101 | +__mod_term_func section is not sufficient to have it called on exit.
|
|
| 102 | +On these platforms we instead lower finalizers as initializers that register
|
|
| 103 | +the actual finalizer function via __cxa_atexit.
|
|
| 104 | + |
|
| 105 | +Affected platforms:
|
|
| 106 | + |
|
| 107 | + Wasm32: does not support .fini_array sections.
|
|
| 108 | + |
|
| 109 | + Darwin: modern macOS dyld no longer processes __DATA,__mod_term_func entries.
|
|
| 110 | + Clang now lowers __attribute__((destructor)) as an initializer that calls
|
|
| 111 | + __cxa_atexit, placing the initializer in __DATA,__mod_init_func (which the
|
|
| 112 | + linker converts to __TEXT,__init_offsets). GHC must follow the same pattern.
|
|
| 113 | + |
|
| 114 | +For a finalizer with label `clbl` and body `body`, on these platforms we
|
|
| 115 | +generate:
|
|
| 116 | + |
|
| 117 | + static void clbl$fini(void) {
|
|
| 118 | + <body>
|
|
| 119 | + }
|
|
| 120 | + static void clbl$fini_atexit(void *arg) {
|
|
| 121 | + clbl$fini();
|
|
| 122 | + }
|
|
| 123 | + void clbl(void) {
|
|
| 124 | + __cxa_atexit(clbl$fini_atexit, 0, 0);
|
|
| 125 | + }
|
|
| 126 | + |
|
| 127 | +The function `clbl` is placed in the initializers list (getInitializers)
|
|
| 128 | +instead of the finalizers list (getFinalizers). During code output,
|
|
| 129 | +emitInitializerDecls places it in .init_array / __mod_init_func, so the
|
|
| 130 | +registration runs at startup.
|
|
| 131 | + |
|
| 132 | +The actual finalizer body is in the static helper `clbl$fini`. A separate
|
|
| 133 | +wrapper `clbl$fini_atexit` with the void(*)(void*) signature expected by
|
|
| 134 | +__cxa_atexit is needed because some platforms (e.g. wasm32) enforce exact
|
|
| 135 | +function signature matching at call sites — a simple cast would trap at
|
|
| 136 | +runtime.
|
|
| 137 | + |
|
| 138 | +This matches what clang does when lowering __attribute__((destructor)) on
|
|
| 139 | +these platforms.
|
|
| 140 | +-}
|
|
| 141 | + |
|
| 68 | 142 | newtype CHeader = CHeader { getCHeader :: SDoc }
|
| 69 | 143 | |
| 70 | 144 | instance Monoid CHeader where
|
| ... | ... | @@ -1107,6 +1107,27 @@ freePreloadObjectFile (ObjectCode *oc) |
| 1107 | 1107 | oc->fileSize = 0;
|
| 1108 | 1108 | }
|
| 1109 | 1109 | |
| 1110 | +/* Note [Object unloading and finalizers]
|
|
| 1111 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 1112 | + * An ObjectCode may contain .fini_array/.dtors sections with finalizers that
|
|
| 1113 | + * should run when the object is unloaded. However, we must only run these
|
|
| 1114 | + * finalizers if the corresponding initializers (.init_array/.ctors) have
|
|
| 1115 | + * actually been executed.
|
|
| 1116 | + *
|
|
| 1117 | + * Archive members start in OBJECT_LOADED state and only progress to
|
|
| 1118 | + * OBJECT_NEEDED -> OBJECT_RESOLVED -> OBJECT_READY when a symbol from
|
|
| 1119 | + * them is actually required. An archive member that was never needed never
|
|
| 1120 | + * has its relocations applied, so its .fini_array section data still
|
|
| 1121 | + * contains zeros (unresolved relocation targets). Running those finalizers
|
|
| 1122 | + * would dereference NULL function pointers.
|
|
| 1123 | + *
|
|
| 1124 | + * When unloadObj sets an object's status to OBJECT_UNLOADED, it does so
|
|
| 1125 | + * regardless of the previous state, so we cannot rely on the status alone
|
|
| 1126 | + * to decide whether finalizers should run. Instead, we track whether
|
|
| 1127 | + * initializers were executed via the initializersRan flag, which is set in
|
|
| 1128 | + * ocRunInit after successfully running the initializers.
|
|
| 1129 | + */
|
|
| 1130 | + |
|
| 1110 | 1131 | /*
|
| 1111 | 1132 | * freeObjectCode() releases all the pieces of an ObjectCode. It is called by
|
| 1112 | 1133 | * the GC when a previously unloaded ObjectCode has been determined to be
|
| ... | ... | @@ -1116,11 +1137,9 @@ void freeObjectCode (ObjectCode *oc) |
| 1116 | 1137 | {
|
| 1117 | 1138 | IF_DEBUG(linker, ocDebugBelch(oc, "freeObjectCode: start\n"));
|
| 1118 | 1139 | |
| 1119 | - // Run finalizers
|
|
| 1120 | - if (oc->type == STATIC_OBJECT &&
|
|
| 1121 | - (oc->status == OBJECT_READY || oc->status == OBJECT_UNLOADED)) {
|
|
| 1122 | - // Only run finalizers if the initializers have also been run, which
|
|
| 1123 | - // happens when we resolve the object.
|
|
| 1140 | + // Run finalizers only if initializers have been run.
|
|
| 1141 | + // See Note [Object unloading and finalizers].
|
|
| 1142 | + if (oc->type == STATIC_OBJECT && oc->initializersRan) {
|
|
| 1124 | 1143 | #if defined(OBJFORMAT_ELF)
|
| 1125 | 1144 | ocRunFini_ELF(oc);
|
| 1126 | 1145 | #elif defined(OBJFORMAT_PEi386)
|
| ... | ... | @@ -1285,6 +1304,7 @@ mkOc( ObjectType type, pathchar *path, char *image, int imageSize, |
| 1285 | 1304 | oc->imageMapped = mapped;
|
| 1286 | 1305 | |
| 1287 | 1306 | oc->misalignment = misalignment;
|
| 1307 | + oc->initializersRan = false;
|
|
| 1288 | 1308 | oc->cxa_finalize = NULL;
|
| 1289 | 1309 | oc->extraInfos = NULL;
|
| 1290 | 1310 | |
| ... | ... | @@ -1681,6 +1701,7 @@ int ocRunInit(ObjectCode *oc) |
| 1681 | 1701 | foreignExportsFinishedLoadingObject();
|
| 1682 | 1702 | |
| 1683 | 1703 | if (!r) { return r; }
|
| 1704 | + oc->initializersRan = true;
|
|
| 1684 | 1705 | oc->status = OBJECT_READY;
|
| 1685 | 1706 | |
| 1686 | 1707 | return 1;
|
| ... | ... | @@ -268,6 +268,12 @@ struct _ObjectCode { |
| 268 | 268 | after allocation, so that we can use realloc */
|
| 269 | 269 | int misalignment;
|
| 270 | 270 | |
| 271 | + /* Set to true after initializers (.init_array, .ctors, etc.) have been
|
|
| 272 | + * executed. Used by freeObjectCode to decide whether finalizers should
|
|
| 273 | + * run: only objects whose initializers ran should have their finalizers
|
|
| 274 | + * executed. See Note [Object unloading and finalizers]. */
|
|
| 275 | + bool initializersRan;
|
|
| 276 | + |
|
| 271 | 277 | /* The address of __cxa_finalize; set when at least one finalizer was
|
| 272 | 278 | * register and therefore we must call __cxa_finalize before unloading.
|
| 273 | 279 | * See Note [Resolving __dso_handle]. */
|
| 1 | +{-# LANGUAGE StaticPointers #-}
|
|
| 2 | +module T27072d where
|
|
| 3 | + |
|
| 4 | +import GHC.StaticPtr
|
|
| 5 | + |
|
| 6 | +f :: StaticPtr Int
|
|
| 7 | +f = static 1
|
|
| 8 | + |
|
| 9 | +g :: StaticPtr Int
|
|
| 10 | +g = static 2 |
| 1 | +SPT entries after init: 2
|
|
| 2 | +SPT entries after finalizer: 0 |
| 1 | +// Test that GHC-generated module initializers and finalizer registrations
|
|
| 2 | +// work correctly on Darwin.
|
|
| 3 | +//
|
|
| 4 | +// On Darwin, GHC lowers finalizers as __cxa_atexit registrations from an
|
|
| 5 | +// initializer placed in __DATA,__mod_init_func (see Note [Finalizers via
|
|
| 6 | +// __cxa_atexit] in GHC.Types.ForeignStubs).
|
|
| 7 | +//
|
|
| 8 | +// This test verifies the mechanism by checking that:
|
|
| 9 | +// 1. The SPT initializer runs at load time (entries are inserted).
|
|
| 10 | +// 2. The SPT finalizer (registered via __cxa_atexit from __mod_init_func)
|
|
| 11 | +// fires during exit() and removes the entries.
|
|
| 12 | +//
|
|
| 13 | +// We verify (2) by registering our own __cxa_atexit checker from a
|
|
| 14 | +// constructor in a dylib that is loaded before the main executable's
|
|
| 15 | +// initializers run. Since __cxa_atexit handlers fire in LIFO order,
|
|
| 16 | +// a handler registered earlier runs later — so our checker runs after the
|
|
| 17 | +// GHC-generated finalizer, and can observe that SPT entries were removed.
|
|
| 18 | +//
|
|
| 19 | +// The Apple linker does not support --wrap, so this is the Darwin
|
|
| 20 | +// equivalent of T27072w's approach.
|
|
| 21 | + |
|
| 22 | +#include "Rts.h"
|
|
| 23 | +#include <stdio.h>
|
|
| 24 | + |
|
| 25 | +extern int hs_spt_key_count(void);
|
|
| 26 | + |
|
| 27 | +int main(int argc, char *argv[]) {
|
|
| 28 | + RtsConfig conf = defaultRtsConfig;
|
|
| 29 | + conf.rts_opts_enabled = RtsOptsAll;
|
|
| 30 | + hs_init_ghc(&argc, &argv, conf);
|
|
| 31 | + |
|
| 32 | + printf("SPT entries after init: %d\n", hs_spt_key_count());
|
|
| 33 | + fflush(stdout);
|
|
| 34 | + |
|
| 35 | + // Do NOT call hs_exit(). Return normally so __cxa_atexit handlers fire,
|
|
| 36 | + // which includes the GHC-generated finalizer registered during init.
|
|
| 37 | + return 0;
|
|
| 38 | +} |
| 1 | +// Checker dylib for T27072d.
|
|
| 2 | +//
|
|
| 3 | +// Compiled as a dylib and linked against the test executable. Because dylib
|
|
| 4 | +// initializers run before the main executable's __mod_init_func entries,
|
|
| 5 | +// our __cxa_atexit registration happens first. Since __cxa_atexit handlers
|
|
| 6 | +// fire in LIFO order, our checker runs *after* the GHC-generated finalizer,
|
|
| 7 | +// allowing us to observe that SPT entries were removed.
|
|
| 8 | + |
|
| 9 | +#include <stdio.h>
|
|
| 10 | + |
|
| 11 | +// Provided by the RTS.
|
|
| 12 | +extern int hs_spt_key_count(void);
|
|
| 13 | + |
|
| 14 | +static void check_spt_finalizer(void *arg __attribute__((unused))) {
|
|
| 15 | + int count = hs_spt_key_count();
|
|
| 16 | + printf("SPT entries after finalizer: %d\n", count);
|
|
| 17 | + fflush(stdout);
|
|
| 18 | +}
|
|
| 19 | + |
|
| 20 | +// Register the checker. This constructor runs during dylib initialization,
|
|
| 21 | +// which happens before the main executable's initializers.
|
|
| 22 | +__attribute__((constructor))
|
|
| 23 | +static void register_spt_checker(void) {
|
|
| 24 | + // Use __cxa_atexit so we participate in the same LIFO chain as the
|
|
| 25 | + // GHC-generated finalizer.
|
|
| 26 | + extern int __cxa_atexit(void (*)(void *), void *, void *);
|
|
| 27 | + extern void *__dso_handle;
|
|
| 28 | + __cxa_atexit(check_spt_finalizer, (void *)0, &__dso_handle);
|
|
| 29 | +} |
| 1 | +{-# LANGUAGE StaticPointers #-}
|
|
| 2 | +module T27072w where
|
|
| 3 | + |
|
| 4 | +import GHC.StaticPtr
|
|
| 5 | + |
|
| 6 | +f :: StaticPtr Int
|
|
| 7 | +f = static 1
|
|
| 8 | + |
|
| 9 | +g :: StaticPtr Int
|
|
| 10 | +g = static 2 |
| 1 | +SPT entries after init: 2
|
|
| 2 | +finalizer: hs_spt_remove called
|
|
| 3 | +finalizer: hs_spt_remove called |
| 1 | +// Test that GHC-generated finalizers actually run on wasm32
|
|
| 2 | +//
|
|
| 3 | +// We use --wrap=hs_spt_remove to intercept calls from the GHC-generated
|
|
| 4 | +// finalizer and verify they happen during exit().
|
|
| 5 | + |
|
| 6 | +#include "Rts.h"
|
|
| 7 | +#include <stdio.h>
|
|
| 8 | + |
|
| 9 | +extern int hs_spt_key_count(void);
|
|
| 10 | + |
|
| 11 | +// --wrap=hs_spt_remove: the linker redirects all calls to hs_spt_remove
|
|
| 12 | +// through our wrapper, and provides __real_hs_spt_remove for the original.
|
|
| 13 | +extern void __real_hs_spt_remove(StgWord64 key[2]);
|
|
| 14 | + |
|
| 15 | +void __wrap_hs_spt_remove(StgWord64 key[2]) {
|
|
| 16 | + printf("finalizer: hs_spt_remove called\n");
|
|
| 17 | + fflush(stdout);
|
|
| 18 | + __real_hs_spt_remove(key);
|
|
| 19 | +}
|
|
| 20 | + |
|
| 21 | +int main(int argc, char *argv[]) {
|
|
| 22 | + RtsConfig conf = defaultRtsConfig;
|
|
| 23 | + conf.rts_opts_enabled = RtsOptsAll;
|
|
| 24 | + hs_init_ghc(&argc, &argv, conf);
|
|
| 25 | + |
|
| 26 | + printf("SPT entries after init: %d\n", hs_spt_key_count());
|
|
| 27 | + fflush(stdout);
|
|
| 28 | + |
|
| 29 | + // Do NOT call hs_exit(). Return normally so exit() fires the
|
|
| 30 | + // __cxa_atexit registered handlers.
|
|
| 31 | + return 0;
|
|
| 32 | +} |
| ... | ... | @@ -256,3 +256,22 @@ test('T24893', normal, compile_and_run, ['-O']) |
| 256 | 256 | test('CCallConv', [req_c], compile_and_run, ['CCallConv_c.c'])
|
| 257 | 257 | test('T26061', normal, compile_and_run, [''])
|
| 258 | 258 | test('T26537', js_broken(26558), compile_and_run, ['-O2 -fregs-graph'])
|
| 259 | + |
|
| 260 | +# Check that GHC-generated finalizers run on Darwin. The Apple linker doesn't
|
|
| 261 | +# support --wrap, so we can't intercept hs_spt_remove directly. Instead we
|
|
| 262 | +# compile a small checker dylib (T27072d_check.c) whose constructor registers
|
|
| 263 | +# a __cxa_atexit handler *before* the executable's __mod_init_func entries run.
|
|
| 264 | +# LIFO ordering ensures the checker fires after the GHC-generated finalizer,
|
|
| 265 | +# so it can observe that SPT entries were removed.
|
|
| 266 | +# Requires dynamic way so the RTS is a dylib (avoids archive conflicts).
|
|
| 267 | +test('T27072d', [req_c, only_ways(['dyn']), when(not opsys('darwin'), skip),
|
|
| 268 | + pre_cmd('{compiler} -shared -no-hs-main'
|
|
| 269 | + ' -optl -undefined -optl dynamic_lookup'
|
|
| 270 | + ' -o T27072d_check.dylib T27072d_check.c')],
|
|
| 271 | + compile_and_run,
|
|
| 272 | + ['T27072d_c.c -no-hs-main'
|
|
| 273 | + ' -optl -Wl,-needed_library,T27072d_check.dylib -optl -rpath -optl .'])
|
|
| 274 | +# check that finalizers are being run, using --wrap to intercept hs_spt_remove.
|
|
| 275 | +# Skipped on Darwin (Apple linker doesn't support --wrap).
|
|
| 276 | +test('T27072w', [req_c, js_skip, when(opsys('darwin'), skip)],
|
|
| 277 | + compile_and_run, ['T27072w_c.c -no-hs-main -optl-Wl,--wrap=hs_spt_remove']) |
| 1 | +module T25926 where
|
|
| 2 | + |
|
| 3 | +f () 0 = ()
|
|
| 4 | +f 'a' _ = () |
| 1 | +T25926.hs:4:3: warning: [GHC-83865] [-Wdeferred-type-errors (in -Wdefault)]
|
|
| 2 | + • Couldn't match expected type ‘()’ with actual type ‘Char’
|
|
| 3 | + • In the pattern: 'a'
|
|
| 4 | + In an equation for ‘f’: f 'a' _ = ()
|
|
| 5 | + |
| 1 | +{-# LANGUAGE OverloadedStrings #-}
|
|
| 2 | + |
|
| 3 | +module T27124 where
|
|
| 4 | + |
|
| 5 | +foo :: [String] -> Bool
|
|
| 6 | +foo "HI" = True
|
|
| 7 | +foo _ = False
|
|
| 8 | + |
|
| 9 | +main = pure () |
| 1 | +T27124.hs:6:5: warning: [GHC-18872] [-Wdeferred-type-errors (in -Wdefault)]
|
|
| 2 | + • Couldn't match type ‘[Char]’ with ‘Char’
|
|
| 3 | + arising from the literal ‘"HI"’
|
|
| 4 | + • In the pattern: "HI"
|
|
| 5 | + In an equation for ‘foo’: foo "HI" = True
|
|
| 6 | + |
| 1 | +test('T25926', normal, compile, ['-fdefer-type-errors'])
|
|
| 2 | +test('T27124', normal, compile, ['-fdefer-type-errors']) |
| 1 | +{-# LANGUAGE FlexibleInstances #-}
|
|
| 2 | +{-# LANGUAGE OverloadedStrings #-}
|
|
| 3 | + |
|
| 4 | +module T27124a where
|
|
| 5 | + |
|
| 6 | +import Data.String (IsString(..))
|
|
| 7 | + |
|
| 8 | +newtype Wrap a = Wrap a deriving (Eq, Show)
|
|
| 9 | + |
|
| 10 | +instance IsString a => IsString (Wrap a) where
|
|
| 11 | + fromString = Wrap . fromString
|
|
| 12 | + |
|
| 13 | +instance {-# INCOHERENT #-} IsString (Wrap Bool) where
|
|
| 14 | + fromString _ = Wrap False
|
|
| 15 | + |
|
| 16 | +f :: (Eq a, IsString a) => Wrap a -> Bool
|
|
| 17 | +f "hello" = True
|
|
| 18 | +f _ = False
|
|
| 19 | + |
|
| 20 | +main :: IO ()
|
|
| 21 | +main = do
|
|
| 22 | + print (f (Wrap ("hello" :: String)))
|
|
| 23 | + print (f (Wrap ("world" :: String))) |
| 1 | 1 | test('overloadedstringsrun01', normal, compile_and_run, [''])
|
| 2 | +test('T27124a', normal, compile, ['-fno-specialise-incoherents']) |
| 1 | +// Minimal module with an initializer and finalizer.
|
|
| 2 | +// The compiler places the function pointers in .init_array/.fini_array
|
|
| 3 | +// (ELF) or __mod_init_func/__mod_term_func (Mach-O).
|
|
| 4 | +//
|
|
| 5 | +// The counter lives in the main binary so it survives after this
|
|
| 6 | +// object is unloaded.
|
|
| 7 | + |
|
| 8 | +extern int init_counter;
|
|
| 9 | + |
|
| 10 | +__attribute__((constructor))
|
|
| 11 | +static void lib_init(void) {
|
|
| 12 | + init_counter++;
|
|
| 13 | +}
|
|
| 14 | + |
|
| 15 | +__attribute__((destructor))
|
|
| 16 | +static void lib_fini(void) {
|
|
| 17 | + init_counter--;
|
|
| 18 | +} |
| 1 | +.PHONY: clean_build_and_run build_and_run clean build
|
|
| 2 | + |
|
| 3 | +clean_build_and_run:
|
|
| 4 | + $(MAKE) clean
|
|
| 5 | + $(MAKE) build_and_run
|
|
| 6 | + |
|
| 7 | +build_and_run: build
|
|
| 8 | + ./main
|
|
| 9 | + |
|
| 10 | +clean:
|
|
| 11 | + $(RM) Lib.o main.o main
|
|
| 12 | + |
|
| 13 | +build: Lib.o main
|
|
| 14 | + |
|
| 15 | +Lib.o: Lib.c
|
|
| 16 | + $(CC) -c -fPIC Lib.c -o Lib.o
|
|
| 17 | + |
|
| 18 | +main: main.c
|
|
| 19 | + "$(TEST_HC)" $(filter-out -rtsopts, $(TEST_HC_OPTS)) \
|
|
| 20 | + -no-hs-main -optc-Werror \
|
|
| 21 | + main.c -o main |
| 1 | +counter before load: 0
|
|
| 2 | +counter after load: 1
|
|
| 3 | +counter after unload: 0 |
| 1 | +test('T27072',
|
|
| 2 | + [req_rts_linker,
|
|
| 3 | + js_skip,
|
|
| 4 | + extra_files(['Lib.c', 'main.c'])],
|
|
| 5 | + makefile_test,
|
|
| 6 | + ['clean_build_and_run']) |
| 1 | +// Test that the RTS linker executes .init_array entries on load and
|
|
| 2 | +// .fini_array entries on unload. The loaded module increments a
|
|
| 3 | +// counter in its initializer and decrements it in its finalizer.
|
|
| 4 | + |
|
| 5 | +#include "Rts.h"
|
|
| 6 | +#include <stdio.h>
|
|
| 7 | + |
|
| 8 | +#if defined(mingw32_HOST_OS)
|
|
| 9 | +#define PATH_STR(str) L##str
|
|
| 10 | +#else
|
|
| 11 | +#define PATH_STR(str) str
|
|
| 12 | +#endif
|
|
| 13 | + |
|
| 14 | +int init_counter = 0;
|
|
| 15 | + |
|
| 16 | +int main(int argc, char *argv[]) {
|
|
| 17 | + RtsConfig conf = defaultRtsConfig;
|
|
| 18 | + conf.rts_opts_enabled = RtsOptsAll;
|
|
| 19 | + hs_init_ghc(&argc, &argv, conf);
|
|
| 20 | + |
|
| 21 | + initLinker_(0);
|
|
| 22 | + insertSymbol(PATH_STR("main"), "init_counter", &init_counter);
|
|
| 23 | + |
|
| 24 | + printf("counter before load: %d\n", init_counter);
|
|
| 25 | + fflush(stdout);
|
|
| 26 | + |
|
| 27 | + int ok;
|
|
| 28 | + ok = loadObj(PATH_STR("Lib.o"));
|
|
| 29 | + if (!ok) {
|
|
| 30 | + errorBelch("loadObj(Lib.o) failed");
|
|
| 31 | + return 1;
|
|
| 32 | + }
|
|
| 33 | + ok = resolveObjs();
|
|
| 34 | + if (!ok) {
|
|
| 35 | + errorBelch("resolveObjs() failed");
|
|
| 36 | + return 1;
|
|
| 37 | + }
|
|
| 38 | + |
|
| 39 | + printf("counter after load: %d\n", init_counter);
|
|
| 40 | + fflush(stdout);
|
|
| 41 | + |
|
| 42 | + ok = unloadObj(PATH_STR("Lib.o"));
|
|
| 43 | + if (!ok) {
|
|
| 44 | + errorBelch("unloadObj(Lib.o) failed");
|
|
| 45 | + return 1;
|
|
| 46 | + }
|
|
| 47 | + |
|
| 48 | + // GC triggers actual unloading and finalizer execution.
|
|
| 49 | + performMajorGC();
|
|
| 50 | + performMajorGC();
|
|
| 51 | + |
|
| 52 | + printf("counter after unload: %d\n", init_counter);
|
|
| 53 | + fflush(stdout);
|
|
| 54 | + |
|
| 55 | + hs_exit();
|
|
| 56 | + return 0;
|
|
| 57 | +} |