GCDEF(gc_closure)
  int i, size;
  hg_t *hg;
  void *p, *q;
  void **pp, **oo;
  void *fixed_size, *dummy;
  size = O_SIZE(o);
  //fprintf(stderr, "beg closure: %d\n", size);
  CLOSURE(p, O_CODE(o), size);
  GC_REDIR(o,p);
  pp = (void**)&LGET(p,0);
  oo = (void**)&LGET(o,0);
  for (i = 0; i < size; i++) {
    GC_REC2(pp[i],oo[i]);
  }
  //fprintf(stderr, "end closure\n");
GCEND(p)

GCDEF(gc_list)
  uint32_t i, size;
  void *p;
  void **pp, **oo;
  size = LIST_SIZE(o);
  /* DEBUG (behind SYMTA_DUMP_SEGV): catch sources with garbage
   * size before gc_alloc underruns the heap, and scan the live
   * heap for the slot that holds the stale dyn `o` so we can
   * re-run with a page watchpoint on it.  This is how bug #13
   * was tracked down to parse_term's parsed-cache write. */
  if (size > 0x100000 && getenv("SYMTA_DUMP_SEGV")) {
    fprintf(stderr,
            "[GCLIST_BAD] o=%p size=%u (0x%x) gid=%llu\n",
            (void*)o, size, size, (unsigned long long)O_GID(o));
    void **heap0v = (void**)api.heap0;
    uint64_t target = (uint64_t)(uintptr_t)o;
    size_t found = 0;
    for (int gi = 0; gi < api.ngens && found < 5; gi++) {
      hg_t *g = api.hg0 + gi;
      void **top = g->top;
      void **base = g->base;
      for (void **q = top; q < base && found < 5; q++) {
        if ((uint64_t)(uintptr_t)*q != target) continue;
        size_t k = (size_t)(q - heap0v);
        fprintf(stderr,
                "[GCLIST_BAD]   holder slot heap[%zu] in gen %d\n",
                k, gi);
        found++;
      }
    }
    fflush(stderr);
    abort();
  }
  LIST(p, size);
  GC_REDIR(o,p);
  pp = &LGET(p,0);
  oo = &LGET(o,0);
  for (i = 0; i < size; i++) {
    /* DEBUG (task #12, behind SYMTA_DBG_LISTSCAN): catch list slots
     * containing a tag-3..5/7 poison dyn at GC time.  The src list
     * holding the corruption is identified by gid + size + slot
     * index, so it can be cross-referenced against the construction
     * site. */
    {
      static int list_dbg = -1;
      if (list_dbg == -1) list_dbg = getenv("SYMTA_DBG_LISTSCAN") ? 1 : 0;
      if (list_dbg) {
        uint64_t v = (uint64_t)oo[i];
        if ((v & 1) && (((v>>1)&0x7FFF) >= 3) &&
            (((v>>1)&0x7FFF) <= 7) && (((v>>1)&0x7FFF) != 6)) {
          static int seen_list = 0;
          if (!seen_list) {
            fprintf(stderr,
                    "[LISTSCAN] src list o=%p (gid=%llu size=%u) "
                    "slot[%u] = %016llx (tag=%llu)\n",
                    (void*)o, (unsigned long long)O_GID(o), size, i,
                    (unsigned long long)v,
                    (unsigned long long)((v>>1)&0x7FFF));
            for (uint32_t j = 0; j < size && j < 16; j++) {
              uint64_t vj = (uint64_t)oo[j];
              fprintf(stderr,
                      "[LISTSCAN]   slot[%u]=%016llx (tag=%llu)\n",
                      j, (unsigned long long)vj,
                      (unsigned long long)((vj>>1)&0x7FFF));
            }
            fflush(stderr);
            seen_list = 1;
          }
        }
      }
    }
    GC_REC(pp[i], oo[i]);
  }
GCEND(p)

GCDEF(gc_view)
  uint32_t i;
  dyn p;
  dyn *pp, *oo;
  dyn base = VIEW_BASE(o);
  uint32_t start = VIEW_START(o);
  uint32_t size = VIEW_SIZE(o);

  //if base is older than the view, we can reuse it
  //FIXME: find the best age threshold for it
  //       theoretically it should depend on the HG size
  if (O_AGE(o) < 1 && VIEW_SHARED(base)) {
    VIEW(p, 0, start, size);
    GC_REDIR(o,p); //place redir befor GC'ing base, because loops
    GC_REC(base, base);
    VIEW_BASE(p) = base;
    goto end;
  }

  //otherwise view is old enough to become a list
  LIST(p, size);
  GC_REDIR(o,p);
  oo = &LGET(base, start);
  pp = &LGET(p,0);

  for (i = 0; i < size; i++) {
    GC_REC(pp[i], oo[i]);
  }
end:
GCEND(p)

#if 1
GCDEF(gc_cons)
again:;
  void *p, *r;
  CONS(p, 0, 0);
  r = p;
  dyn car = CAR(o);
  dyn cdr = CDR(o);
  GC_REDIR(o,p);
  GC_REC(CAR(p), car);
  o = cdr;
  //GC them in one loop, so stack space wont be an issue.
  while (!IMMEDIATE(o) && O_TAG(o) == T_CONS) {
    void *oo_ = (void*)(o);
    uint32_t hg_ = O_AGE(oo_);
    if (hg_ != GC_AGE) {
      if (hg_ == GC_MOVED) {
        o = O_RELOC(oo_); /*already moved*/
        break;
      } else {
        o = oo_; /*older gen*/
        break;
      }
    }

    void *prev = p;
    CONS(p, 0, 0);
    CDR(prev) = p;
    //fprintf(stderr, "%s\n", print_object(CAR(o)));
    dyn car = CAR(o);
    dyn cdr = CDR(o);
    GC_REDIR(o,p);
    GC_REC(CAR(p), car);
    o = cdr;
  }
  GC_REC(CDR(p), o);
GCEND(r)
#else
//this simpler code can overflow the stack!!!
GCDEF(gc_cons)
  void *p;
  CONS(p, 0, 0);
  dyn car = CAR(o);
  dyn cdr = CDR(o);
  GC_REDIR(o,p);
  GC_REC(CAR(p), car);
  GC_REC(CDR(p), cdr);
GCEND(p)
#endif

GCDEF(gc_text)
  void *p;
  p = alloc_bigtext(BIGTEXT_DATA(o), BIGTEXT_SIZE(o));
  GC_REDIR(o,p);
GCEND(p)

GCDEF(gc_bytes)
  void *p;
  int size = (int)BYTES_SIZE(o);
  p = alloc_bytes(size);
  memcpy(BYTES_DATA(p), BYTES_DATA(o), size);
  GC_REDIR(o,p);
GCEND(p)

//We don't really need to GC the maps whose values and keys are immediate
//should we track the values?
static void tbl_gc_internals(dyn o) {
  CASE(AM_TYPE(o))
  GOT(AM_EMPTY)
  GOT(AM_GENERIC)
    dh_t *hm = AM_BASE(o);
    NH_FOR(dh,i,hm) {
      GC_REC(*dhKey(hm,i),*dhKey(hm,i));
      GC_REC(*dhVal(hm,i),*dhVal(hm,i));
    }
  GOT(AM_TEXT)
    /* AM-6b: text keys are dyns stored directly in th_t. Mark
     * both keys and values; otherwise a BIGTEXT key whose only
     * reference is the slot would get reclaimed. */
    th_t *hm = AM_BASE(o);
    NH_FOR(th,i,hm) {
      GC_REC(*thKey(hm,i),*thKey(hm,i));
      GC_REC(*thVal(hm,i),*thVal(hm,i));
    }
  GOT(AM_INT)
    ih_t *hm = AM_BASE(o);
    NH_FOR(ih,i,hm) {
      dyn *t = ihVal(hm,i);
      GC_REC(*t,*t);
    }
  GOT(AM_BITMAP0)
  GOT(AM_BITMAP1)
  ESAC
}

GCDEF(gc_tbl)
  dyn r;
  AM_ALLOC(r);
  AM_BASE(r) = AM_BASE(o);
  AM_SET_TYPE(r,AM_TYPE(o));
  AM_SET_YOUNGEST(r,api.hgp->age);
  GC_REDIR(o,r);
  GC_REC(AM_VOID(r), AM_VOID(o));
  tbl_gc_internals(r);
GCEND(r)


GCDEF(gc_custom)
  int i, size;
  void *p;
  void **pp, **oo;
  uint64_t tag = O_TAG(o);
  size = types[tag].size;
  /* Defensive (task #12): tags 3, 4, 5, 7 are reserved placeholder
   * slots in the type table (_unused0_, _unused1_, _unused2_,
   * _data_).  Tag 6 (_tag_, T_TAG) IS a legitimate tag used by
   * Symta's tagged-enum forms (`_tag (_data X)`), but the others
   * should never appear on a live heap dyn.  Reaching here for
   * one of the truly-unused tags means something wrote a raw
   * integer with low bits matching the tag pattern into a heap
   * slot.  Treat it as an immediate so the corruption doesn't
   * propagate -- the stale value is harmless on its own; only
   * gc_custom's automatic OBJECT-with-reserved-tag re-allocation
   * snowballs it into segfaults later (the macroexpand crash
   * pinned in tests/runtime/tiny-gen0.sh).  Set SYMTA_FATAL_BAD_TAG
   * to abort immediately for diagnosis. */
  if ((tag >= 3 && tag <= 5) || tag == 7) {
    static int warned = 0;
    if (!warned) {
      fprintf(stderr,
              "[GC] tag-%llu heap-dyn=%p reached gc_custom (reserved "
              "slot %s); treating as immediate.  Set "
              "SYMTA_FATAL_BAD_TAG=1 to abort.\n",
              (unsigned long long)tag, (void*)o,
              tag < arrlen(types) ? types[tag].name : "?");
      fflush(stderr);
      warned = 1;
      if (getenv("SYMTA_FATAL_BAD_TAG")) abort();
    }
    return o;
  }
  OBJECT(p, tag, size);
  O_CODE(p) = O_CODE(o);
  GC_REDIR(o,p);
  pp = (void**)&LGET(p,0);
  oo = (void**)&LGET(o,0);
  for (i = 0; i < size; i++) {
    GC_REC(pp[i], oo[i]);
  }
GCEND(p)
