Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
04d143c0
by Luite Stegeman at 2026-04-21T14:05:33-04:00
-
014087e7
by Luite Stegeman at 2026-04-21T14:05:34-04:00
21 changed files:
- + changelog.d/fix-finalizers-27072
- compiler/GHC/Driver/CodeOutput.hs
- compiler/GHC/Linker/Executable.hs
- compiler/GHC/Types/ForeignStubs.hs
- rts/Linker.c
- rts/LinkerInternals.h
- rts/linker/Elf.c
- + 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/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
- testsuite/tests/th/all.T
Changes:
| 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 |
| ... | ... | @@ -119,6 +119,7 @@ codeOutput logger tmpfs llvm_config dflags unit_state this_mod filenm location g |
| 119 | 119 | { a <- linted_cmm_stream
|
| 120 | 120 | ; let stubs = genForeignStubs a
|
| 121 | 121 | ; emitInitializerDecls this_mod stubs
|
| 122 | + ; emitFinalizerDecls this_mod stubs
|
|
| 122 | 123 | ; return (stubs, a) }
|
| 123 | 124 | |
| 124 | 125 | ; let dus1 = newTagDUniqSupply CodeGenTag dus0
|
| ... | ... | @@ -133,19 +134,23 @@ codeOutput logger tmpfs llvm_config dflags unit_state this_mod filenm location g |
| 133 | 134 | }
|
| 134 | 135 | |
| 135 | 136 | -- | See Note [Initializers and finalizers in Cmm] in GHC.Cmm.InitFini for details.
|
| 136 | -emitInitializerDecls :: Module -> ForeignStubs -> CgStream RawCmmGroup ()
|
|
| 137 | -emitInitializerDecls this_mod (ForeignStubs _ cstub)
|
|
| 138 | - | initializers <- getInitializers cstub
|
|
| 139 | - , not $ null initializers =
|
|
| 140 | - let init_array = CmmData sect statics
|
|
| 141 | - lbl = mkInitializerArrayLabel this_mod
|
|
| 142 | - sect = Section InitArray lbl
|
|
| 137 | +emitInitializerDecls, emitFinalizerDecls :: Module -> ForeignStubs -> CgStream RawCmmGroup ()
|
|
| 138 | +emitInitializerDecls = emitInitFiniArrayDecls InitArray mkInitializerArrayLabel getInitializers
|
|
| 139 | +emitFinalizerDecls = emitInitFiniArrayDecls FiniArray mkFinalizerArrayLabel getFinalizers
|
|
| 140 | + |
|
| 141 | +emitInitFiniArrayDecls :: SectionType -> (Module -> CLabel) -> (CStub -> [CLabel])
|
|
| 142 | + -> Module -> ForeignStubs -> CgStream RawCmmGroup ()
|
|
| 143 | +emitInitFiniArrayDecls sect_type mk_lbl get_labels this_mod (ForeignStubs _ cstub)
|
|
| 144 | + | labels <- get_labels cstub
|
|
| 145 | + , not $ null labels =
|
|
| 146 | + let lbl = mk_lbl this_mod
|
|
| 147 | + sect = Section sect_type lbl
|
|
| 143 | 148 | statics = CmmStaticsRaw lbl
|
| 144 | 149 | [ CmmStaticLit $ CmmLabel fn_name
|
| 145 | - | fn_name <- initializers
|
|
| 150 | + | fn_name <- labels
|
|
| 146 | 151 | ]
|
| 147 | - in Stream.yield [init_array]
|
|
| 148 | -emitInitializerDecls _ _ = return ()
|
|
| 152 | + in Stream.yield [CmmData sect statics]
|
|
| 153 | +emitInitFiniArrayDecls _ _ _ _ _ = return ()
|
|
| 149 | 154 | |
| 150 | 155 | doOutput :: String -> (Handle -> IO a) -> IO a
|
| 151 | 156 | doOutput filenm io_action = bracket (openFile filenm WriteMode) hClose io_action
|
| ... | ... | @@ -300,7 +300,19 @@ linkExecutable logger tmpfs opts unit_env o_files dep_units = do |
| 300 | 300 | then ["-Wl,--gc-sections"]
|
| 301 | 301 | else [])
|
| 302 | 302 | |
| 303 | + -- On Windows, module .o files may be archives (see
|
|
| 304 | + -- Note [Object merging] in GHC.Driver.Pipeline.Execute).
|
|
| 305 | + -- Use --whole-archive to ensure all archive members are
|
|
| 306 | + -- included, especially those containing .ctors/.dtors
|
|
| 307 | + -- initializer/finalizer sections. See Note [Initializers and
|
|
| 308 | + -- finalizers in Cmm] in GHC.Cmm.InitFini.
|
|
| 309 | + ++ (if platformOS platform == OSMinGW32
|
|
| 310 | + then ["-Wl,--whole-archive"]
|
|
| 311 | + else [])
|
|
| 303 | 312 | ++ o_files
|
| 313 | + ++ (if platformOS platform == OSMinGW32
|
|
| 314 | + then ["-Wl,--no-whole-archive"]
|
|
| 315 | + else [])
|
|
| 304 | 316 | ++ lib_path_opts)
|
| 305 | 317 | ++ extra_ld_inputs
|
| 306 | 318 | ++ map GHC.SysTools.Option (
|
| ... | ... | @@ -59,11 +59,85 @@ initializerCStub platform clbl declarations body = |
| 59 | 59 | -- | @finalizerCStub fn_nm decls body@ is a 'CStub' containing C finalizer
|
| 60 | 60 | -- function (e.g. an entry of the @.fini_array@ section) named
|
| 61 | 61 | -- @fn_nm@ with the given body and the given set of declarations.
|
| 62 | +--
|
|
| 63 | +-- See Note [Finalizers via __cxa_atexit]
|
|
| 62 | 64 | finalizerCStub :: Platform -> CLabel -> SDoc -> SDoc -> CStub
|
| 63 | -finalizerCStub platform clbl declarations body =
|
|
| 64 | - functionCStub platform clbl declarations body
|
|
| 65 | +finalizerCStub platform clbl declarations body
|
|
| 66 | + | ArchWasm32 <- platformArch platform
|
|
| 67 | + = -- See Note [Finalizers via __cxa_atexit]
|
|
| 68 | + cxaAtexitFinalizerCStub platform clbl declarations body
|
|
| 69 | +finalizerCStub platform clbl declarations body
|
|
| 70 | + | OSDarwin <- platformOS platform
|
|
| 71 | + = -- See Note [Finalizers via __cxa_atexit]
|
|
| 72 | + cxaAtexitFinalizerCStub platform clbl declarations body
|
|
| 73 | +finalizerCStub platform clbl declarations body
|
|
| 74 | + = functionCStub platform clbl declarations body
|
|
| 65 | 75 | `mappend` CStub empty [] [clbl]
|
| 66 | 76 | |
| 77 | +-- | Generate a @__cxa_atexit@-based finalizer.
|
|
| 78 | +-- See Note [Finalizers via __cxa_atexit]
|
|
| 79 | +cxaAtexitFinalizerCStub :: Platform -> CLabel -> SDoc -> SDoc -> CStub
|
|
| 80 | +cxaAtexitFinalizerCStub platform clbl declarations body =
|
|
| 81 | + let clbl_pretty = pprCLabel platform clbl
|
|
| 82 | + fini_name = hcat [clbl_pretty, text "$fini"]
|
|
| 83 | + wrapper_name = hcat [clbl_pretty, text "$fini_atexit"]
|
|
| 84 | + c_code = vcat
|
|
| 85 | + [ declarations
|
|
| 86 | + , text "int __cxa_atexit(void (*)(void *), void *, void *);"
|
|
| 87 | + , hcat [text "static void ", fini_name, text "(void)"]
|
|
| 88 | + , braces body
|
|
| 89 | + , hcat [text "static void ", wrapper_name, text "(void *arg __attribute__((unused)))"]
|
|
| 90 | + , braces (hcat [fini_name, text "();"])
|
|
| 91 | + , hsep [text "void", clbl_pretty, text "(void)"]
|
|
| 92 | + , braces (hcat [text "__cxa_atexit(", wrapper_name, text ", 0, 0);"])
|
|
| 93 | + ]
|
|
| 94 | + in CStub c_code [clbl] []
|
|
| 95 | + |
|
| 96 | +{-
|
|
| 97 | +Note [Finalizers via __cxa_atexit]
|
|
| 98 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 99 | +On some platforms, placing a function pointer in the .fini_array /
|
|
| 100 | +__mod_term_func section is not sufficient to have it called on exit.
|
|
| 101 | +On these platforms we instead lower finalizers as initializers that register
|
|
| 102 | +the actual finalizer function via __cxa_atexit.
|
|
| 103 | + |
|
| 104 | +Affected platforms:
|
|
| 105 | + |
|
| 106 | + Wasm32: does not support .fini_array sections.
|
|
| 107 | + |
|
| 108 | + Darwin: modern macOS dyld no longer processes __DATA,__mod_term_func entries.
|
|
| 109 | + Clang now lowers __attribute__((destructor)) as an initializer that calls
|
|
| 110 | + __cxa_atexit, placing the initializer in __DATA,__mod_init_func (which the
|
|
| 111 | + linker converts to __TEXT,__init_offsets). GHC must follow the same pattern.
|
|
| 112 | + |
|
| 113 | +For a finalizer with label `clbl` and body `body`, on these platforms we
|
|
| 114 | +generate:
|
|
| 115 | + |
|
| 116 | + static void clbl$fini(void) {
|
|
| 117 | + <body>
|
|
| 118 | + }
|
|
| 119 | + static void clbl$fini_atexit(void *arg) {
|
|
| 120 | + clbl$fini();
|
|
| 121 | + }
|
|
| 122 | + void clbl(void) {
|
|
| 123 | + __cxa_atexit(clbl$fini_atexit, 0, 0);
|
|
| 124 | + }
|
|
| 125 | + |
|
| 126 | +The function `clbl` is placed in the initializers list (getInitializers)
|
|
| 127 | +instead of the finalizers list (getFinalizers). During code output,
|
|
| 128 | +emitInitializerDecls places it in .init_array / __mod_init_func, so the
|
|
| 129 | +registration runs at startup.
|
|
| 130 | + |
|
| 131 | +The actual finalizer body is in the static helper `clbl$fini`. A separate
|
|
| 132 | +wrapper `clbl$fini_atexit` with the void(*)(void*) signature expected by
|
|
| 133 | +__cxa_atexit is needed because some platforms (e.g. wasm32) enforce exact
|
|
| 134 | +function signature matching at call sites — a simple cast would trap at
|
|
| 135 | +runtime.
|
|
| 136 | + |
|
| 137 | +This matches what clang does when lowering __attribute__((destructor)) on
|
|
| 138 | +these platforms.
|
|
| 139 | +-}
|
|
| 140 | + |
|
| 67 | 141 | newtype CHeader = CHeader { getCHeader :: SDoc }
|
| 68 | 142 | |
| 69 | 143 | instance Monoid CHeader where
|
| ... | ... | @@ -1117,6 +1117,27 @@ freePreloadObjectFile (ObjectCode *oc) |
| 1117 | 1117 | oc->fileSize = 0;
|
| 1118 | 1118 | }
|
| 1119 | 1119 | |
| 1120 | +/* Note [Object unloading and finalizers]
|
|
| 1121 | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 1122 | + * An ObjectCode may contain .fini_array/.dtors sections with finalizers that
|
|
| 1123 | + * should run when the object is unloaded. However, we must only run these
|
|
| 1124 | + * finalizers if the corresponding initializers (.init_array/.ctors) have
|
|
| 1125 | + * actually been executed.
|
|
| 1126 | + *
|
|
| 1127 | + * Archive members start in OBJECT_LOADED state and only progress to
|
|
| 1128 | + * OBJECT_NEEDED -> OBJECT_RESOLVED -> OBJECT_READY when a symbol from
|
|
| 1129 | + * them is actually required. An archive member that was never needed never
|
|
| 1130 | + * has its relocations applied, so its .fini_array section data still
|
|
| 1131 | + * contains zeros (unresolved relocation targets). Running those finalizers
|
|
| 1132 | + * would dereference NULL function pointers.
|
|
| 1133 | + *
|
|
| 1134 | + * When unloadObj sets an object's status to OBJECT_UNLOADED, it does so
|
|
| 1135 | + * regardless of the previous state, so we cannot rely on the status alone
|
|
| 1136 | + * to decide whether finalizers should run. Instead, we track whether
|
|
| 1137 | + * initializers were executed via the initializersRan flag, which is set in
|
|
| 1138 | + * ocRunInit after successfully running the initializers.
|
|
| 1139 | + */
|
|
| 1140 | + |
|
| 1120 | 1141 | /*
|
| 1121 | 1142 | * freeObjectCode() releases all the pieces of an ObjectCode. It is called by
|
| 1122 | 1143 | * the GC when a previously unloaded ObjectCode has been determined to be
|
| ... | ... | @@ -1126,11 +1147,9 @@ void freeObjectCode (ObjectCode *oc) |
| 1126 | 1147 | {
|
| 1127 | 1148 | IF_DEBUG(linker, ocDebugBelch(oc, "freeObjectCode: start\n"));
|
| 1128 | 1149 | |
| 1129 | - // Run finalizers
|
|
| 1130 | - if (oc->type == STATIC_OBJECT &&
|
|
| 1131 | - (oc->status == OBJECT_READY || oc->status == OBJECT_UNLOADED)) {
|
|
| 1132 | - // Only run finalizers if the initializers have also been run, which
|
|
| 1133 | - // happens when we resolve the object.
|
|
| 1150 | + // Run finalizers only if initializers have been run.
|
|
| 1151 | + // See Note [Object unloading and finalizers].
|
|
| 1152 | + if (oc->type == STATIC_OBJECT && oc->initializersRan) {
|
|
| 1134 | 1153 | #if defined(OBJFORMAT_ELF)
|
| 1135 | 1154 | ocRunFini_ELF(oc);
|
| 1136 | 1155 | #elif defined(OBJFORMAT_PEi386)
|
| ... | ... | @@ -1295,6 +1314,7 @@ mkOc( ObjectType type, pathchar *path, char *image, int imageSize, |
| 1295 | 1314 | oc->imageMapped = mapped;
|
| 1296 | 1315 | |
| 1297 | 1316 | oc->misalignment = misalignment;
|
| 1317 | + oc->initializersRan = false;
|
|
| 1298 | 1318 | oc->cxa_finalize = NULL;
|
| 1299 | 1319 | oc->extraInfos = NULL;
|
| 1300 | 1320 | |
| ... | ... | @@ -1691,6 +1711,7 @@ int ocRunInit(ObjectCode *oc) |
| 1691 | 1711 | foreignExportsFinishedLoadingObject();
|
| 1692 | 1712 | |
| 1693 | 1713 | if (!r) { return r; }
|
| 1714 | + oc->initializersRan = true;
|
|
| 1694 | 1715 | oc->status = OBJECT_READY;
|
| 1695 | 1716 | |
| 1696 | 1717 | 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]. */
|
| ... | ... | @@ -1308,6 +1308,16 @@ do_Elf_Rel_relocations ( ObjectCode* oc, char* ehdrC, |
| 1308 | 1308 | case COMPAT_R_386_NONE: break;
|
| 1309 | 1309 | case COMPAT_R_386_32: *pP = value; break;
|
| 1310 | 1310 | case COMPAT_R_386_PC32: *pP = value - P; break;
|
| 1311 | + case COMPAT_R_386_PLT32: *pP = value - P; break;
|
|
| 1312 | + case COMPAT_R_386_GOTOFF: *pP = value - (Elf_Addr)oc->info->got_start; break;
|
|
| 1313 | + case COMPAT_R_386_GOTPC: *pP = (Elf_Addr)oc->info->got_start + A - P; break;
|
|
| 1314 | + case COMPAT_R_386_GOT32:
|
|
| 1315 | + case COMPAT_R_386_GOT32X:
|
|
| 1316 | + CHECK(symbol);
|
|
| 1317 | + CHECK(symbol->got_addr);
|
|
| 1318 | + *pP = (Elf_Addr)symbol->got_addr
|
|
| 1319 | + - (Elf_Addr)oc->info->got_start + A;
|
|
| 1320 | + break;
|
|
| 1311 | 1321 | # endif
|
| 1312 | 1322 | |
| 1313 | 1323 | # if defined(arm_HOST_ARCH)
|
| 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 | +} |
| ... | ... | @@ -260,3 +260,22 @@ test('T25364', normal, compile_and_run, ['']) |
| 260 | 260 | test('T26061', normal, compile_and_run, [''])
|
| 261 | 261 | test('T26537', normal, compile_and_run, ['-O2 -fregs-graph'])
|
| 262 | 262 | test('T24016', normal, compile_and_run, ['-O1 -fPIC'])
|
| 263 | + |
|
| 264 | +# Check that GHC-generated finalizers run on Darwin. The Apple linker doesn't
|
|
| 265 | +# support --wrap, so we can't intercept hs_spt_remove directly. Instead we
|
|
| 266 | +# compile a small checker dylib (T27072d_check.c) whose constructor registers
|
|
| 267 | +# a __cxa_atexit handler *before* the executable's __mod_init_func entries run.
|
|
| 268 | +# LIFO ordering ensures the checker fires after the GHC-generated finalizer,
|
|
| 269 | +# so it can observe that SPT entries were removed.
|
|
| 270 | +# Requires dynamic way so the RTS is a dylib (avoids archive conflicts).
|
|
| 271 | +test('T27072d', [req_c, only_ways(['dyn']), when(not opsys('darwin'), skip),
|
|
| 272 | + pre_cmd('{compiler} -shared -no-hs-main'
|
|
| 273 | + ' -optl -undefined -optl dynamic_lookup'
|
|
| 274 | + ' -o T27072d_check.dylib T27072d_check.c')],
|
|
| 275 | + compile_and_run,
|
|
| 276 | + ['T27072d_c.c -no-hs-main'
|
|
| 277 | + ' -optl -Wl,-needed_library,T27072d_check.dylib -optl -rpath -optl .'])
|
|
| 278 | +# check that finalizers are being run, using --wrap to intercept hs_spt_remove.
|
|
| 279 | +# Skipped on Darwin (Apple linker doesn't support --wrap).
|
|
| 280 | +test('T27072w', [req_c, js_skip, when(opsys('darwin'), skip)],
|
|
| 281 | + compile_and_run, ['T27072w_c.c -no-hs-main -optl-Wl,--wrap=hs_spt_remove']) |
| 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 | +} |
| ... | ... | @@ -636,7 +636,6 @@ test('T25209', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques']) |
| 636 | 636 | test('TH_MultilineStrings', normal, compile_and_run, [''])
|
| 637 | 637 | test('T25252',
|
| 638 | 638 | [extra_files(['T25252B.hs', 'T25252_c.c']),
|
| 639 | - when(arch('i386'), expect_broken_for(25260,['ext-interp'])),
|
|
| 640 | 639 | req_th,
|
| 641 | 640 | req_c],
|
| 642 | 641 | compile_and_run, ['-fPIC T25252_c.c'])
|