Add -fkeep-cafs
authorSimon Marlow <marlowsd@gmail.com>
Fri, 28 Sep 2018 12:27:22 +0000 (14:27 +0200)
committerKrzysztof Gogolewski <krz.gogolewski@gmail.com>
Fri, 28 Sep 2018 12:27:22 +0000 (14:27 +0200)
Summary:
I noticed while playing around with
https://github.com/fbsamples/ghc-hotswap/ that the main binary needs to
have a custom main function to set `config.keep_cafs = true` when
initialising the runtime. This is pretty annoying, it means an extra
C file with some cryptic incantations in it, and a `-no-hs-main` flag.

So I've replaced this with a link-time flag to GHC, `-fkeep-cafs` that
does the same thing.

Test Plan:
New unit test that tests for the RTS's GC'd CAFs assertion, and also
the -keep-cafs flag.

Reviewers: bgamari, osa1, erikd, noamz

Reviewed By: osa1

Subscribers: rwbarton, carter

Differential Revision: https://phabricator.haskell.org/D5183

compiler/main/DynFlags.hs
compiler/main/SysTools/ExtraObj.hs
docs/users_guide/phases.rst
testsuite/tests/rts/KeepCafs1.hs [new file with mode: 0644]
testsuite/tests/rts/KeepCafs2.hs [new file with mode: 0644]
testsuite/tests/rts/KeepCafsBase.hs [new file with mode: 0644]
testsuite/tests/rts/KeepCafsMain.hs [new file with mode: 0644]
testsuite/tests/rts/Makefile
testsuite/tests/rts/all.T
testsuite/tests/rts/keep-cafs-fail.stdout [new file with mode: 0644]
testsuite/tests/rts/keep-cafs.stdout [new file with mode: 0644]

index e7e541b..7726001 100644 (file)
@@ -564,6 +564,7 @@ data GeneralFlag
    -- forwards all -L flags to the collect2 command without using a
    -- response file and as such breaking apart.
    | Opt_SingleLibFolder
+   | Opt_KeepCAFs
 
    -- output style opts
    | Opt_ErrorSpans -- Include full span info in error messages,
@@ -4003,7 +4004,8 @@ fFlagsDeps = [
   flagSpec "show-warning-groups"              Opt_ShowWarnGroups,
   flagSpec "hide-source-paths"                Opt_HideSourcePaths,
   flagSpec "show-loaded-modules"              Opt_ShowLoadedModules,
-  flagSpec "whole-archive-hs-libs"            Opt_WholeArchiveHsLibs
+  flagSpec "whole-archive-hs-libs"            Opt_WholeArchiveHsLibs,
+  flagSpec "keep-cafs"                        Opt_KeepCAFs
   ]
   ++ fHoleFlags
 
index bbcb1b6..774884a 100644 (file)
@@ -104,6 +104,10 @@ mkExtraObjToLinkIntoBinary dflags = do
             <> text (if rtsOptsSuggestions dflags
                         then "true"
                         else "false") <> semi,
+        text "__conf.keep_cafs = "
+            <> text (if gopt Opt_KeepCAFs dflags
+                       then "true"
+                       else "false") <> semi,
         case rtsOpts dflags of
             Nothing   -> Outputable.empty
             Just opts -> text "    __conf.rts_opts= " <>
index 531f8c0..788b9be 100644 (file)
@@ -1169,3 +1169,17 @@ for example).
 
     Also, you may need to use the :ghc-flag:`-rdynamic` flag to ensure that
     that symbols are not dropped from your PIE objects.
+
+.. ghc-flag:: -keep-cafs
+    :shortdesc: Do not garbage-collect CAFs (top-level expressions) at runtime
+    :type: dynamic
+    :category: linking
+
+    :since: 8.8.1
+
+    Disables the RTS's normal behaviour of garbage-collecting CAFs
+    (Constant Applicative Forms, in other words top-level
+    expressions). This option is useful for specialised applications
+    that do runtime dynamic linking, where code dynamically linked in
+    the future might require the value of a CAF that would otherwise
+    be garbage-collected.
diff --git a/testsuite/tests/rts/KeepCafs1.hs b/testsuite/tests/rts/KeepCafs1.hs
new file mode 100644 (file)
index 0000000..f654bfb
--- /dev/null
@@ -0,0 +1,9 @@
+module KeepCafs1 where
+
+import KeepCafsBase
+
+foreign export ccall "getX"
+  getX :: IO Int
+
+getX :: IO Int
+getX = return x
diff --git a/testsuite/tests/rts/KeepCafs2.hs b/testsuite/tests/rts/KeepCafs2.hs
new file mode 100644 (file)
index 0000000..ac57430
--- /dev/null
@@ -0,0 +1,9 @@
+module KeepCafs2 where
+
+import KeepCafsBase
+
+foreign export ccall "getX"
+  getX :: IO Int
+
+getX :: IO Int
+getX = return (x + 1)
diff --git a/testsuite/tests/rts/KeepCafsBase.hs b/testsuite/tests/rts/KeepCafsBase.hs
new file mode 100644 (file)
index 0000000..184db3d
--- /dev/null
@@ -0,0 +1,4 @@
+module KeepCafsBase (x) where
+
+x :: Int
+x = last [1..1000]
diff --git a/testsuite/tests/rts/KeepCafsMain.hs b/testsuite/tests/rts/KeepCafsMain.hs
new file mode 100644 (file)
index 0000000..2f6ad5a
--- /dev/null
@@ -0,0 +1,25 @@
+module Main (main) where
+
+import Foreign
+import GHCi.ObjLink
+import System.Mem
+import System.Exit
+
+foreign import ccall "dynamic"
+  callGetX :: FunPtr (IO Int) -> IO Int
+
+main :: IO ()
+main = do
+  initObjLinker DontRetainCAFs
+  let
+    loadAndCall obj = do
+      loadObj obj
+      resolveObjs
+      r <- lookupSymbol "getX"
+      case r of
+        Nothing -> die "cannot find getX"
+        Just ptr -> callGetX (castPtrToFunPtr ptr) >>= print
+      unloadObj obj
+      performGC
+  loadAndCall "KeepCafs1.o"
+  loadAndCall "KeepCafs2.o"
index bf7e163..496e04e 100644 (file)
@@ -190,3 +190,13 @@ T14695:
 InternalCounters:
        "$(TEST_HC)" +RTS -s --internal-counters -RTS 2>&1 | grep "Internal Counters"
        -"$(TEST_HC)" +RTS -s -RTS 2>&1 | grep "Internal Counters"
+
+.PHONY: KeepCafsFail
+KeepCafsFail:
+       "$(TEST_HC)" -c -g -v0 KeepCafsBase.hs KeepCafs1.hs KeepCafs2.hs
+       "$(TEST_HC)" -g -v0 KeepCafsMain.hs KeepCafsBase.o -debug -rdynamic -fwhole-archive-hs-libs $(KEEPCAFS)
+       ./KeepCafsMain 2>&1 || echo "exit($$?)"
+
+.PHONY: KeepCafs
+KeepCafs:
+       "${MAKE}" KeepCafsFail KEEPCAFS=-fkeep-cafs
index eb06dcc..a537ee4 100644 (file)
@@ -431,3 +431,25 @@ test('nursery-chunks1',
   ],
   compile_and_run,
   [''])
+
+# Test for the "Evaluated a CAF that was GC'd" assertion in the debug
+# runtime, by dynamically loading code that re-evaluates the CAF.
+# Also tests the -rdynamic and -fwhole-archive-hs-libs flags for constructing
+# binaries that support runtime dynamic loading.
+test('keep-cafs-fail',
+  [ extra_files(['KeepCafsBase.hs', 'KeepCafs1.hs',
+                 'KeepCafs2.hs', 'KeepCafsMain.hs']),
+    filter_stdout_lines('Evaluated a CAF|exit.*'),
+    ignore_stderr, # on OS X the shell emits an "Abort trap" message to stderr
+  ],
+  run_command,
+  ['$MAKE -s --no-print-directory KeepCafsFail'])
+
+# Test the -fkeep-cafs flag
+test('keep-cafs',
+  [ extra_files(['KeepCafsBase.hs', 'KeepCafs1.hs',
+                 'KeepCafs2.hs', 'KeepCafsMain.hs'])
+  ],
+  run_command,
+  ['$MAKE -s --no-print-directory KeepCafs'])
+
diff --git a/testsuite/tests/rts/keep-cafs-fail.stdout b/testsuite/tests/rts/keep-cafs-fail.stdout
new file mode 100644 (file)
index 0000000..6eaf652
--- /dev/null
@@ -0,0 +1,5 @@
+KeepCafsMain: internal error: Evaluated a CAF (0xaac9d8) that was GC'd!
+    (GHC version 8.7.20180910 for x86_64_unknown_linux)
+    Please report this as a GHC bug:  http://www.haskell.org/ghc/reportabug
+Aborted (core dumped)
+exit(134)
diff --git a/testsuite/tests/rts/keep-cafs.stdout b/testsuite/tests/rts/keep-cafs.stdout
new file mode 100644 (file)
index 0000000..b5b9afd
--- /dev/null
@@ -0,0 +1,2 @@
+1000
+1001