[implement finalizers in the RTS, add foreignptr rts routines, properly set saved_gc on safe ffi calls
John Meacham <john@repetae.net>**20120221132313
 Ignore-this: 1a27919674abc4a6955c245eec88aaf9
] hunk ./Makefile.am 117
-	   rts/lib/lib_cbits.c rts/jhc_rts_header.h rts/lib/lib_cbits.h
+	   rts/lib/lib_cbits.c rts/jhc_rts_header.h rts/lib/lib_cbits.h rts/rts/gc_jgc_internal.h
hunk ./lib/jhc/System/Mem.hs 7
-foreign import ccall "hs_perform_gc" performGC :: IO ()
+foreign import ccall safe "hs_perform_gc" performGC :: IO ()
hunk ./rts/rts/constants.h 30
-#define SLAB_VIRTUAL_VALUE    256
+#define SLAB_VIRTUAL_VALUE     256
hunk ./rts/rts/constants.h 33
-#define SLAB_VIRTUAL_SPECIAL  512
+#define SLAB_VIRTUAL_SPECIAL   512
hunk ./rts/rts/constants.h 36
-#define SLAB_VIRTUAL_CONSTANT 1024
+#define SLAB_VIRTUAL_CONSTANT  1024
+
+// virtual flag to indication location has been freed. (for debugging)
+#define SLAB_VIRTUAL_FREED     2048
+
+// virtual flag to indication location is lazy.
+#define SLAB_VIRTUAL_LAZY      4096
+
+// virtual flag to indication location is func.
+#define SLAB_VIRTUAL_FUNC      8192
hunk ./rts/rts/gc_jgc.c 6
+#include "rts/gc_jgc_internal.h"
hunk ./rts/rts/gc_jgc.c 10
-struct s_arena {
-        struct s_megablock *current_megablock;
-        SLIST_HEAD(,s_block) free_blocks;
-        unsigned block_used;
-        unsigned block_threshold;
-        SLIST_HEAD(,s_cache) caches;
-        SLIST_HEAD(,s_block) monolithic_blocks;
-        SLIST_HEAD(,s_megablock) megablocks;
-        unsigned number_gcs;    // number of garbage collections
-        unsigned number_allocs; // number of allocations since last garbage collection
-};
-
-struct s_megablock {
-        void *base;
-        unsigned next_free;
-        SLIST_ENTRY(s_megablock) next;
-};
-
-struct s_block {
-        SLIST_ENTRY(s_block) link;
-        unsigned char flags;  // defined in rts/constants.h
-        unsigned char color;  // offset in words to first entry.
-        union {
-                // A normal block.
-                struct {
-                        unsigned char num_ptrs;
-                        unsigned char size;
-                        unsigned short num_free;
-                        unsigned short next_free;
-                } pi;
-                // A monolithic block.
-                struct {
-                        unsigned num_ptrs;
-                } m;
-        } u;
-        bitarray_t used[];
-};
-
-struct s_cache {
-        SLIST_ENTRY(s_cache) next;
-        SLIST_HEAD(,s_block) blocks;
-        SLIST_HEAD(,s_block) full_blocks;
-        unsigned char color;
-        unsigned char size;
-        unsigned char num_ptrs;
-        unsigned char flags;
-        unsigned short num_entries;
-        struct s_arena *arena;
-        void (*init_fn)(void *);
-        void (*fini_fn)(void *);
-#if _JHC_PROFILE
-        unsigned allocations;
-#endif
-};
-
hunk ./rts/rts/gc_jgc.c 16
-static void gc_perform_gc(gc_t gc);
+void gc_perform_gc(gc_t gc) A_STD;
hunk ./rts/rts/gc_jgc.c 84
-static void
+void A_STD
hunk ./rts/rts/gc_jgc.c 167
-        if(JHC_STATUS) {
+        if (JHC_STATUS) {
hunk ./rts/rts/gc_jgc.c 185
-#define GC_MAX_BLOCK_ENTRIES 100
+#define GC_MAX_BLOCK_ENTRIES 150
hunk ./rts/rts/gc_jgc.c 188
-static struct s_cache *array_atomic_caches[GC_STATIC_ARRAY_NUM];
+static struct s_cache *array_caches_atomic[GC_STATIC_ARRAY_NUM];
hunk ./rts/rts/gc_jgc.c 206
+                find_cache(&array_caches_atomic[i], arena, i + 1, 0);
hunk ./rts/rts/gc_jgc.c 216
-                struct s_cache *sc = SLIST_FIRST(&arena->caches);
-                for(;sc;sc = SLIST_NEXT(sc,next)) {
+                struct s_cache *sc;
+                SLIST_FOREACH(sc,&arena->caches,next)
hunk ./rts/rts/gc_jgc.c 219
-                }
hunk ./rts/rts/gc_jgc.c 233
-s_monoblock(struct s_arena *arena, unsigned size, unsigned nptrs) {
+s_monoblock(struct s_arena *arena, unsigned size, unsigned nptrs, unsigned flags) {
hunk ./rts/rts/gc_jgc.c 235
-        b->flags = SLAB_MONOLITH;
+        b->flags = flags | SLAB_MONOLITH;
hunk ./rts/rts/gc_jgc.c 244
-// Allocate an array of count garbage collectable locations in the garbage collected heap
+// Allocate an array of count garbage collectable locations in the garbage
+// collected heap.
hunk ./rts/rts/gc_jgc.c 255
-        return s_monoblock(arena, count, count);
+        return s_monoblock(arena, count, count, 0);
+        abort();
+}
+
+// Allocate an array of count non-garbage collectable locations in the garbage
+// collected heap.
+heap_t A_STD
+gc_array_alloc_atomic(gc_t gc, unsigned count, unsigned flags)
+{
+        if (!count)
+               return NULL;
+        if (count <= GC_STATIC_ARRAY_NUM && !flags)
+                return (wptr_t)s_alloc(gc,array_caches_atomic[count - 1]);
+        if (count < GC_MAX_BLOCK_ENTRIES && !flags)
+                return s_alloc(gc, find_cache(NULL, arena, count, 0));
+        return s_monoblock(arena, count, count, flags);
hunk ./rts/rts/gc_jgc.c 299
-        int ret = !mb->base;
+        int ret = !base;
hunk ./rts/rts/gc_jgc.c 302
-        int ret = !mb->base;
+        int ret = !base;
hunk ./rts/rts/gc_jgc.c 306
-        int ret = !mb->base;
+        int ret = !base;
hunk ./rts/rts/gc_jgc.c 359
+typedef void (*finalizer_ptr)(HsPtr arg);
+typedef void (*finalizer_env_ptr)(HsPtr env, HsPtr arg);
+
+void hs_foreignptr_env_helper(HsPtr env, HsPtr arg) {
+        ((finalizer_ptr)env)(arg);
+}
+
hunk ./rts/rts/gc_jgc.c 371
-                if(BIT_IS_SET(pg->used, 0)) {
+                if (pg->used[0]) {
hunk ./rts/rts/gc_jgc.c 374
-                }
-                else  {
+                } else {
+                        if (pg->flags & SLAB_FLAG_FINALIZER) {
+                                HsPtr *ptr = (HsPtr *)pg;
+                                if(ptr[pg->color + 1]) {
+                                        finalizer_ptr *fp = ptr[pg->color + 1];
+                                        do {
+                                                fp[0](ptr[pg->color]);
+                                        } while(*++fp);
+                                }
+                        }
hunk ./rts/rts/gc_jgc.c 599
+uint32_t
+get_heap_flags(void * sp) {
+        uint32_t ret = 0;
+        switch (GET_PTYPE(sp)) {
+        case P_VALUE: return SLAB_VIRTUAL_VALUE;
+        case P_FUNC: return SLAB_VIRTUAL_FUNC;
+        case P_LAZY:
+                     ret |= SLAB_VIRTUAL_LAZY;
+        case P_WHNF:
+                     if (S_BLOCK(sp) == NULL)
+                             return (ret | SLAB_VIRTUAL_SPECIAL);
+                     if ((void *)sp >= nh_start && (void *)sp <= nh_end)
+                             return (ret | SLAB_VIRTUAL_CONSTANT);
+                     return ret |= S_BLOCK(sp)->flags;
+        }
+        return ret;
+}
+
+heap_t A_STD
+gc_malloc_foreignptr(unsigned alignment, unsigned size, bool finalizer) {
+        // we don't allow higher alignments yet.
+        assert (alignment <= sizeof(uintptr_t));
+        // no finalizers yet
+        assert (!finalizer);
+        unsigned spacing = 1 + finalizer;
+        wptr_t *res = gc_array_alloc_atomic(saved_gc, spacing + TO_BLOCKS(size),
+                                             finalizer ? SLAB_FLAG_FINALIZER : SLAB_FLAG_NONE);
+        res[0] = (wptr_t)(res + spacing);
+        if (finalizer)
+                res[1] = NULL;
+        return TO_SPTR(P_WHNF, res);
+}
+
+heap_t A_STD
+gc_new_foreignptr(HsPtr ptr) {
+        HsPtr *res = gc_array_alloc_atomic(saved_gc, 2, SLAB_FLAG_FINALIZER);
+        res[0] = ptr;
+        res[1] = NULL;
+        return TO_SPTR(P_WHNF, res);
+}
+
+bool A_STD
+gc_add_foreignptr_finalizer(wptr_t fp, HsFunPtr finalizer) {
+        if (!(SLAB_FLAG_FINALIZER & get_heap_flags(fp)))
+                return false;
+        HsFunPtr **res = (HsFunPtr**)FROM_SPTR(fp);
+        unsigned len = 0;
+        if (res[1])
+                while(res[1][len++]);
+        else
+                len = 1;
+        res[1] = realloc(res[1], (len + 1) * sizeof(HsFunPtr));
+        HsFunPtr *ptrs = res[1];
+        ptrs[len - 1] = finalizer;
+        ptrs[len] = NULL;
+        return true;
+}
+
hunk ./rts/rts/gc_jgc.c 678
+void hs_perform_gc(void) {
+        gc_perform_gc(saved_gc);
+}
+
hunk ./rts/rts/gc_jgc.h 5
+#include <stdint.h>
hunk ./rts/rts/gc_jgc.h 7
+#include "HsFFI.h"
hunk ./rts/rts/gc_jgc.h 18
-#define GC_BASE sizeof(void *)
-#define TO_BLOCKS(x) ((x) <= GC_BASE ? 1 : (((x) - 1)/GC_BASE) + 1)
+#define TO_BLOCKS(x) (((x) + sizeof(uintptr_t) - 1)/sizeof(uintptr_t))
hunk ./rts/rts/gc_jgc.h 30
+void A_STD gc_perform_gc(gc_t gc);
+uint32_t get_heap_flags(void* sp);
hunk ./rts/rts/gc_jgc.h 34
-heap_t (gc_alloc)(gc_t gc,struct s_cache **sc, unsigned count, unsigned nptrs);
-heap_t gc_array_alloc(gc_t gc, unsigned count);
-heap_t gc_array_alloc_atomic(gc_t gc, unsigned count);
+heap_t (gc_alloc)(gc_t gc,struct s_cache **sc, unsigned count, unsigned nptrs) A_STD;
+heap_t gc_array_alloc(gc_t gc, unsigned count) A_STD;
+heap_t gc_array_alloc_atomic(gc_t gc, unsigned count, unsigned slab_flags) A_STD;
+/* foreignptr, saved_gc must be set properly. */
+heap_t gc_malloc_foreignptr(unsigned alignment, unsigned size, bool finalizer) A_STD;
+heap_t gc_new_foreignptr(HsPtr ptr) A_STD;
+bool gc_add_foreignptr_finalizer(struct sptr* fp, HsFunPtr finalizer) A_STD;
addfile ./rts/rts/gc_jgc_internal.h
hunk ./rts/rts/gc_jgc_internal.h 1
+#ifndef GC_JGC_INTERNAL_H
+#define GC_JGC_INTERNAL_H
+
+#include "rts/gc_jgc.h"
+#include "sys/bitarray.h"
+#include "sys/queue.h"
+
+#if _JHC_GC == _JHC_GC_JGC
+
+struct s_arena {
+        struct s_megablock *current_megablock;
+        SLIST_HEAD(,s_block) free_blocks;
+        unsigned block_used;
+        unsigned block_threshold;
+        SLIST_HEAD(,s_cache) caches;
+        SLIST_HEAD(,s_block) monolithic_blocks;
+        SLIST_HEAD(,s_megablock) megablocks;
+        unsigned number_gcs;    // number of garbage collections
+        unsigned number_allocs; // number of allocations since last garbage collection
+};
+
+struct s_megablock {
+        void *base;
+        unsigned next_free;
+        SLIST_ENTRY(s_megablock) next;
+};
+
+struct s_block {
+        SLIST_ENTRY(s_block) link;
+        unsigned char flags;  // defined in rts/constants.h
+        unsigned char color;  // offset in words to first entry.
+        union {
+                // A normal block.
+                struct {
+                        unsigned char num_ptrs;
+                        unsigned char size;
+                        unsigned short num_free;
+                        unsigned short next_free;
+                } pi;
+                // A monolithic block.
+                struct {
+                        unsigned num_ptrs;
+                } m;
+        } u;
+        bitarray_t used[];
+};
+
+struct s_cache {
+        SLIST_ENTRY(s_cache) next;
+        SLIST_HEAD(,s_block) blocks;
+        SLIST_HEAD(,s_block) full_blocks;
+        unsigned char color;
+        unsigned char size;
+        unsigned char num_ptrs;
+        unsigned char flags;
+        unsigned short num_entries;
+        struct s_arena *arena;
+#if _JHC_PROFILE
+        unsigned allocations;
+#endif
+};
+#endif
+#endif
hunk ./rts/test/Makefile 8
-TESTS=slab_test stableptr_test
+TESTS=slab_test stableptr_test jgc_test
hunk ./rts/test/Makefile 14
-slab_test: slab_test.c $(RTSFILES)
-
hunk ./rts/test/Makefile 20
+	./jgc_test
hunk ./rts/test/Makefile 23
+slab_test: slab_test.c $(RTSFILES)
+jgc_test:  jgc_test.c seatest.c $(RTSFILES)
addfile ./rts/test/jgc_test.c
hunk ./rts/test/jgc_test.c 1
+#include "jhc_rts_header.h"
+#include "rts/gc_jgc_internal.h"
+#include "rts/constants.h"
+#include "seatest.h"
+
+bool
+block_aligned(void *p) {
+        return (S_BLOCK(p) == p);
+}
+
+void
+block_sanity(struct s_arena *arena, struct s_cache *sc, struct s_block *b) {
+        assert_true (!sc == !!(b->flags & SLAB_MONOLITH));
+        assert_true (block_aligned(b));
+}
+
+void
+cache_sanity(struct s_arena *arena, struct s_cache *sc) {
+        struct s_block *b;
+        assert_true(sc->arena == arena);
+        SLIST_FOREACH(b, &sc->blocks, link)
+            block_sanity(arena, sc, b);
+}
+
+void
+arena_sanity(struct s_arena *arena) {
+        assert_true(!!arena);
+        struct s_cache *sc;
+        struct s_block *b;
+        SLIST_FOREACH(sc, &arena->caches, next)
+            cache_sanity(arena, sc);
+        SLIST_FOREACH(b, &arena->monolithic_blocks, link)
+            block_sanity(arena, NULL, b);
+
+}
+
+#define PTR1 (HsPtr)0xDEADBEEF
+#define PTR2 (HsPtr)0xB00B1E5
+
+void foreignptr_test(void) {
+        gc_t gc = saved_gc;
+        HsPtr **ptr = gc_new_foreignptr(PTR1);
+        assert_ptr_equal(PTR1, ptr[0]);
+        assert_ptr_equal(NULL, ptr[1]);
+        assert_true(gc_add_foreignptr_finalizer((sptr_t)ptr, PTR2));
+        assert_bit_mask_matches(get_heap_flags(ptr), SLAB_FLAG_FINALIZER);
+        assert_ptr_equal(PTR1, ptr[0]);
+        assert_true(!!ptr[1]);
+        assert_ptr_equal(ptr[1][0], PTR2);
+        arena_sanity(arena);
+}
+
+void basic_test(void) {
+        arena_sanity(arena);
+        gc_t gc = saved_gc;
+        heap_t e = gc_alloc(gc, NULL, 2, 2);
+        ((void **)e)[0] = e;
+        ((void **)e)[1] = e;
+        gc[0] = e;
+        arena_sanity(arena);
+        gc_perform_gc(gc + 1);
+        arena_sanity(arena);
+}
+
+int main(int argc, char *argv[])
+{
+        hs_init(&argc, &argv);
+        test_fixture_start();
+        run_test(basic_test);
+        run_test(foreignptr_test);
+        test_fixture_end();
+        hs_exit();
+        return 0;
+}
hunk ./rts/test/seatest.c 108
+void seatest_assert_ptr_equal(void * expected, void * actual, const char* function, unsigned int line)
+{
+	char s[SEATEST_PRINT_BUFFER_SIZE];
+	sprintf(s, "Expected %p but was %p", expected, actual);
+	seatest_simple_test_result(expected==actual, s, function, line);
+}
+
hunk ./rts/test/seatest.h 24
+void seatest_assert_ptr_equal(void * expected, void * actual, const char* function, unsigned int line);
hunk ./rts/test/seatest.h 47
+#define assert_ptr_equal(expected, actual) do {  seatest_assert_ptr_equal(expected, actual, __FUNCTION__, __LINE__); } while (0)
hunk ./rts/test/slab_test.c 1
-#define JHC_VALGRIND 1
-
-#define _JHC_STANDALONE 0
-#define _JHC_GC _JHC_GC_JGC
-
hunk ./src/C/FFI.hs 16
-data Safety = Safe | Unsafe deriving(Eq,Ord,Show)
-    {-! derive: Binary !-}
-
hunk ./src/C/FromGrin2.hs 533
-nodeAssign v t as e' = cna where
-    cna = do
-        cpr <- asks rCPR
-        v' <- convertVal v
-        case mlookup t cpr of
-            Just (TyRepRawVal signed) -> do
-                [arg] <- return as
-                t <- convertType $ getType arg
-                arg' <- iDeclare $ convertVal arg
-                let s = arg' =* cast t (if signed then f_RAW_GET_F v' else f_RAW_GET_UF v')
-                ss <- convertBody e'
-                return $ s & ss
-            _ -> do
-                declareStruct t
-                as' <- iDeclare $ mapM convertVal as
-                let ass = concat [perhapsM (a `Set.member` fve) $ a' =* (project' (arg i) (concrete t v')) | a' <- as' | Var a _ <- as |  i <- [( 1 :: Int) ..] ]
-                    fve = freeVars e'
-                ss' <- convertBody e'
-                return $ mconcat ass & ss'
+nodeAssign v t as e' = do
+    cpr <- asks rCPR
+    v' <- convertVal v
+    case mlookup t cpr of
+        Just (TyRepRawVal signed) -> do
+            [arg] <- return as
+            t <- convertType $ getType arg
+            arg' <- iDeclare $ convertVal arg
+            let s = arg' =* cast t (if signed then f_RAW_GET_F v' else f_RAW_GET_UF v')
+            ss <- convertBody e'
+            return $ s & ss
+        _ -> do
+            declareStruct t
+            as' <- iDeclare $ mapM convertVal as
+            let ass = concat [perhapsM (a `Set.member` fve) $ a' =* (project' (arg i) (concrete t v')) | a' <- as' | Var a _ <- as |  i <- [( 1 :: Int) ..] ]
+                fve = freeVars e'
+            ss' <- convertBody e'
+            return $ mconcat ass & ss'
hunk ./src/C/FromGrin2.hs 563
-convertExp (Prim (Func _ n as r rs@(_:_)) vs ty) = do
-        vs' <- mapM convertVal vs
-        rt <- mapM convertType ty
-        let rrs = map basicType' (r:rs)
-        ras <- mapM (newVar . basicType') rs
-        (stmt,rv) <- basicType' r `newTmpVar` (functionCall (name $ unpackPS n) ([ cast (basicType' t) v | v <- vs' | t <- as ] ++ map reference ras))
-        return $ (stmt, structAnon (zip (rv:ras) rt))
+convertExp (Prim Func { primArgTypes = as, primRetType = r, primRetArgs = rs@(_:_), ..} vs ty) = do
+    tell mempty { wRequires = primRequires }
+    vs' <- mapM convertVal vs
+    rt <- mapM convertType ty
+    let rrs = map basicType' (r:rs)
+    ras <- mapM (newVar . basicType') rs
+    (stmt,rv) <- basicType' r `newTmpVar` (functionCall (name $ unpackPS funcName) ([ cast (basicType' t) v | v <- vs' | t <- as ] ++ map reference ras))
+    return $ (stmt, structAnon (zip (rv:ras) rt))
+convertExp (Prim Func { primRetArgs = [], .. } vs ty) = do
+    tell mempty { wRequires = primRequires }
+    vs' <- mapM convertVal vs
+    rt <- convertTypes ty
+    let fcall =  cast rt (functionCall (name $ unpackPS funcName) [ cast (basicType' t) v | v <- vs' | t <- primArgTypes ])
+    return (if primSafety == Safe && fopts FO.Jgc then v_saved_gc =* v_gc else mempty,fcall)
hunk ./src/C/FromGrin2.hs 612
-convertExp Alloc { expValue = v, expCount = c, expRegion = r } | r == region_heap, TyINode == getType v  = do
+convertExp Alloc { expValue = v, expCount = c, expRegion = r }
+        | r == region_heap, TyINode == getType v  = do
hunk ./src/C/FromGrin2.hs 627
-        (malloc,tmp) <- jhc_malloc_atomic (operator "*" (sizeof uintptr_t) c') =:: ptrType uintptr_t
+        (malloc,tmp) <- jhc_malloc_atomic c' =:: ptrType uintptr_t
hunk ./src/C/FromGrin2.hs 638
-ccaf (v,val) = text "/* " <> text (show v) <> text " = " <> (text $ P.render (pprint val)) <> text "*/\n" <>
+ccaf (v,val) = text "/* " <> text (show v) <> text " = " <>
+    (text $ P.render (pprint val)) <> text "*/\n" <>
hunk ./src/C/FromGrin2.hs 641
-     text "#define " <> tshow (varName v) <+>  text "(MKLAZY_C(&_" <> tshow (varName v) <> text "))\n";
+     text "#define " <> tshow (varName v) <+>  text "(MKLAZY_C(&_" <>
+     tshow (varName v) <> text "))\n";
hunk ./src/C/FromGrin2.hs 703
-    | (Func _ n as r []) <- p = do
-        vs' <- mapM convertVal vs
-        rt <- convertTypes ty
-        return $ cast rt (functionCall (name $ unpackPS n) [ cast (basicType' t) v | v <- vs' | t <- as ])
hunk ./src/C/FromGrin2.hs 919
---gc_roots vs = functionCall (name "gc_begin_frame0") (constant (number (fromIntegral $ length vs)):vs)
---gc_roots vs   = functionCall (name "gc_frame0") (v_gc:constant (number (fromIntegral $ length vs)):vs)
hunk ./src/C/FromGrin2.hs 926
-jhc_malloc_atomic sz | fopts FO.Jgc = functionCall (name "gc_alloc") [v_gc,nullPtr, tbsize sz, toExpression (0::Int)]
-                     | otherwise = jhc_malloc nullPtr (0::Int) sz
+jhc_malloc_atomic sz | fopts FO.Jgc = functionCall (name "gc_array_alloc_atomic") [v_gc,nullPtr, sz, toExpression (0::Int)]
+                     | otherwise = jhc_malloc nullPtr (0::Int) (sizeof sptr_t *# sz)
hunk ./src/C/FromGrin2.hs 975
+v_saved_gc = variable (name "saved_gc")
hunk ./src/C/Prims.hs 17
-data CallConv = CCall | StdCall | CApi | Primitive | DotNet 
+data CallConv = CCall | StdCall | CApi | Primitive | DotNet
hunk ./src/C/Prims.hs 21
+data Safety = Safe | Unsafe deriving(Eq,Ord,Show)
+    {-! derive: Binary !-}
+
hunk ./src/C/Prims.hs 58
-	primRetArgs :: [ExtType]
+	primRetArgs :: [ExtType],
+        primSafety  :: Safety
hunk ./src/E/FromHs.hs 402
-            (True,ExtTypeVoid,[]) -> cFun $ \rs -> return (ELam tvrWorld, 
+            (True,ExtTypeVoid,[]) -> cFun $ \rs -> return (ELam tvrWorld,
hunk ./src/E/FromHs.hs 462
-    cDecl (HsForeignDecl _ (FfiSpec (Import rcn req) _ CCall) n _) = do
+    cDecl (HsForeignDecl _ (FfiSpec (Import rcn req) safe CCall) n _) = do
hunk ./src/E/FromHs.hs 467
-                      EPrim (Func req (packString rcn) cts crt cras) args rt)
+                      EPrim (Func req (packString rcn) cts crt cras safe) args rt)
hunk ./src/E/FromHs.hs 1003
-
hunk ./src/Grin/Main.hs 118
+           ("rts/gc_jgc_internal.h",gc_jgc_internal_h),