;; This file defines the traversal of objects for the GC and similar
;; purposes. The description supports the generation of multiple C
;; functions, each specialized to a particular traversal mode, while
;; sharing the overall traversal implementation.

;; Roughly the first half of this file is the semi-declarative
;; specification in Parenthe-C, and the second half is the Parenthe-C
;; compiler that generates C code. The lines between the
;; specification, compiler, and supporting C code in "gc.c" are
;; (unfortunately) not very strict.

;; Code is generated by calling the functions listed here:
(disable-unbound-warning
 mkgc-ocd.inc
 mkgc-oce.inc
 mkgc-par.inc
 mkvfasl.inc
 mkheapcheck.inc)

;; Currently supported traversal modes:
;;   - copy
;;   - sweep
;;   - sweep-in-old ; like sweep, but don't update impure
;;   - mark
;;   - self-test   : check immediate pointers only for self references
;;   - size        : immediate size, so does not recur
;;   - measure     : recurs for reachable size
;;   - check

;; For the specification, there are a few declaration forms described
;; below, such as `trace` to declare a pointer-valued field within an
;; object (to be copied in copy mode and swept in sweep mode).
;; Otherwise, the "declaration" nature of the specification is based
;; on selecting code fragments statically via `case-mode` and
;; `case-flag`. Macros that expand to those forms (e.g., `trace-tlc`)
;; provide a further declarative veneer.

;; Internals:
(disable-unbound-warning
 trace-base-types
 trace-object-types
 trace-macros)

(define trace-base-types '())
(define trace-object-types '())
(define trace-macros (make-eq-hashtable))

;; This macro just makes sure our main specification has a fixed
;; shape:
(define-syntax define-trace-root
  (syntax-rules (case-type typed-object case-typedfield)
    [(_ (case-type
         [type type-stmt ...]
         ...
         [typed-object
          (case-typefield
           [object-type object-type-stmt ...]
           ...)]))
     (begin
       (set! trace-base-types '((type type-stmt ...) ...))
       (set! trace-object-types '((object-type object-type-stmt ...) ...)))]))

;; A "trace macro" is non-hygienically expanded:
(define-syntax define-trace-macro
  (syntax-rules ()
    [(_ (id arg ...) body ...)
     (eq-hashtable-set! trace-macros 'id '((arg ...) body ...))]))

;; Primitive actions/declarations, must be used as statements in roughly
;; this order (but there are exceptions to the order):
;;  - (space <space>) : target for copy; works as a constraint for other modes
;;  - (size <size> [<scale>]) : size for copy; skips rest in size mode
;;  - (mark <flag>) : in mark mode, skips rest except counting;
;;      possible <flags>:
;;       * one-bit : record as one bit per segment; inferred when size matches
;;                   alignment or for `space-data`
;;       * within-segment : allocated within on segment; can be inferred from size
;;       * no-sweep : no need to sweep content (perhaps covered by `trace-now`);
;;                    inferred for `space-data`
;;       * counting-root : check a counting root before pushing to sweep stack
;;  - (trace <field>) : relocate for sweep, copy for copy, recur otherwise
;;  - (trace-pure <field>) : like `trace`, but no need for generation tracking
;;  - (trace-early <field>) : relocate for sweep, copy, and mark; recur otherwise; implies pure
;;  - (trace-now <field>) : direct recur; implies pure
;;  - (trace-early-rtd <field>) : for record types, avoids recur on #!base-rtd; implies pure
;;  - (trace-pure-code <field>) : like `trace-pure`, but special handling in parallel mode
;;  - (trace-reference <field>) : like `trace`, but for a reference bytevector element
;;  - (trace-ptrs <field> <count>) : trace an array of pointers
;;  - (trace-pure-ptrs <field> <count>) : pure analog of `trace-ptrs`
;;  - (trace-reference-ptrs <field> <count>) : like `trace-ptrs`, but for a reference bytevector
;;  - (copy <field>) : copy for copy, ignore otherwise
;;  - (copy-bytes <field> <count>) : copy an array of bytes
;;  - (copy-flonum <field>) : copy flonum and forward
;;  - (copy-flonum* <field>) : copy potentially forwarded flonum
;;  - (copy-type <field>) : copy type from `_` to `_copy_`
;;  - (count <counter> [<size> [<scale> [<modes>]]]) :
;;       uses preceding `size` declaration unless <size>;
;;       normally counts in copy mode, but <modes> can override
;;  - (as-mark-end <statement> ...) : declares that <statement>s implement counting,
;;       which means that it's included for mark mode
;;  - (skip-forwarding) : disable forward-pointer installation in copy mode
;;  - (assert <expr>) : assertion
;;
;; In the above declarations, nonterminals like <space> can be
;; an identifier or a Parenthe-C expression. The meaning of a plain
;; identifier depends on the nonterminal:
;;  - <space>  : should be a `space-...` from cmacros
;;  - <size>   : should be a constant from cmacros
;;  - <field>  : accessor from cmacros, implicitly applied to `_` and `_copy_`

;; Parenthe-C is just what it sounds like: C code written in S-expression
;; form. Use `(<op> <arg> ...)` as usual, and the generated code transforms
;; to infix as appropriate for reorganized operators. The statement versus
;; expression distinction is important; primitive declarations must be in
;; statement positions.
;;
;; Statements:
;;  - <expr>
;;  - <declaration> : like `(space <space>)`, etc., above
;;  - (set! <id> <expr>) : renders as `<id> = <expr>;`
;;  - (set! <id> <assign-op> <expr>) : renders as `<id> <assign-op> <expr>;`
;;  - (cond [<expr> <stmt> ...] ... [else <stmt> ...])
;;  - (when <expr> <stmt> ...) : shorthand for `(cond [<expr> <stmt> ...] [else])`
;;  - (while :? <expr> <stmt> ...)
;;  - (do-while <stmt> ... :? <expr>)
;;  - (break)
;;  - (define <id> : <type> <expr>) : discarded if <id> is unused
;;  - (let* ([<id> : <type> <expr>] ...) <stmt> ...)
;;  - (case-mode [<modes> <stmt> ...] ... [else <stmt>]) : static
;;      case dispatch based on mode, where <modes> can be one <mode> or
;;      a parenthesized sequence of <mode>s
;;  - (case-flag <flag> [on <stmt> ...] [off <stmt> ...]) : static dispatch
;;      based on a configuration flag
;;  - (case-space [<space> <stmt> ...] .... [else <stmt> ...]) : run-time
;;      dispatch based on the space of _
;;
;; Expressions:
;;  - <id> : a constant from cmacros or a C name
;;  - <literal> : a literal number or string
;;  - (<field-or-expr> <arg>) : function call, operation use, or field access
;;  - (<field-or-expr> <arg> <arg2>) : function call, operation use, or array
;;      field access
;;  - (<id-or-expr> <arg> <arg> ...) : function call or operation use
;;  - (just <expr>) : same as <expr>, sometimes useful when <expr> is a symbol
;;  - (cond [<expr> <expr>] ... [else <expr>])
;;  - (case-flag <flag> [on <expr>] [off <expr>]) : static dispatch
;;  - (cast <type> <expr>)
;;  - (array-ref <expr> <expr>)
;;
;; Built-in variables:
;;  - _                 : object being copied, swept, etc.
;;  - _copy_            : target in copy mode, same as _ otherwise
;;  - _size_            : size of the current object, but only in parallel mode
;;  - _tf_              : type word
;;  - _tg_              : target generation
;;  - _backreferences?_ : dynamic flag indicating whether backreferences are on
;;  - _bytevector-pad?_ : whether a bytevector has a pad word in its header
;;
;; Stylistically, prefer constants and fields using the hyphenated
;; names from cmacros instead of the corresponding C name. Use C names
;; for derived functions, like `size_record_inst` or `FIX`.

(define-trace-root
  (case-type
   
   [pair
    (case-space
     [(< space-weakpair)
      (space space-impure)
      (try-double-pair trace pair-car
                       trace pair-cdr
                       countof-pair)]
     [space-ephemeron
      (space space-ephemeron)
      (size size-ephemeron)
      (copy pair-car)
      (copy pair-cdr)
      (case-mode
       [(copy)
        (set! (ephemeron-prev-ref _copy_) 0)
        (set! (ephemeron-next _copy_) 0)]
       [(check)
        (trace pair-car)
        (trace pair-cdr)]
       [else])
      (add-ephemeron-to-pending)
      (mark one-bit no-sweep)
      (assert-ephemeron-size-ok)
      (count countof-ephemeron)]
     [space-weakpair
      (space space-weakpair)
      (case-mode
       [(check) (trace pair-car)]
       [else])
      (try-double-pair copy pair-car
                       trace pair-cdr
                       countof-weakpair)]
     [else ; => space-reference-array as used for dirty resweep by owner thread
      (case-mode
       [(sweep)
        (space space-reference-array)
        (size size-pair)
        (mark)
        (trace-reference pair-car)
        (trace-reference pair-cdr)
        (count countof-pair)]
       [else
        (S_error_abort "misplaced pair")])])]
   [closure
    (define code : ptr (CLOSCODE _))
    (trace-code-early code) ; not traced in parallel mode
    ;; In parallel mode, don't use any fields of `code` until the
    ;; second on after the type, because the type and first field may
    ;; be overwritten with forwarding information
    (cond
      [(and-not-as-dirty
        (or-assume-continuation
         (& (code-type code) (<< code-flag-continuation code-flags-offset))))
       ;; continuation
       (space (cond
                [(and-counts (is_counting_root si _)) space-count-pure]
                [else space-continuation]))
       (size size-continuation)
       (case-mode
        [self-test]
        [mark
         (copy-stack-length continuation-stack-length continuation-stack-clength)
         (mark one-bit counting-root)]
        [else
         (copy-clos-code code)
         (copy-stack-length continuation-stack-length continuation-stack-clength)
         (copy continuation-stack-clength)
         (trace-pure-nonself continuation-winders)
         (trace-nonself continuation-attachments)
         (cond
           [(== (continuation-stack-length _) scaled-shot-1-shot-flag)]
           [else
            (case-mode
             [(sweep)
              (define stk : ptr (continuation-stack _))
              (define s_si : seginfo* NULL)
              (when (&& (!= stk (cast ptr 0))
                        (begin
                          (set! s_si (SegInfo (ptr_get_segment stk)))
                          (-> s_si old_space)))
                (cond
                  [(! (SEGMENT_IS_LOCAL s_si stk))
                   ;; A stack segment has a single owner, so it's ok for us
                   ;; to sweep the stack content, even though it's on a
                   ;; remote segment relative to the current sweeper.
                   (RECORD_REMOTE s_si)]
                  [else
                   (set! (continuation-stack _)
                         (copy_stack _tgc_
                                     (continuation-stack _)
                                     (& (continuation-stack-length _))
                                     (continuation-stack-clength _)))]))]
             [else])
            (count countof-stack (continuation-stack-length _) 1 [measure])
            (trace-pure continuation-link)
            (trace-return continuation-return-address (continuation-return-address _))
            (case-mode
             [copy (copy continuation-stack)]
             [else
              (define stack : uptr (cast uptr (continuation-stack _)))
              (trace-stack stack
                           (+ stack (continuation-stack-clength _))
                           (cast uptr (continuation-return-address _)))])])])
       (count countof-continuation)]

      [else
       ;; closure (not a continuation)
       (space
        (cond
          [(and-counts (is_counting_root si _)) space-count-impure]
          [_backreferences?_
           space-closure]
          [else
           (cond
             [(& (code-type code) (<< code-flag-mutable-closure code-flags-offset))
              ;; in parallel mode, assume that code pointer is static and doesn't need to be swept
              space-impure]
             [else
              (case-flag parallel?
                [on
                 ;; use space-closure so code reference (not a regular ptr) is swept correctly
                 space-closure]
                [off
                 space-pure])])]))
       (define len : uptr (code-closure-length code))
       (size (size_closure len))
       (when-mark
        (case-space
         [space-pure
          (mark one-bit counting-root)
          (count countof-closure)]
         [else
          (mark counting-root)
          (count countof-closure)]))
       (cond
         [(and-purity-sensitive-mode
           (& (code-type code) (<< code-flag-mutable-closure code-flags-offset)))
          (copy-clos-code code)
          (trace-ptrs closure-data len)]
         [(and-not-as-dirty 1)
          (copy-clos-code code)
          (trace-pure-ptrs closure-data len)]
         [else])
       (pad (when (== (& len 1) 0)
              (set! (closure-data _copy_ len) (FIX 0))))
       (count countof-closure)])]
   
   [symbol
    (space space-symbol)
    (size size-symbol)
    (mark one-bit)
    (trace/define symbol-value val)
    (trace-local-symcode symbol-pvalue val)
    (trace-nonself symbol-plist)
    (trace-nonself symbol-name)
    (trace-nonself symbol-splist)
    (trace-nonself symbol-hash)
    (count countof-symbol)]
   
   [flonum
    (space space-data)
    (size size-flonum)
    (mark)
    (copy-flonum flonum-data)
    (count countof-flonum)
    (skip-forwarding)]
   
   [typed-object
    (case-typefield

     [record
      (trace-early-rtd record-type)
      ;; If the record contains only pointers, put it into space-pure
      ;; or space-impure. Otherwise, put it into
      ;; space-pure-typed-object or space-impure-record. We could put
      ;; all records into space-{pure,impure}-record or even into
      ;; space-impure-record, but by picking the target space more
      ;; carefully, we may reduce fragmentation and sweeping cost.
      (define rtd : ptr (record-type _))
      (space
       (cond
         [(and-counts (is_counting_root si _))
          space-count-impure]
         [(== (record-type-pm rtd) (FIX -1))
          ;; All pointers
          (cond
           [_backreferences?_
            (cond
              [(== (record-type-mpm rtd) (FIX 0))
               ;; All immutable
               space-pure-typed-object]
              [else
               space-impure-record])]
           [else
            (cond
              [(== (record-type-mpm rtd) (FIX 0))
               ;; All immutable
               space-pure]
              [else
               space-impure])])]
         [else
          ;; Mixture of pointers and non-pointers
          (cond
            [(== (record-type-mpm rtd) (FIX 0))
             ;; All immutable
             space-pure-typed-object]
            [else
             space-impure-record])]))
      (define len : uptr (UNFIX (record-type-size rtd)))
      (size (size_record_inst len))
      (mark counting-root)
      (trace-record rtd len)
      (pad (when (\|\| (== p_spc space-pure) (\|\| (== p_spc space-impure)
                                              (and-counts (== p_spc space-count-impure))))
             (let* ([ua_size : uptr (unaligned_size_record_inst len)])
               (when (!= p_sz ua_size)
                 (set! (* (cast ptr* (TO_VOIDP (+ (cast uptr (UNTYPE _copy_ type_typed_object)) ua_size))))
                       (FIX 0))))))
      (count-record rtd)]

     [vector
      ;; Assumes vector lengths look like fixnums;
      ;; if not, vectors will need their own space
      (space
       (cond
         [(& (cast uptr _tf_) vector_immutable_flag)
          (cond
           [_backreferences?_ space-pure-typed-object]
           [else space-pure])]
         [else
          (cond
           [_backreferences?_ space-impure-typed-object]
           [else space-impure])]))
      (define len : uptr (Svector_length _))
      (size (size_vector len))
      (mark)
      (copy-type vector-type)
      (trace-ptrs vector-data len)
      (pad (when (== (& len 1) 0)
             (set! (vector-data _copy_ len) (FIX 0))))
      (count countof-vector)]

     [any-stencil-vector ; stencil-vector or $stencil-vector
      ;; Assumes stencil-vector tags look like immediates or fixnums;
      ;; if not, stencil vectors will need their own space
      (space
       (cond
        [_backreferences?_ space-impure-typed-object]
        [else space-impure]))
      (define len : uptr (Sstencil_vector_length _))
      (size (size_stencil_vector len))
      (mark within-segment) ; see assertion
      (assert-stencil-vector-size)
      (copy-type stencil-vector-type)
      (trace-ptrs stencil-vector-data len)
      (pad (when (== (& len 1) 0)
             (set! (stencil-vector-data _copy_ len) (FIX 0))))
      (count countof-stencil-vector)]

     [string
      (space space-data)
      (define sz : uptr (size_string (Sstring_length _)))
      (size (just sz))
      (mark)
      (copy-bytes string-type sz)
      (count countof-string)]

     [fxvector
      (space space-data)
      (define sz : uptr (size_fxvector (Sfxvector_length _)))
      (size (just sz))
      (mark)
      (copy-bytes fxvector-type sz)
      (count countof-fxvector)]

     [flvector
      (space space-data)
      (define sz : uptr (size_flvector (Sflvector_length _)))
      (size (just sz))
      (mark)
      (copy-bytes flvector-type sz)
      (count countof-flvector)]

     [bytevector
      (case-space
       [space-reference-array
        (space space-reference-array)
        (define sz : uptr (size_bytevector (Sbytevector_length _)))
        (size (just sz))
        (mark)
        (copy-type bytevector-type)
        (define len : uptr (Sbytevector_reference_length _))
        (trace-reference-ptrs bytevector-data len)
        (pad (when _bytevector-pad?_
               (set! (* (cast ptr* (TO_VOIDP (+ (cast uptr _copy_) bytevector_pad_disp)))) (FIX 0))))
        (pad (let* ([extra : uptr (- (Sbytevector_length _) (* len ptr_bytes))])
               ;; copy another word if needed for extra bytes, and
               ;; then one more word if needed for allocation alignment
               (cond
                 [(== extra 0)
                  (when (== (& len 1) (if _bytevector-pad?_ 1 0))
                    (set! (INITBVREFIT _copy_ len) (FIX 0)))]
                 [else
                  (set! (INITBVREFIT _copy_ len) (INITBVREFIT _ len))
                  (let* ([xlen : uptr (+ len 1)])
                    (when (== (& xlen 1) (if _bytevector-pad?_ 1 0))
                      (set! (INITBVREFIT _copy_ xlen) (FIX 0))))])))
        (count countof-bytevector)]
       [else
        (space space-data)
        (define sz : uptr (size_bytevector (Sbytevector_length _)))
        (size (just sz))
        (mark)
        (copy-bytes bytevector-type sz)
        (count countof-bytevector)])]

     [tlc
      (space
       (cond
        [_backreferences?_ space-impure-typed-object]
        [else space-impure]))
      (size size-tlc)
      (mark)
      (copy-type tlc-type)
      (trace-nonself tlc-ht)
      (as-mark-end
       (trace-tlc tlc-next tlc-keyval)
       (count countof-tlc))]

     [box
      (space
       (cond
         [(== (box-type _) type-immutable-box)
          (cond
           [_backreferences?_ space-pure-typed-object]
           [else space-pure])]
         [else
          (cond
            [_backreferences?_ space-impure-typed-object]
            [else space-impure])]))
      (size size-box)
      (mark)
      (copy-type box-type)
      (trace box-ref)
      (count countof-box)]

     [ratnum
      (space (case-flag parallel?
               [on space-pure]
               [off space-data]))
      (size size-ratnum)
      (copy-type ratnum-type)
      (trace-nonparallel-now ratnum-numerator)
      (trace-nonparallel-now ratnum-denominator)
      (case-flag parallel?
        [on (pad (set! (ratnum-pad _copy_) 0))]
        [off])
      (mark)
      (count countof-ratnum)]

     [exactnum
      (space (case-flag parallel?
               [on space-pure]
               [off space-data]))
      (size size-exactnum)
      (copy-type exactnum-type)
      (trace-nonparallel-now exactnum-real)
      (trace-nonparallel-now exactnum-imag)
      (case-flag parallel?
        [on (pad (set! (exactnum-pad _copy_) 0))]
        [off])
      (mark)
      (count countof-exactnum)]

     [inexactnum
      (space space-data)
      (size size-inexactnum)
      (mark)
      (copy-type inexactnum-type)
      (copy-flonum* inexactnum-real)
      (copy-flonum* inexactnum-imag)
      (count countof-inexactnum)]

     [bignum
      (space space-data)
      (define sz : uptr (size_bignum (BIGLEN _)))
      (size (just sz))
      (mark)
      (copy-bytes bignum-type sz)
      (count countof-bignum)]

     [port
      (space space-port)
      (size size-port)
      (mark one-bit)
      (copy-type port-type)
      (trace-nonself port-handler)
      (copy port-ocount)
      (copy port-icount)
      (trace-buffer PORT_FLAG_OUTPUT port-obuffer port-olast)
      (trace-buffer PORT_FLAG_INPUT port-ibuffer port-ilast)
      (trace port-info)
      (trace-nonself port-name)
      (count countof-port)]

     [code
      (space space-code)
      (define len : uptr (code-length _)) ; in bytes
      (size (size_code len))
      (mark one-bit)
      (when (and-not-as-dirty 1)
       (copy-type code-type)
       (copy code-length)
       (copy code-reloc)
       (trace-pure-nonself code-name)
       (trace-pure-nonself code-arity-mask)
       (copy code-closure-length)
       (trace-pure-nonself code-info)
       (trace-pure-nonself code-pinfo*)
       (trace-code len))
      (count countof-code)]

     [thread
      (space (cond
               [(and-counts (is_counting_root si _)) space-count-pure]
               [else space-pure-typed-object]))
      (size size-thread)
      (mark one-bit)
      (case-mode
       [self-test]
       [else
        (copy-type thread-type)
        (when (and-not-as-dirty 1)
          (trace-tc thread-tc))])
      (count countof-thread)]

     [rtd-counts
      (space space-data)
      (size size-rtd-counts)
      (mark)
      (copy-bytes rtd-counts-type size_rtd_counts)
      (count countof-rtd-counts)]

     [phantom
      (space space-data)
      (size size-phantom)
      (mark)
      (copy-type phantom-type)
      (copy phantom-length)
      (case-mode
       [(copy mark)
        (as-mark-end
         (count countof-phantom)
         ;; Separate from `count`, because we want to track sizes even
         ;; if counting is not enabled:
         (GC_MUTEX_ACQUIRE)
         (set! (array-ref (array-ref S_G.bytesof _tg_) countof-phantom)
               +=
               (phantom-length _))
         (GC_MUTEX_RELEASE))]
       [measure (set! measure_total += (phantom-length _))]
       [else])])]))

(define-trace-macro (trace-nonself field)
  (case-mode
   [self-test]
   [else
    (trace field)]))

(define-trace-macro (trace-pure-nonself field)
  (case-mode
   [self-test]
   [else
    (trace-pure field)]))

(define-trace-macro (trace-nonparallel-now field)
  (case-flag parallel?
    [on (trace-pure field)]
    [off (trace-now field)]))

(define-trace-macro (try-double-pair do-car pair-car
                                     do-cdr pair-cdr
                                     count-pair)
  (case-mode
   [copy
    ;; Try to copy two pairs at a time
    (define cdr_p : ptr (Scdr _))
    (define qsi : seginfo* NULL)
    (cond
      [(&& (!= cdr_p _)
           (&& (== (TYPEBITS cdr_p) type_pair)
               (&& (== (ptr_get_segment cdr_p) (ptr_get_segment _))
                   (&& (!= (FWDMARKER cdr_p) forward_marker)
                       ;; Checking `marked_mask`, in
                       ;; case the cdr pair is locked
                       (! (-> si marked_mask))))))
       (size size-pair 2)
       (define new_cdr_p : ptr (cast ptr (+ (cast uptr _copy_) size_pair)))
       (set! (pair-car _copy_) (pair-car _))
       (set! (pair-cdr _copy_) new_cdr_p)
       (set! (pair-car new_cdr_p) (pair-car cdr_p))
       (set! (pair-cdr new_cdr_p) (pair-cdr cdr_p))
       (set! (FWDMARKER cdr_p) forward_marker)
       (set! (FWDADDRESS cdr_p) new_cdr_p)
       (case-flag maybe-backreferences?
        [on (ADD_BACKREFERENCE_FROM new_cdr_p new_p _tg_)]
        [off])
       (count count-pair size-pair 2)]
      [else
       (size size-pair) 
       (do-car pair-car)
       (do-cdr pair-cdr)
       (count count-pair)])]
   [else
    (size size-pair)
    (mark)
    (assert (= (constant size-pair) (constant byte-alignment)))
    (do-car pair-car)
    (do-cdr pair-cdr)
    (count count-pair)]))

(define-trace-macro (add-ephemeron-to-pending)
  (case-mode
   [(sweep mark)
    (add_ephemeron_to_pending _tgc_ _)]
   [measure
    (add_ephemeron_to_pending_measure _tgc_ _)]
   [else]))

(define-trace-macro (assert-ephemeron-size-ok)
  ;; needed for dirty sweep strategy:
  (assert (zero? (modulo (constant bytes-per-card) (constant size-ephemeron)))))

(define-trace-macro (assert-stencil-vector-size)
  ;; needed for within-mark-byte
  (assert (< (+ (* (constant stencil-vector-mask-bits) (constant ptr-bytes))
                (constant header-size-stencil-vector)
                (constant byte-alignment))
             (constant bytes-per-segment))))

(define-trace-macro (trace-code-early code)
  (unless-code-relocated
   ;; In parallel mode, the `code` pointer may or may not have been
   ;; forwarded. In that case, we may misinterpret the forward marker
   ;; as a code type with flags, but it's ok, because the flags will
   ;; only be set for static-generation objects
   (case-flag parallel?
     [on (case-mode
          [(sweep sweep-in-old)
           (trace-pure-code (just code))]
          [else])]
     [off (trace-early (just code))])))

(define-trace-macro (copy-clos-code code)
  (case-mode
   [(copy)
    (SETCLOSCODE _copy_ code)]
   [(sweep sweep-in-old)
    (unless-code-relocated
     (SETCLOSCODE _copy_ code))]
   [else]))

(define-trace-macro (copy-stack-length continuation-stack-length continuation-stack-clength)
  (case-mode
   [(copy mark)
    ;; Don't promote general one-shots, but promote opportunistic one-shots
    (cond
      [(== (continuation-stack-length _) opportunistic-1-shot-flag)
       (set! (continuation-stack-length _copy_) (continuation-stack-clength _))
       ;; May need to recur at end to promote link:
       (GC_MUTEX_ACQUIRE)
       (set! conts_to_promote (S_cons_in (-> _tgc_ tc) space_new 0 _copy_ conts_to_promote))
       (GC_MUTEX_RELEASE)]
      [else
       (copy continuation-stack-length)])]
   [else
    (copy continuation-stack-length)]))

(define-trace-macro (trace/define ref val)
  (case-mode
   [(copy measure)
    (trace ref)]
   [(sweep sweep-in-old)
    (trace ref) ; can't trace `val` directly, because we need an impure relocate
    (define val : ptr (ref _))]
   [else]))

(define-trace-macro (trace-symcode symbol-pvalue val)
  (case-mode
   [(sweep sweep-in-old)
    (define code : ptr (cond
                         [(Sprocedurep val) (CLOSCODE val)]
                         [else (SYMCODE _)]))
    (case-flag as-dirty?
       [on (trace (just code))]
       [off (trace-pure (just code))])
    (INITSYMCODE _ code)]
   [measure]
   [else
    (copy symbol-pvalue)]))

(define-trace-macro (trace-local-symcode symbol-pvalue val)
  (case-mode
   [(sweep)
    (case-flag parallel?
      [on
       (define v_si : seginfo* (cond
                                 [(Sprocedurep val) (SegInfo (ptr_get_segment val))]
                                 [else NULL]))
       (cond
         [(\|\|
           (\|\|
            (== v_si NULL)
            (! (-> v_si old_space)))
           (SEGMENT_IS_LOCAL v_si val))
          (trace-symcode symbol-pvalue val)]
         [else
          (RECORD_REMOTE v_si)])]
      [off (trace-symcode symbol-pvalue val)])]
   [else
    (trace-symcode symbol-pvalue val)]))

(define-trace-macro (trace-tlc tlc-next tlc-keyval)
  (case-mode
   [(copy mark)
    (define next : ptr (tlc-next _))
    (define keyval : ptr (tlc-keyval _))
    (case-mode
     [copy
      (set! (tlc-next _copy_) next)
      (set! (tlc-keyval _copy_) keyval)]
     [else])
    ;; If next isn't false and keyval is old, add tlc to a list of tlcs
    ;; to process later. Determining if keyval is old is a (conservative)
    ;; approximation to determining if key is old. We can't easily
    ;; determine if key is old, since keyval might or might not have been
    ;; swept already. NB: assuming keyvals are always pairs.
    (when (&& (!= next Sfalse) (OLDSPACE keyval))
      (GC_MUTEX_ACQUIRE)
      (set! tlcs_to_rehash (S_cons_in (-> _tgc_ tc) space_new 0 _copy_ tlcs_to_rehash))
      (GC_MUTEX_RELEASE))]
   [else
    (trace-nonself tlc-keyval)
    (trace-nonself tlc-next)]))

(define-trace-macro (trace-record trd len)
  (case-mode
   [(copy)
    (copy-bytes record-data (- len ptr_bytes))]
   [else
    ;; record-type descriptor was forwarded already
    (let* ([num : ptr (case-flag as-dirty?
                       [on (record-type-mpm rtd)]
                       [off (record-type-pm rtd)])]
           [pp : ptr* (& (record-data _ 0))])
      ;; Process cells for which bit in pm is set, and quit when pm == 0
      (cond
        [(Sfixnump num)
         ;; Ignore bit for already forwarded rtd
         (let* ([mask : uptr (>> (cast uptr (UNFIX num)) 1)])
           (cond
             [(case-flag as-dirty?
               [on 0]
               [off (== mask (>> (cast uptr -1) 1))])
              (let* ([ppend : ptr* (- (cast ptr* (TO_VOIDP (+ (cast uptr (TO_PTR pp)) len))) 1)])
                (while
                 :? (< pp ppend)
                 (trace (* pp))
                 (set! pp += 1)))]
             [else
              (while
               :? (!= mask 0)
               (when (& mask 1)
                 (trace (* pp)))
               (set! mask >>= 1)
               (set! pp += 1))]))]
        [(== num #t)
         ;; special protocol for an object created by `make-ftype-scheme-object-pointer`
         (define offset : uptr (cast uptr (* (+ pp  1))))
         (define obj : ptr (TO_PTR (- (cast uptr (* pp)) offset)))
         (trace (just obj))
         (set! (* pp) (TO_PTR (+ (cast uptr obj) offset)))]
        [else
         (case-flag as-dirty?
          [on]
          [off
           (case-mode
            [(sweep)
             (case-flag parallel?
               [on
                (define pm_si : seginfo* (SegInfo (ptr_get_segment num)))
                (cond
                  [(\|\|
                    (! (-> pm_si old_space))
                    (SEGMENT_IS_LOCAL pm_si num))
                   (trace-record-type-pm num rtd)]
                  [else
                   ;; Try again in the bignum's sweeper
                   (RECORD_REMOTE pm_si)
                   (set! num S_G.zero_length_bignum)])]
               [off
                (trace-record-type-pm num rtd)])]
            [(sweep-in-old self-test)
             (trace-record-type-pm num rtd)]
            [(check) (check-bignum num)]
            [else])])
         (let* ([index : iptr (- (BIGLEN num) 1)]
                ;; Ignore bit for already forwarded rtd
                [mask : bigit (>> (bignum-data num index) 1)]
                [bits : INT (- bigit_bits 1)])
           (while
            :? 1
            (do-while
             (when (& mask 1)
               (trace (* pp)))
             (set! mask >>= 1)
             (set! pp += 1)
             (set! bits -= 1)
             ;; while:
             :? (> bits 0))
            (when (== index 0) (break))
            (set! index -= 1)
            (set! mask (bignum-data num index))
            (set! bits bigit_bits)))]))]))

(define-trace-macro (trace-record-type-pm num rtd)
  ;; Bignum pointer mask may need forwarding
  (trace-pure (record-type-pm rtd))
  (set! num (record-type-pm rtd)))

(define-trace-macro (count-record rtd)
  (case-mode
   [(copy mark)
    (as-mark-end
     (case-flag counts?
      [on
       (when S_G.enable_object_counts
         (let* ([c_rtd : ptr (cond
                               [(== _tf_ _) _copy_]
                               [else rtd])]
                [counts : ptr (record-type-counts c_rtd)])
           (cond
             [(== counts Sfalse)
              (let* ([grtd : IGEN (GENERATION c_rtd)])
                (set! (array-ref (array-ref S_G.countof grtd) countof_rtd_counts) += 1)
                ;; Allocate counts struct in same generation as rtd. Initialize timestamp & counts.
                (find_gc_room _tgc_ space_data grtd type_typed_object size_rtd_counts counts)
                (set! (rtd-counts-type counts) type_rtd_counts)
                (set! (rtd-counts-timestamp counts) (array-ref S_G.gctimestamp 0))
                (let* ([g : IGEN 0])
                  (while
                   :? (<= g static_generation)
                   (set! (rtd-counts-data counts g) 0)
                   (set! g += 1)))
                (set! (record-type-counts c_rtd) counts)
                (set! (array-ref S_G.rtds_with_counts grtd)
                      ;; For max_copied_generation, the list will get copied again in `rtds_with_counts` fixup;
                      ;; meanwhile, allocating in `space_impure` would copy and sweep old list entries causing
                      ;; otherwise inaccessible rtds to be retained
                      (S_cons_in (-> _tgc_ tc)
                                 (cond [(<= grtd MAX_CG) space_new] [else space_impure])
                                 (cond [(<= grtd MAX_CG) 0] [else grtd])
                                 c_rtd
                                 (array-ref S_G.rtds_with_counts grtd)))
                (set! (array-ref (array-ref S_G.countof grtd) countof_pair) += 1))]
             [else
              (trace-early (just counts))
              (set! (record-type-counts c_rtd) counts)
              (when (!= (rtd-counts-timestamp counts) (array-ref S_G.gctimestamp 0))
                (S_fixup_counts counts))])
           (set! (rtd-counts-data counts _tg_) (+ (rtd-counts-data counts _tg_) 1))))
       ;; Copies size that we may have already gathered, but needed for counting from roots:
       (case-mode
        [(copy)
         (when (== p_spc space-count-impure) (set! count_root_bytes += p_sz))]
        [else])
       (count countof-record)]
      [off]))]
   [else]))

(define-trace-macro (trace-buffer flag port-buffer port-last)
  (case-mode
   [(copy measure)
    (copy port-last)
    (copy port-buffer)]
   [sweep
    (when (& (cast uptr _tf_) flag)
      (define n : iptr (- (cast iptr (port-last _))
                          (cast iptr (port-buffer _))))
      (trace port-buffer)
      (set! (port-last _) (cast ptr (+ (cast iptr (port-buffer _)) n))))]
   [(sweep-in-old check)
    (when (& (cast uptr _tf_) flag)
      (trace port-buffer))]
   [else
    (trace-nonself port-buffer)]))

(define-trace-macro (trace-tc offset)
  (case-mode
   [copy
    (copy offset)]
   [else
    (define tc : ptr (cast ptr (offset _)))
    (when (!= tc (cast ptr 0))
      (case-mode
       [(sweep)
        (let* ([old_stack : ptr (tc-scheme-stack tc)])
          (when (OLDSPACE old_stack)
            (let* ([clength : iptr (- (cast uptr (SFP tc)) (cast uptr old_stack))])
              ;; Include SFP[0], which contains the return address
              (set! (tc-scheme-stack tc) (copy_stack _tgc_
                                                     old_stack
                                                     (& (tc-scheme-stack-size tc))
                                                     (+ clength (sizeof ptr))))
              (set! (tc-sfp tc) (cast ptr (+ (cast uptr (tc-scheme-stack tc)) clength)))
              (set! (tc-esp tc) (cast ptr (- (+ (cast uptr (tc-scheme-stack tc))
                                                (tc-scheme-stack-size tc))
                                             stack_slop))))))]
       [measure
        (measure_add_stack_size (tc-scheme-stack tc) (tc-scheme-stack-size tc))]
       [else])
      (set! (tc-stack-cache tc) Snil)
      (trace-pure (tc-cchain tc))
      (trace-pure (tc-stack-link tc))
      (trace-pure (tc-winders tc))
      (trace-pure (tc-attachments tc))
      (trace-pure (tc-handler-stack tc))
      (case-mode
       [sweep
        (set! (tc-cached-frame tc) Sfalse)]
       [else])
      (trace-return NO-COPY-MODE (FRAME tc 0))
      (trace-stack (cast uptr (tc-scheme-stack tc))
                   (cast uptr (SFP tc))
                   (cast uptr (FRAME tc 0)))
      (case-mode
       [(sweep)
        (PAR_SWEEPERS_WORKAROUND)
        (set! (tc-U tc) 0)
        (set! (tc-V tc) 0)
        (set! (tc-W tc) 0)
        (set! (tc-X tc) 0)
        (set! (tc-Y tc) 0)]
       [else])
      (trace-pure (tc-threadno tc))
      (trace-pure (tc-current-input tc))
      (trace-pure (tc-current-output tc))
      (trace-pure (tc-current-error tc))
      (trace-pure (tc-sfd tc))
      (trace-pure (tc-current-mso tc))
      (trace-pure (tc-target-machine tc))
      (trace-pure (tc-fxlength-bv tc))
      (trace-pure (tc-fxfirst-bit-set-bv tc))
      (trace-pure (tc-compile-profile tc))
      (trace-pure (tc-subset-mode tc))
      (trace-pure (tc-default-record-equal-procedure tc))
      (trace-pure (tc-default-record-hash-procedure tc))
      (trace-pure (tc-compress-format tc))
      (trace-pure (tc-compress-level tc))
      (trace-pure (tc-parameters tc))
      (case-mode
       [(sweep)
        (set! (tc-DSTBV tc) Sfalse)
        (set! (tc-SRCBV tc) Sfalse)]
       [else])
      (let* ([i : INT 0])
        (while
         :? (< i virtual_register_count)
         (trace-pure (tc-virtual-registers tc i))
         (set! i += 1))))]))

(define-trace-macro (trace-stack base-expr fp-expr ret-expr)
  (define base : uptr base-expr)
  (define fp : uptr fp-expr)
  (define ret : uptr ret-expr)

  (while
   :? (!= fp base)
   (when (< fp base)
     (S_error_abort "sweep_stack(gc): malformed stack"))
   (set! fp (- fp (ENTRYFRAMESIZE ret)))
   (let* ([oldret : iptr ret]
          [num : ptr (ENTRYLIVEMASK oldret)] ; keep close to `ENTRYFRAMESIZE`
          [pp : ptr* (cast ptr* (TO_VOIDP fp))])
     (set! ret (cast iptr (* pp)))
     (trace-return NO-COPY-MODE (* pp))
     (cond
       [(Sfixnump num)
        (let* ([mask : uptr (UNFIX num)])
          (while
           :? (!= mask 0)
           (set! pp += 1)
           (when (& mask #x0001)
             (trace-pure (* pp)))
           (set! mask >>= 1)))]
       [else
        (case-mode
         [(check) (check-bignum num)]
         [else
          (define n_si : seginfo* (SegInfo (ptr_get_segment num)))
          (cond
            [(! (-> n_si old_space))]
            [(SEGMENT_IS_LOCAL n_si num)
             (trace-pure (* (ENTRYNONCOMPACTLIVEMASKADDR oldret)))
             (set! num  (ENTRYLIVEMASK oldret))]
            [else
             (case-mode
              [(measure)]
              [else (RECORD_REMOTE n_si)])
             (set! num S_G.zero_length_bignum)])])
        (let* ([index : iptr (BIGLEN num)])
          (while
           :? (!= index 0)
           (set! index -= 1)
           (let* ([bits : INT bigit_bits]
                  [mask : bigit (bignum-data num index)])
             (while
              :? (> bits 0)
              (set! bits -= 1)
              (set! pp += 1)
              (when (& mask 1) (trace-pure (* pp)))
              (set! mask >>= 1)))))]))))

(define-trace-macro (trace-return copy-field field)
  (case-mode
   [copy
    (copy copy-field)]
   [else
    (define xcp : ptr field)
    (trace-return-code field xcp)]))

(define-trace-macro (trace-return-code field xcp)
  (define co : iptr (+ (ENTRYOFFSET xcp) (- (cast uptr xcp) (cast uptr (TO_PTR (ENTRYOFFSETADDR xcp))))))
  (define c_p : ptr (cast ptr (- (cast uptr xcp) co)))
  (case-mode
   [(sweep sweep-in-old)
    (define x_si : seginfo* (SegInfo (ptr_get_segment c_p)))
    (when (-> x_si old_space)
      (relocate_code c_p x_si)
      (case-mode
       [sweep-in-old]
       [else
        (set! field (cast ptr (+ (cast uptr c_p) co)))]))]
   [else
    (trace-pure (just c_p))]))

(define-trace-macro (trace-code len)
  (case-mode
   [(copy)
    (copy-bytes code-data len)]
   [else
    (define t : ptr (code-reloc _))
    (define m : iptr (cond
                       [t (reloc-table-size t)]
                       [else 0]))
    (define oldco : ptr (cond
                          [t (reloc-table-code t)]
                          [else 0]))
    (define a : iptr 0)
    (define n : iptr 0)
    (while
     :? (< n m)
     (let* ([entry : uptr (reloc-table-data t n)]
            [item_off : uptr 0]
            [code_off : uptr 0])
       (set! n (+ n 1))
       (cond
         [(RELOC_EXTENDED_FORMAT entry)
          (set! item_off (reloc-table-data t n))
          (set! n (+ n 1))
          (set! code_off (reloc-table-data t n))
          (set! n (+ n 1))]
         [else
          (set! item_off (RELOC_ITEM_OFFSET entry))
          (set! code_off (RELOC_CODE_OFFSET entry))])
       (set! a (+ a code_off))
       (let* ([obj : ptr (S_get_code_obj (RELOC_TYPE entry) oldco a item_off)])
         (trace-pure (just obj))
         (case-mode
          [sweep
           (S_set_code_obj "gc" (RELOC_TYPE entry) _ a obj item_off)]
          [else]))))

    (case-mode
     [sweep
      (cond
        [(&& (== from_g static_generation)
             (&& (! S_G.retain_static_relocation)
                 (== 0 (& (code-type _) (<< code_flag_template code_flags_offset)))))
         (set! (code-reloc _) (cast ptr 0))]
        [else
         (let* ([t_si : seginfo* (SegInfo (ptr_get_segment t))])
           (when (-> t_si old_space)
             (cond
               [(SEGMENT_IS_LOCAL t_si t)
                (set! n (size_reloc_table (reloc-table-size t)))
                (count countof-relocation-table (just n) 1 sweep)
                (cond
                  [(-> t_si use_marks)
                   (cond
                     [(! (marked t_si t))
                      (mark_untyped_data_object _tgc_ t n t_si)])]
                  [else
                   (let* ([oldt : ptr t])
                     (find_gc_room _tgc_ space_data from_g type-untyped n t)
                     (memcpy_aligned (TO_VOIDP t) (TO_VOIDP oldt) n))])]
               [else
                (RECORD_REMOTE t_si)])))
         (set! (reloc-table-code t) _)
         (set! (code-reloc _) t)])
      (S_record_code_mod (-> _tgc_ tc) (cast uptr (TO_PTR (& (code-data _ 0)))) (cast uptr (code-length _)))]
     [else])]))

(define-trace-macro (check-bignum var)
  (trace (just var))
  (check_bignum var))

(define-trace-macro (unless-code-relocated stmt)
  (case-flag code-relocated?
   [on]
   [off
    (case-flag as-dirty?
     [on]
     [off stmt])]))

(define-trace-macro (or-assume-continuation e)
  (case-flag assume-continuation?
   [on 1]
   [off e]))

(define-trace-macro (and-counts e)
  (case-flag counts?
   [on e]
   [off 0]))

(define-trace-macro (and-not-as-dirty e)
  (case-flag as-dirty?
   [on 0]
   [off e]))

(define-trace-macro (or-not-as-dirty e)
  (case-flag as-dirty?
   [on e]
   [off 1]))

(define-trace-macro (and-purity-sensitive-mode e)
  (case-mode
   [(sweep sweep-in-old) e]
   [else 0]))

(define-trace-macro (when-mark e)
  (case-mode
   [(mark) e]
   [else]))

(define-trace-macro (pad e)
  (case-mode
   [(copy) e]
   [else]))

;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Parenthe-C compiler
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Every compiler needs its own little implementation of `match`, right?
;; Just pairs and literals, no ellipses.
(define-syntax (match stx)
  (syntax-case stx (else)
    [(_ expr [pattern rhs ...] ... [else else-rhs ...])
     #'(let ([v expr]) (matching v [pattern rhs ...] ... [else else-rhs ...]))]
    [(_ expr [pattern rhs ...] ...)
     #'(let ([v expr]) (match v [pattern rhs ...] ... [else (error 'match "no matching clause: ~s" v)]))]))

(define-syntax (matching stx)
  (syntax-case stx ()
    [(_ v [else rhs ...])
     #'(let () rhs ...)]
    [(_ v [pattern rhs ...] more ...)
     (letrec ([gen-match (lambda (pat quoted?)
                           (cond
                             [(identifier? pat)
                              (if quoted?
                                  #`(eq? v '#,pat)
                                  #t)]
                             [else
                              (syntax-case pat (quasiquote unquote)
                                [(quasiquote p)
                                 (if quoted?
                                     (error 'match "bad quasiquote")
                                     (gen-match #'p #t))]
                                [(unquote p)
                                 (if quoted?
                                     (gen-match #'p #f)
                                     (error 'match "bad unquote"))]
                                [(a . b)
                                 #`(and (pair? v)
                                        (let ([v (car v)])
                                          #,(gen-match #'a quoted?))
                                        (let ([v (cdr v)])
                                          #,(gen-match #'b quoted?)))]
                                [other
                                 #'(equal? v 'other)])]))]
              [get-binds (lambda (pat quoted?)
                           (cond
                             [(identifier? pat)
                              (if quoted?
                                  '()
                                  (list pat))]
                             [else
                              (syntax-case pat (quasiquote unquote)
                                [(quasiquote p)
                                 (get-binds #'p #t)]
                                [(unquote p)
                                 (get-binds #'p #f)]
                                [(a . b)
                                 (append (get-binds #'a quoted?)
                                         (get-binds #'b quoted?))]
                                [other '()])]))]
              [get-vals (lambda (pat quoted?)
                          (cond
                            [(identifier? pat)
                             (if quoted?
                                 #''()
                                 #'(list v))]
                            [else
                             (syntax-case pat (quasiquote unquote)
                               [(quasiquote p)
                                (get-vals #'p #t)]
                               [(unquote p)
                                (get-vals #'p #f)]
                               [(a . b)
                                #`(append (let ([v (car v)])
                                            #,(get-vals #'a quoted?))
                                          (let ([v (cdr v)])
                                            #,(get-vals #'b quoted?)))]
                               [other #''()])]))])
       (syntax-case #'pattern (quasiquote)
         [(quasiquote p)
          #`(if #,(gen-match #'pattern #f)
                (let-values ([#,(get-binds #'pattern #f)
                              (apply values #,(get-vals #'pattern #f))])
                  rhs ...)
                (matching v more ...))]
         [_
          (error 'match "bad pattern ~s" #'pattern)]))]))

(let ()

  (define preserve-flonum-eq? #t)
  
  ;; A config is an association list. Mostly, it determines the
  ;; generation mode, but it is also used to some degree as an
  ;; environment-like map to communicate information from one
  ;; statement to later statements.
  ;;
  ;; Some keys:
  ;;   - 'mode [required]
  ;;   - 'maybe-backreferences?
  ;;   - 'known-space [to prune generated cases]
  ;;   - 'known-types [to prune generated cases]

  (define lookup
    (case-lambda
     [(key config default)
      (let ([a (assq key config)])
        (if a
            (cadr a)
            default))]
     [(key config)
      (let ([a (assq key config)])
        (if a
            (cadr a)
            (error 'lookup "not found: ~s" key)))]))

  ;; A sequence wraps a list of string and other sequences with
  ;; formatting information
  (define-record-type seq
    (fields l))
  (define-record-type block-seq
    (fields l))
  (define-record-type indent-seq
    (fields pre mid post))

  ;; More convenient constructors for sequences:
  (define (code . l) (make-seq l))
  (define (code-block . l) (make-block-seq l))
  (define (code-indent pre mid post) (make-indent-seq pre mid post))

  ;; Main C-generation entry point:
  (define (generate name config)
    (define base-types (prune trace-base-types config))
    (define object-types (prune trace-object-types config))
    (define mode (lookup 'mode config))
    (code
     (format "static ~a ~a(~aptr p~a)"
             (case (lookup 'mode config)
               [(copy mark) "IGEN"]
               [(size) "uptr"]
               [(self-test) "IBOOL"]
               [(sweep) (if (lookup 'as-dirty? config #f)
                            "IGEN"
                            "void")]
               [(sweep-in-old) "void"]
               [else "void"])
             name
             (case (lookup 'mode config)
               [(copy mark sweep sweep-in-old measure) "thread_gc *tgc, "]
               [else ""])
             (case (lookup 'mode config)
               [(copy) ", seginfo *si, ptr *dest"]
               [(mark) ", seginfo *si"]
               [(sweep)
                (cond
                  [(lookup 'as-dirty? config #f) ", IGEN youngest"]
                  [(lookup 'no-from-g? config #f) ""]
                  [else ", IGEN from_g"])]
               [(check) ", uptr seg, ISPC s_in, IBOOL aftergc"]
               [else ""]))
     (let ([body
            (lambda ()
              (let ([config (cons (list 'used (make-eq-hashtable)) config)])
                (cond
                  [(null? base-types)
                   (cond
                     [(null? object-types)
                      (error 'generate "no relevant types")]
                     [(null? (cdr object-types))
                      (code-block (statements (cdar object-types)
                                              (cons `(type ,(caar object-types)) config)))]
                     [else
                      (generate-typed-object-dispatch object-types (cons '(basetype typed-object) config))])]
                  [else
                   (cond
                     [(null? object-types)
                      (generate-type-dispatch base-types config)]
                     [else
                      (generate-type-dispatch 
                       (cons (cons 'typed-object
                                   (generate-typed-object-dispatch object-types (cons '(basetype typed-object)
                                                                                      config)))
                             base-types)
                       config)])])))])
       (case (lookup 'mode config)
         [(copy)
          (code-block
           "check_triggers(tgc, si);"
           (code-block
            "ptr new_p;"
            "IGEN tg = TARGET_GENERATION(si);"
            (body)
            "tgc->sweep_change = SWEEP_CHANGE_PROGRESS;"
            "FWDADDRESS(p) = new_p;"
            "FWDMARKER(p) = forward_marker;"
            (and (lookup 'maybe-backreferences? config #f)
                 "ADD_BACKREFERENCE(p, tg);")
            "*dest = new_p;"
            "return tg;"))]
         [(mark)
          (code-block
           "check_triggers(tgc, si);"
           (ensure-segment-mark-mask "si" "")
           (body)
           "tgc->sweep_change = SWEEP_CHANGE_PROGRESS;"
           "ADD_BACKREFERENCE(p, si->generation);"
           "return si->generation;")]
         [(sweep)
          (code-block
           "FLUSH_REMOTE_BLOCK"
           (and (lookup 'maybe-backreferences? config #f)
                "PUSH_BACKREFERENCE(p)")
           (body)
           (and (lookup 'maybe-backreferences? config #f)
                "POP_BACKREFERENCE()")
           "FLUSH_REMOTE(tgc, p);"
           (and (lookup 'as-dirty? config #f)
                "return youngest;"))]
         [(sweep-in-old)
          (code-block
           "FLUSH_REMOTE_BLOCK"
           (body)
           "ASSERT_EMPTY_FLUSH_REMOTE();")]
         [(measure)
          (body)]
         [(self-test)
          (code-block
           (body)
           "return 0;")]
         [else
          (body)]))))

  (define (generate-type-dispatch l config)
    (let ([multi? (and (pair? l) (pair? (cdr l)))])
      (code-block
       (and multi? "ITYPE t = TYPEBITS(p);")
       (let loop ([l l] [else? #f])
         (cond
           [(null? l)
            (and multi?
                 (code "else"
                       (code-block
                        (format "S_error_abort(\"~a: illegal type\");" (lookup 'mode config)))))]
           [else
            (code
             (and multi?
                  (format "~aif (t == ~a)" (if else? "else " "") (as-c 'type (caar l))))
             (let ([c (cdar l)])
               (if (block-seq? c)
                   c
                   (code-block (statements c (cons (list 'basetype (caar l))
                                                           config)))))
             (loop (cdr l) #t))])))))

  (define (generate-typed-object-dispatch l config)
    (code-block
     "ptr tf = TYPEFIELD(p);"
     (let loop ([l l] [else? #f])
       (cond
         [(null? l)
          (code "else"
                (code-block
                 (format "S_error_abort(\"~a: illegal typed object type\");" (lookup 'mode config))))]
         [else
          (let* ([ty (caar l)]
                 [mask (lookup-constant (string->symbol (format "mask-~a" ty)))]
                 [type-constant? (eqv? mask (constant byte-constant-mask))])
            (code (format "~aif (~a)" (if else? "else " "")
                          (if type-constant?
                              (format "(iptr)tf == ~a" (as-c 'type ty))
                              (format "TYPEP(tf, ~a, ~a)" (as-c 'mask ty) (as-c 'type ty))))
                  (code-block (statements (cdar l) (cons* (list 'tf "tf")
                                                          (list 'type ty)
                                                          (if type-constant?
                                                              (cons `(type-constant ,(as-c 'type ty))
                                                                    config)
                                                              config))))
                  (loop (cdr l) #t)))]))))

  ;; list of S-expressions -> code sequence
  (define (statements l config)
    (cond
      [(null? l) (code)]
      [else
       (let ([a (car l)])
         (match a
           [`(case-mode . ,all-clauses)
            (let ([body (find-matching-mode (lookup 'mode config) all-clauses)])
              (statements (append body (cdr l)) config))]
           [`(case-space . ,all-clauses)
            (code
             (code-block
              (format "ISPC p_at_spc = ~a;"
                      (case (lookup 'mode config)
                        [(copy mark) "si->space"]
                        [else "SPACE(p)"]))
              (let loop ([all-clauses all-clauses] [else? #f])
                (match all-clauses
                  [`([else . ,body])
                   (code
                    "else"
                    (code-block (statements body config)))]
                  [`([,spc . ,body] . ,rest)
                   (unless (or (symbol? spc)
                               (and (pair? spc)
                                    (memq (car spc) '(< <= == >= >))
                                    (pair? (cdr spc))
                                    (symbol? (cadr spc))
                                    (null? (cddr spc))))
                     (error 'case-space "bad space spec: ~s" spc))
                   (code
                    (format "~aif (p_at_spc ~a ~a)"
                            (if else? "else " "")
                            (if (pair? spc) (car spc) "==")
                            (as-c (if (pair? spc) (cadr spc) spc)))
                    (code-block (statements body config))
                    (loop rest #t))])))
             (statements (cdr l) config))]
           [`(case-flag ,flag
                        [on . ,on]
                        [off . ,off])
            (let ([body (if (lookup flag config #f)
                            on
                            off)])
              (statements (append body (cdr l)) config))]
           [`(trace-early-rtd ,field)
            (code (case (and (not (lookup 'as-dirty? config #f))
                             (lookup 'mode config))
                    [(copy mark)
                     (cond
                       [(lookup 'counts? config #f)
                        (code
                         "/* Relocate to make sure we aren't using an oldspace descriptor"
                         "   that has been overwritten by a forwarding marker, but don't loop"
                         "   on tag-reflexive base descriptor */"
                         (format "if (p != ~a)"
                                 (lookup 'tf config (format "TYPEFIELD(p)")))
                         (code-block
                          (statements `((trace-early ,field)) config)))]
                       [else
                        (code
                         "/* Do not inspect the type or first field of the rtd, because"
                         "   it may have been overwritten for forwarding. */")])]
                    [(measure sweep sweep-in-old check)
                     (statements `((trace-early ,field)) (cons `(early-rtd? #t) config))]
                    [else #f])
                  (statements (cdr l) (cons `(copy-extra-rtd ,field) config)))]
           [`(trace ,field)
            (code (trace-statement field config #f 'impure)
                  (statements (cdr l) config))]
           [`(trace-pure ,field)
            (code (and (not (lookup 'as-dirty? config #f))
                       (trace-statement field config #f 'pure))
                  (statements (cdr l) config))]
           [`(trace-pure-code ,field)
            (code (and (not (lookup 'as-dirty? config #f))
                       (trace-statement field (cons `(early-code? #t) config) #f 'pure))
                  (statements (cdr l) config))]
           [`(trace-early ,field)
            (code (trace-statement field config #t 'pure)
                  (statements (cdr l) (if (symbol? field)
                                          (cons `(copy-extra ,field) config)
                                          config)))]
           [`(trace-now ,field)
            (code
             (case (lookup 'mode config)
               [(copy)
                (code-block
                 (format "ptr tmp_p = ~a;" (field-expression field config "p" #f))
                 (relocate-statement 'pure "tmp_p" config)
                 (format "~a = tmp_p;" (field-expression field config "new_p" #f)))]
               [(self-test) #f]
               [(measure)
                (statements (list `(trace ,field)) config)]
               [(mark)
                (relocate-statement 'pure (field-expression field config "p" #t) config)]
               [else
                (and (not (lookup 'as-dirty? config #f))
                     (trace-statement field config #f 'pure))])
             (statements (cdr l) config))]
           [`(trace-reference ,field)
            (code (trace-statement field config #f 'reference)
                  (statements (cdr l) config))]
           [`(copy ,field)
            (code (copy-statement field config)
                  (statements (cdr l) config))]
           [`(copy-flonum ,field)
            (cond
              [(and preserve-flonum-eq?
                    (eq? 'copy (lookup 'mode config)))
               (code (copy-statement field config)
                     "flonum_set_forwarded(tgc, p, si);"
                     "FLONUM_FWDADDRESS(p) = new_p;"
                     (statements (cdr l) config))]
              [else
               (statements (cons `(copy ,field) (cdr l)) config)])]
           [`(copy-flonum* ,field)
            (cond
              [preserve-flonum-eq?
               (case (lookup 'mode config)
                 [(copy)
                  (code (code-block
                         (format "ptr tmp_p = TYPE(TO_PTR(&~a), type_flonum);" (field-expression field config "p" #t))
                         "if (flonum_is_forwarded_p(tmp_p, si))"
                         (format "  ~a = FLODAT(FLONUM_FWDADDRESS(tmp_p));"
                                 (field-expression field config "new_p" #f))
                         "else"
                         (format "  ~a = ~a;"
                                 (field-expression field config "new_p" #f)
                                 (field-expression field config "p" #f)))
                        (statements (cdr l) config))]
                 [else (statements (cdr l) config)])]
              [else
               (statements (cons `(copy ,field) (cdr l)) config)])]
           [`(copy-bytes ,offset ,len)
            (code (case (lookup 'mode config)
                    [(copy)
                     (format "memcpy_aligned(&~a, &~a, ~a);"
                             (field-expression offset config "new_p" #t)
                             (field-expression offset config "p" #t)
                             (expression len config))]
                    [else #f])
                  (statements (cdr l) config))]
           [`(copy-type ,field)
            (case (lookup 'mode config)
              [(copy)
               (code
                (format "~a = ~a;"
                        (field-expression field config "new_p" #f)
                        (or (lookup 'type-constant config #f)
                            "(uptr)tf"))
                (statements (cdr l) config))]
              [else
               (statements (cons `(copy ,field) (cdr l)) config)])]
           [`(trace-ptrs ,offset ,len)
            (statements (cons `(trace-ptrs ,offset ,len impure)
                              (cdr l))
                        config)]
           [`(trace-pure-ptrs ,offset ,len)
            (statements (cons `(trace-ptrs ,offset ,len pure)
                              (cdr l))
                        config)]
           [`(trace-reference-ptrs ,offset ,len)
            (statements (cons `(trace-ptrs ,offset ,len reference)
                              (cdr l))
                        config)]
           [`(trace-ptrs ,offset ,len ,purity/kind)
            (case (lookup 'mode config)
              [(copy)
               (statements (cons `(copy-bytes ,offset (* ptr_bytes ,len))
                                 (cdr l))
                           config)]
              [(sweep measure sweep-in-old check self-test)
               (code
                (loop-over-pointers
                 (field-expression offset config "p" #t)
                 len
                 (trace-statement `(array-ref p_p idx) config #f purity/kind)
                 config
                 purity/kind)
                (statements (cdr l) config))]
              [else (statements (cdr l) config)])]
           [`(count ,counter)
            (code (count-statement counter #f 1 'copy config)
                  (statements (cdr l) config))]
           [`(count ,counter ,size)
            (statements (cons `(count ,counter ,size 1 copy) (cdr l)) config)]
           [`(count ,counter ,size ,scale)
            (statements (cons `(count ,counter ,size ,scale copy) (cdr l)) config)]
           [`(count ,counter ,size ,scale ,modes)
            (code (count-statement counter size scale modes
                                   (cons `(constant-size? ,(symbol? size))
                                         config))
                  (statements (cdr l) config))]
           [`(as-mark-end . ,stmts)
            (statements (append stmts (cdr l))
                        config)]
           [`(space ,s)
            (case (lookup 'mode config)
              [(copy)
               (code (code-indent "ISPC p_spc = "
                                  (expression s config #f #t)
                                  ";")
                     (statements (cdr l) (cons '(space-ready? #t) config)))]
              [(mark)
               (statements (cdr l) (if (symbol? s)
                                       (cons `(known-space ,s) config)
                                       config))]
              [else (statements (cdr l) config)])]
           [`(size ,sz)
            (statements (cons `(size ,sz ,1) (cdr l)) config)]
           [`(size ,sz ,scale)
            (let* ([mode (lookup 'mode config)]
                   [mode (if (lookup 'return-size? config #f)
                             (case mode
                               [(sweep) 'sweep+size]
                               [else mode])
                             mode)]
                   [was-used? (let ([used? (hashtable-ref (lookup 'used config) 'p_sz #f)])
                                (hashtable-set! (lookup 'used config) 'p_sz #f)
                                used?)]
                   [config (if (and (symbol? sz)
                                    (eqv? scale 1))
                               (cons `(known-size ,sz) config)
                               config)]
                   [config (if (symbol? sz)
                               (cons '(constant-size? #t)
                                     config)
                               config)]
                   [rest
                    (case mode
                      [(copy)
                       (unless (lookup 'space-ready? config #f)
                         (error 'generate "size before space"))
                       (hashtable-set! (lookup 'used config) 'p_sz #t)
                       (code (format "~a, ~a, p_sz, new_p);"
                                     "find_gc_room(tgc, p_spc, tg"
                                     (as-c 'type (lookup 'basetype config)))
                             (statements (let ([extra (lookup 'copy-extra config #f)])
                                           (if extra
                                               (cons `(copy ,extra) (cdr l))
                                               (let* ([mode (lookup 'mode config)]
                                                      [extra (and (memq mode '(copy))
                                                                  (lookup 'copy-extra-rtd config #f))])
                                                 (if extra
                                                     (cons `(set! (,extra _copy_)
                                                                  ,(case (and (lookup 'counts? config #f)
                                                                              mode)
                                                                     [(copy)
                                                                      `(cond
                                                                         [(== tf _) _copy_]
                                                                         [else rtd])]
                                                                     [else 'rtd]))
                                                           (cdr l))
                                                     (cdr l)))))
                                         (cons '(copy-ready? #t)
                                               config)))]
                      [(size)
                       (hashtable-set! (lookup 'used config) 'p_sz #t)
                       (code "return p_sz;")]
                      [(measure)
                       (hashtable-set! (lookup 'used config) 'p_sz #t)
                       (code "measure_total += p_sz;"
                             (statements (cdr l) config))]
                      [else (statements (cdr l) config)])]
                   [used? (hashtable-ref (lookup 'used config) 'p_sz #f)])
              (hashtable-set! (lookup 'used config) 'p_sz was-used?)
              (cond
                [used?
                 (code-block
                  (format "uptr p_sz = ~a;" (let ([s (size-expression sz config)])
                                              (if (= scale 1)
                                                  s
                                                  (format "~a * (~a)" scale s))))
                  rest)]
                [else rest]))]
           [`(skip-forwarding)
            (case (lookup 'mode config)
              [(copy)
               (unless (null? (cdr l))
                 (error 'skip-forwarding "not at end"))
               (code "*dest = new_p;"
                     "tgc->sweep_change = SWEEP_CHANGE_PROGRESS;"
                     "return tg;")]
              [else
               (statements (cdr l) config)])]
           [`(mark . ,flags)
            (for-each (lambda (flag)
                        (unless (memq flag '(one-bit no-sweep within-segment counting-root))
                          (error 'mark "bad flag ~s" flag)))
                      flags)
            (case (lookup 'mode config)
              [(mark)
               (let* ([count-stmt (let loop ([l (cdr l)])
                                   (cond
                                     [(null? l) (error 'mark "could not find `count` or `as-mark-end` ~s" config)]
                                     [else
                                      (match (car l)
                                        [`(count . ,rest) (car l)]
                                        [`(as-mark-end . ,stmts) (car l)]
                                        [`(case-mode . ,all-clauses)
                                         (let ([body (find-matching-mode 'mark all-clauses)])
                                           (loop (append body (cdr l))))]
                                        [`(,id . ,args)
                                         (let ([m (eq-hashtable-ref trace-macros id #f)])
                                           (if m
                                               (loop (append (apply-macro m args)
                                                             (cdr l)))
                                               (loop (cdr l))))]
                                        [else (loop (cdr l))])]))])
                 (code
                  (mark-statement flags config)
                  (statements (list count-stmt) config)))]
              [else
               (statements (cdr l) config)])]
           [`(define ,id : ,type ,rhs)
            (let* ([used (lookup 'used config)]
                   [prev-used? (hashtable-ref used id #f)])
              (hashtable-set! used id #f)
              (let* ([rest (statements (cdr l) config)]
                     [used? (hashtable-ref (lookup 'used config) id #f)])
                (hashtable-set! used id prev-used?)
                (if used?
                    (code-block (code-indent (format "~a ~a = " type id)
                                             (expression rhs config #f #t)
                                             ";")
                                rest)
                    rest)))]
           [`(cond . ,clauses)
            (code
             (let loop ([clauses clauses] [else? #f])
               (match clauses
                 [`() (code)]
                 [`([else . ,rhss])
                  (cond
                    [(null? rhss)
                     (code)]
                    [else
                     (if else?
                         (code "else"
                               (code-block
                                (statements rhss config)))
                         (statements rhss config))])]
                 [`([,test . ,rhss] . ,clauses)
                  (let ([tst (expression test config)])
                    (cond
                      [(equal? tst "0")
                       (loop clauses else?)]
                      [else
                       (let ([rhs (statements rhss config)])
                         (cond
                           [(equal? tst "1")
                            (if else?
                                (code "else" (code-block rhs))
                                rhs)]
                           [else
                            (code (format "~aif (~a)" (if else? "else " "") tst)
                                  (code-block rhs)
                                  (loop clauses #t))]))]))]))
             (statements (cdr l) config))]
           [`(let* ,binds . ,body)
            (code
             (code-block
              (let loop ([binds binds])
                (match binds
                  [`() (statements body config)]
                  [`([,id : ,type ,rhs] . ,binds)
                   (code (code-indent (format "~a ~a = " type id)
                                      (expression rhs config #f #t)
                                      ";")
                         (loop binds))])))
             (statements (cdr l) config))]
           [`(while :? ,tst . ,body)
            (code (format "while (~a)" (expression tst config))
                  (code-block
                   (statements body config))
                  (statements (cdr l) config))]
           [`(do-while . ,body+test)
            (let-values ([(body tst)
                          (let loop ([body+test body+test] [rev-body '()])
                            (match body+test
                              [`(:? ,test) (values (reverse rev-body) test)]
                              [`(,e . ,rest)
                               (loop rest (cons e rev-body))]))])
              (code "do"
                    (code-block
                     (statements body config))
                    (format "while (~a);"  (expression tst config))
                    (statements (cdr l) config)))]
           [`(when _bytevector-pad?_ . ,body)
            (statements (append
                         (if (getprop 'bytevector-pad-disp '*constant* #f)
                             body
                             '())
                         (cdr l))
                        config)]
           [`(when ,tst . ,body)
            (statements (cons `(cond [,tst . ,body][else]) (cdr l))
                        config)]
           [`(set! ,lhs ,rhs)
            (code (code-indent (format "~a = "
                                       (expression lhs config))
                               (expression rhs config #f #t)
                               ";")
                  (statements (cdr l) config))]
           [`(set! ,lhs ,op ,rhs)
            (unless (memq op '(+= -= <<= >>=))
              (error 'set! "not an update op ~s" op))
            (code (format "~a ~a ~a;"
                          (expression lhs config)
                          op
                          (expression rhs config))
                  (statements (cdr l) config))]
           [`(break)
            (code "break;")]
           [`(assert ,expr)
            (unless (eval expr)
              (error 'assert "failed: ~s" expr))
            (statements (cdr l) config)]
           [`(,id . ,args)
            (let ([m (eq-hashtable-ref trace-macros id #f)])
              (if m
                  (statements (append (apply-macro m args)
                                      (cdr l))
                              config)
                  (code (format "~a;" (expression a config #f #t))
                        (statements (cdr l) config))))]
           [else
            (code (format "~a;" (expression a config #f #t))
                  (statements (cdr l) config))]))]))

  ;; S-expression -> string
  (define expression
    (case-lambda
     [(a config) (expression a config #f #f)]
     [(a config protect?) (expression a config protect? #f)]
     [(a config protect? multiline?)
      (define (protect s)
        (if protect? (format "(~a)" s) s))
      (match a
        [`_ "p"]
        [`_copy_ (case (lookup 'mode config)
                   [(copy) "new_p"]
                   [else "p"])]
        [`_size_
         (cond
           [(lookup 'parallel? config #f)
            (hashtable-set! (lookup 'used config) 'p_sz #t)
            "p_sz"]
           [else "SIZE"])]
        [`_tf_
         (lookup 'tf config "TYPEFIELD(p)")]
        [`_tg_
         (case (lookup 'mode config)
           [(copy) "tg"]
           [(mark) "TARGET_GENERATION(si)"]
           [else "target_generation"])]
        [`_tgc_ "tgc"]
        [`_backreferences?_
         (if (lookup 'maybe-backreferences? config #f)
             "BACKREFERENCES_ENABLED"
             "0")]
        [`(if _bytevector-pad?_ ,tru ,fls)
         (let ([e (if (getprop 'bytevector-pad-disp '*constant* #f)
                      tru
                      fls)])
           (expression e config protect? multiline?))]
        [`(just ,id)
         (hashtable-set! (lookup 'used config) id #t)
         (symbol->string id)]
        [`(case-flag ,flag
           [on ,on]
           [off ,off])
         (let ([e (if (lookup flag config #f)
                      on
                      off)])
           (expression e config protect? multiline?))]
        [`(case-mode . ,all-clauses)
         (match (find-matching-mode (lookup 'mode config) all-clauses)
           [`(,e)
            (expression e config protect? multiline?)]
           [`,any
            (error 'case-mode "bad form ~s" a)])]
        [`(cond . ,clauses)
         (let loop ([clauses clauses] [protect? protect?])
           (match clauses
             [`([else ,rhs]) (expression rhs config protect? multiline?)]
             [`([,test ,rhs] . ,clauses)
              (let ([tst (expression test config #t #t)])
                (cond
                  [(equal? tst "0")
                   (loop clauses protect?)]
                  [(equal? tst "1")
                   (expression rhs config protect? multiline?)]
                  [else
                   (if multiline?
                       (format "(~a\n ? ~a\n : ~a)"
                               tst
                               (indent-newlines (expression rhs config #t #t) 3)
                               (indent-newlines (loop clauses #t) 3))
                       (format "(~a ? ~a : ~a)"
                               tst
                               (expression rhs config #t #f)
                               (loop clauses #t)))]))]))]
        [`(cast ,type ,e)
         (protect (format "(~a)~a" type (expression e config #t)))]
        [`(array-ref ,array ,index)
         (protect (format "~a[~a]"
                          (expression array config #t)
                          (expression index config)))]
        [`(set! ,lhs ,rhs) ; a `set!` used as an expression
         (format "(~a = ~a)"
                 (expression lhs config #t)
                 (expression rhs config #t))]
        [`(,op ,a)
         (cond
           [(memq op '(& - !))
            (protect (format "~a~a" op (expression a config #t)))]
           [(get-offset-value op)
            => (lambda (v)
                 (protect (field-ref-expression (expression a config) v op #f config)))]
           [(eq-hashtable-ref trace-macros op #f)
            => (lambda (m)
                 (expression (car (apply-macro m (list a))) config protect? multiline?))]
           [else
            (protect (format "~a(~a)" op (expression a config #t)))])]
        [`(begin ,a ,b)
         (format "(~a, ~a)" (expression a config #t) (expression b config #t))]
        [`(,op ,a ,b)
         (cond
           [(memq op '(& && \|\| == != + - * < > <= >= << >> ->))
            (protect (format "~a ~a ~a" (expression a config #t) op (expression b config #t)))]
           [(get-offset-value op)
            => (lambda (v)
                 (protect (field-ref-expression (expression a config) v op b config)))]
           [else
            (protect (format "~a(~a, ~a)" op (expression a config) (expression b config)))])]
        [`(,rator . ,rands)
         (unless (symbol? rator)
           (error 'expression "expected a symbol for function name: ~s" rator))
         (format "~a(~a)"
                 rator
                 (comma-ize (map (lambda (r) (expression r config)) rands)))]
        [else
         (cond
           [(eq? a #f) "Sfalse"]
           [(eq? a #t) "Strue"]
           [(symbol? a)
            (cond
              [(getprop a '*c-name* #f)
               => (lambda (c-name) c-name)]
              [else
               (hashtable-set! (lookup 'used config) a #t)
               (symbol->string a)])]
           [else
            (format "~s" a)])])]))

  (define (find-matching-mode mode all-clauses)
    (let loop ([clauses all-clauses])
      (match clauses
        [`([else . ,body])
         body]
        [`([,cl-mode . ,cl-body] . ,clauses)
         (if (or (eq? cl-mode mode)
                 (and (pair? cl-mode)
                      (memq mode cl-mode)))
             cl-body
             (loop clauses))]
        [`()
         (error 'case-mode "no matching case for ~s in ~s" mode all-clauses)])))

  (define (loop-over-pointers ptr-e len body config purity/kind)
    (code-block
     (format "uptr idx, p_len = ~a;" (expression len config))
     (format "ptr *p_p = ~a&~a;" (if (eq? purity/kind 'reference) "(ptr*)" "")
             ptr-e)
     "for (idx = 0; idx < p_len; idx++)"
     (code-block body)))

  (define (trace-statement field config early? purity/kind)
    (define mode (lookup 'mode config))
    (define (reference->object e)
      (if (eq? purity/kind 'reference)
          (format "S_maybe_reference_to_object(~a)" e)
          e))
    (cond
      [(or (eq? mode 'sweep)
           (eq? mode 'sweep-in-old)
           (and early? (or (eq? mode 'copy)
                           (eq? mode 'mark))))
       (relocate-statement purity/kind (field-expression field config "p" #t) config)]
      [(eq? mode 'copy)
       (copy-statement field config)]
      [(eq? mode 'measure)
       (measure-statement (reference->object (field-expression field config "p" #f)))]
      [(eq? mode 'self-test)
       (format "if (p == ~a) return 1;" (reference->object (field-expression field config "p" #f)))]
      [(eq? mode 'check)
       (format "check_pointer(&(~a), ~a, ~a, ~a, seg, s_in, aftergc);"
               (field-expression field config "p" #f)
               (match field
                 [`(just ,_) "0"]
                 [else "1"])
               (if (eq? purity/kind 'reference) "1" "0")
               (expression '_ config))]
      [else #f]))

  (define (relocate-statement purity/kind e config)
    (define mode (lookup 'mode config))
    (case mode
      [(sweep-in-old)
       (case purity/kind
         [(pure) (format "relocate_pure(&~a);" e)]
         [(reference) (format "relocate_reference_indirect(~a);" e)]
         [else (format "relocate_indirect(~a);" e)])]
      [else
       (if (lookup 'as-dirty? config #f)
           (case purity/kind
             [(pure) (error 'relocate-statement "pure as dirty?")]
             [(reference) (format "relocate_reference_dirty(&~a, youngest);" e)]
             [else (format "relocate_dirty(&~a, youngest);" e)])
           (let ([in-owner (case mode
                             [(copy mark) (if (lookup 'parallel? config #f)
                                              "_in_owner"
                                              "")]
                             [else ""])])
             (format "relocate_~a~a(&~a~a);" purity/kind in-owner e (if (eq? purity/kind 'pure) "" ", from_g"))))]))

  (define (measure-statement e)
    (code
     "{ /* measure */"
     (format "  ptr r_p = ~a;" e)
     "  if (!FIXMEDIATE(r_p))"
     "    push_measure(tgc, r_p);"
     "}"))

  (define (copy-statement field config)
    (define mode (lookup 'mode config))
    (case mode
      [(copy)
       (cond
         [(symbol? field)
          (unless (lookup 'copy-ready? config #f)
            (error 'copy "need size before: ~s" field))
          (format "~a = ~a;"
                  (field-expression field config "new_p" #f)
                  (field-expression field config "p" #f))]
         [else
          (when (eq? mode 'copy)
            (error 'copy "pointless copy to self for ~s" field))
          #f])]
      [else #f]))

  (define (count-statement counter size scale modes config)
    (let* ([real-mode (lookup 'mode config)]
           [mode (if (eq? real-mode 'mark) 'copy real-mode)])
      (cond
        [(or (eq? mode modes) (and (pair? modes) (memq mode modes)))
         (cond
           [(lookup 'counts? config #f)
            (let ([tg (case real-mode
                        [(copy) "tg"]
                        [(sweep) "from_g"]
                        [(mark) "TARGET_GENERATION(si)"]
                        [else "target_generation"])])
              (code
               (format "S_G.countof[~a][~a] += ~a;" tg (as-c counter) scale)
               (if (lookup 'constant-size? config #f)
                   #f
                   (format "S_G.bytesof[~a][~a] += ~a;"
                           tg
                           (as-c counter)
                           (let ([s (if size
                                        (expression size config)
                                        (begin
                                          (hashtable-set! (lookup 'used config) 'p_sz #t)
                                          "p_sz"))])
                             (if (eqv? scale 1)
                                 s
                                 (format "~a * (~a)" scale s)))))))]
           [else #f])]
        [else #f])))

  (define (mark-statement flags config)
    (let* ([known-space (lookup 'known-space config #f)]
           [sz (let ([sz (lookup 'known-size config #f)])
                 (and sz (get-size-value sz)))]
           [one-bit? (or (memq 'one-bit flags)
                         (eq? 'space-data known-space)
                         (eqv? sz (constant byte-alignment)))]
           [within-segment? (or (memq 'within-segment flags)
                                (and sz
                                     (< sz (constant bytes-per-segment))))]
           [no-sweep? (or (memq 'no-sweep flags)
                          (eq? known-space 'space-data))]
           [within-loop-statement
            (lambda (decl si step count? final)
              (code-block
               "uptr offset = 0;"
               "while (offset < p_sz) {"
               "  ptr mark_p = (ptr)((uptr)p + offset);"
               decl
               (format "  ~a->marked_mask[segment_bitmap_byte(mark_p)] |= segment_bitmap_bit(mark_p);" si)
               (and count? (format "  ~a->marked_count += ~a;" si step))
               (format "  offset += ~a;" step)
               final
               "}"))]
           [type (let ([t (lookup 'basetype config)])
                   (if (eq? t 'type-untyped)
                       #f
                       (as-c 'type (lookup 'basetype config))))]
           [untype (lambda ()
                     (if type
                         (format "(uptr)UNTYPE(p, ~a)" type)
                         (format "(uptr)p")))])
      (hashtable-set! (lookup 'used config) 'p_sz #t)
      (code
       (cond
         [one-bit?
          (code
           "si->marked_mask[segment_bitmap_byte(p)] |= segment_bitmap_bit(p);"
           (cond
             [within-segment?
              "si->marked_count += p_sz;"]
             [else
              (code-block
               (format "uptr addr = ~a;" (untype))
               "uptr seg = addr_get_segment(addr);"
               "uptr end_seg = addr_get_segment(addr + p_sz - 1);"
               "if (seg == end_seg) {"
               "  si->marked_count += p_sz;"
               "} else {"
               "  seginfo *mark_si; IGEN g;"
               "  si->marked_count += ((uptr)build_ptr(seg+1,0)) - addr;"
               "  seg++;"
               "  while (seg < end_seg) {"
               "    mark_si = SegInfo(seg);"
               "    g = mark_si->generation;"
               "    if (!fully_marked_mask[g]) init_fully_marked_mask(tgc, g);"
               "    mark_si->marked_mask = fully_marked_mask[g];"
               "    mark_si->marked_count = bytes_per_segment;"
               "    seg++;"
               "  }"
               "  mark_si = SegInfo(end_seg);"
               "  {"
               (ensure-segment-mark-mask "mark_si" "    ")
               "    /* no need to set a bit: it's enough to have made `marked_mask` non-NULL */"
               "    mark_si->marked_count += addr + p_sz - (uptr)build_ptr(end_seg,0);"
               "  }"
               "}")]))]
         [within-segment?
          (code
           "si->marked_count += p_sz;"
           (cond
             [sz
              (code-block
               "ptr mark_p = p;"
               (let loop ([sz sz])
                 (code
                  "si->marked_mask[segment_bitmap_byte(mark_p)] |= segment_bitmap_bit(mark_p);"
                  (let ([sz (- sz (constant byte-alignment))])
                    (if (zero? sz)
                        #f
                        (code
                         "mark_p = (ptr)((uptr)mark_p + byte_alignment);"
                         (loop sz)))))))]
             [else
              (within-loop-statement #f "si" "byte_alignment" #f #f)]))]
         [else
          (let ([step "byte_alignment"])
            (code-block
             (format "uptr addr = (uptr)UNTYPE(p, ~a);" type)
             "if (addr_get_segment(addr) == addr_get_segment(addr + p_sz - 1))"
             (code-block
              "si->marked_count += p_sz;"
              (within-loop-statement #f "si" step #f #f))
             "else"
             (within-loop-statement (code
                                     "  seginfo *mark_si = SegInfo(ptr_get_segment(mark_p));"
                                     (ensure-segment-mark-mask "mark_si" "  "))
                                    "mark_si"
                                    step
                                    #t
                                    #f)))])
       (cond
         [no-sweep? #f]
         [else
          (let ([push "push_sweep(p);"])
            (cond
              [(and (memq 'counting-root flags)
                    (lookup 'counts? config #f))
               (code "if (!is_counting_root(si, p))"
                     (code-block push))]
              [else push]))]))))

  (define (field-expression field config arg protect?)
    (if (symbol? field)
        (cond
          [(get-offset-value field)
           => (lambda (v)
                (field-ref-expression arg v field 0 config))]
          [else
           (error 'field "identifier is not a field accessor: ~s" field)])
        (expression field config protect?)))

  (define (size-expression sz config)
    (if (symbol? sz)
        (cond
          [(get-size-value sz)
           => (lambda (v) (as-c sz))]
          [else
           (error 'size "identifier is not a size: ~s" sz)])
        (expression sz config)))

  (define (field-ref-expression obj v acc-name index config)
    (let ([c-ref (getprop acc-name '*c-ref* #f)])
      (unless c-ref
        (error 'field-ref "could not find accessor for ~s" acc-name))
      (cond
        [(pair? c-ref)
         (unless index
           (error 'field-ref "missing index for array field ~s" acc-name))
         (format "~a(~a, ~a)" (car c-ref) obj (expression index config))]
        [else
         (when (and index (not (eq? index 0)))
           (error 'field-ref "index not allowed for non-array field ~s" acc-name))
         (format "~a(~a)" c-ref obj)])))
  
  (define (ensure-segment-mark-mask si inset)
    (code
     (format "~aif (!~a->marked_mask) {" inset si)
     (format "~a  init_mask(tgc, ~a->marked_mask, ~a->generation, 0);"
             inset si si)
     (format "~a}" inset)))

  (define (just-mark-bit-space? sp)
    (case sp
      [(space-symbol space-port) #t]
      [else (atomic-space? sp)]))

  (define (atomic-space? sp)
    (case sp
      [(space-data) #t]
      [else #f]))

  ;; Slightly hacky way to check whether `op` is an accessor
  (define (get-offset-value op)
    (getprop (string->symbol (format "~a-disp" op)) '*constant* #f))

  ;; Check whether `op` is a size (probably)
  (define (get-size-value op)
    (getprop op '*constant* #f))

  ;; Convert to C constant name
  (define as-c
    (case-lambda
     [(sym)
      (or (getprop sym '*c-name* #f)
          (error 'as-type "failed for ~s" sym))]
     [(prefix base)
      (or (getprop (string->symbol (format "~a-~a" prefix base)) '*c-name* #f)
          (error 'as-type "failed for ~s ~s" prefix base))]))

  (define (comma-ize l)
    (apply string-append
           (let loop ([l l])
             (if (null? l)
                 '("")
                 (if (null? (cdr l))
                     (list (car l))
                     (list* (car l) ", " (loop (cdr l))))))))

  (define (apply-macro m l)
    (define args (car m))
    (define body (cdr m))
    (unless (= (length args) (length l))
      (error 'apply-macro "wrong macro argument count: ~s vs ~s" args l))
    (let ([subs (map cons args l)])
      (let loop ([m body])
        (cond
          [(symbol? m)
           (let ([a (assq m subs)])
             (if a
                 (cdr a)
                 m))]
          [(pair? m)
           (cons (loop (car m)) (loop (cdr m)))]
          [else m]))))

  (define (type-included? type config)
    (let ([types (lookup 'known-types config #f)])
      (if (not types)
          #t
          (memq type types))))

  (define (prune types config)
    (let loop ([types types])
      (if (null? types)
          '()
          (let ([s (prune-one (car types) config)])
            (if s
                (cons s (loop (cdr types)))
                (loop (cdr types)))))))

  (define (prune-one type config)
    (define known-types (lookup 'known-types config #f))
    (cond
      [(or (not known-types)
           (memq (car type) known-types))
       (let ([known-space (lookup 'known-space config #f)])
         (cond
           [(or (not known-space)
                (body-has-space? (cdr type) known-space config))
            type]
           [else #f]))]
      [else #f]))

  (define (body-has-space? body space config)
    (cond
      [(null? body) (error 'base-has-space? "no `space` specification in body")]
      [else
       (let ([a (car body)])
         (cond
           [(and (pair? a) (eq? (car a) 'space))
            (body-has-tail? (cdr a) space config)]
           [(and (pair? a) (memq (car a) '(case-space cond)))
            (unless (null? (cdr body)) (error 'body-has-space? "there's more?"))
            (let loop ([clauses (cdr a)])
              (if (null? clauses)
                  #f
                  (or (body-has-space? (cdar clauses) space config)
                      (loop (cdr clauses)))))]
           [else
            (body-has-space? (cdr body) space config)]))]))

  (define (body-has-tail? body key config)
    (cond
      [(null? body) #f]
      [else
       (let ([a (car body)])
         (match a
           [`(cond . ,clauses)
            (ormap (lambda (clause)
                     (body-has-tail? (cdr clause) key config))
                   clauses)]
           [else
            (body-has-tail? (cdr body) key config)]))]))

  (define print-code
    (case-lambda
     [(c)
      (print-code c 0)
      (newline)]
     [(c indentation)
      (cond
        [(not c) (void)]
        [(seq? c)
         (for-each (lambda (p)
                     (print-code p indentation))
                   (seq-l c))]
        [(block-seq? c)
         (let ([l (block-seq-l c)])
           (cond
             [(and (pair? l)
                   (null? (cdr l))
                   (block-seq? (car l)))
              (print-code (car l) indentation)]
             [else
              (indent indentation)
              (printf "{\n")
              (for-each (lambda (p)
                          (print-code p (+ indentation 2)))
                        l)
              (indent indentation)
              (printf "}\n")]))]
        [(indent-seq? c)
         (indent indentation)
         (printf "~a" (indent-seq-pre c))
         (printf "~a" (indent-newlines (indent-seq-mid c)
                                       (+ indentation (string-length (indent-seq-pre c)))))
         (printf "~a" (indent-seq-post c))
         (newline)]
        [else
         (indent indentation)
         (printf "~a\n" (indent-newlines c indentation))])]))

  (define (indent n)
    (display (make-string n #\space)))

  (define (indent-newlines s n)
    (list->string
     (let loop ([l (string->list s)])
       (cond
         [(null? l) '()]
         [(eqv? #\newline (car l))
          (cons #\newline (append (string->list (make-string n #\space))
                                  (loop (cdr l))))]
         [else (cons (car l) (loop (cdr l)))]))))

  (define (gen-gc ofn count? measure? parallel?)
    (guard
     (x [#t (raise x)])
     (parameterize ([current-output-port (open-output-file ofn 'replace)])
       (print-code (generate "copy"
                             `((mode copy)
                               (maybe-backreferences? ,count?)
                               (counts? ,count?)
                               (parallel? ,parallel?))))
       (print-code (generate "sweep"
                             `((mode sweep)
                               (maybe-backreferences? ,count?)
                               (counts? ,count?)
                               (parallel? ,parallel?))))
       (print-code (generate "sweep_object_in_old"
                             `((mode sweep-in-old)
                               (maybe-backreferences? ,count?)
                               (parallel? ,parallel?))))
       (print-code (generate "sweep_dirty_object"
                             `((mode sweep)
                               (maybe-backreferences? ,count?)
                               (counts? ,count?)
                               (parallel? ,parallel?)
                               (as-dirty? #t))))
       (letrec ([sweep1
                 (case-lambda
                  [(type) (sweep1 type (format "sweep_~a" type) '())]
                  [(type name) (sweep1 type name '())]
                  [(type name extra-configs)
                   (print-code (generate name
                                         (append
                                          extra-configs
                                          `((mode sweep)
                                            (known-types (,type))
                                            (maybe-backreferences? ,count?)
                                            (counts? ,count?)))))])])
         (sweep1 'record "sweep_record" `((parallel? ,parallel?)))
         (sweep1 'record "sweep_dirty_record" `((as-dirty? #t)
                                                (parallel? ,parallel?)))
         (sweep1 'symbol "sweep_symbol" `((parallel? ,parallel?)))
         (sweep1 'symbol "sweep_dirty_symbol" `((as-dirty? #t)
                                                (parallel? ,parallel?)))
         (sweep1 'thread "sweep_thread" `((no-from-g? #t)
                                          (parallel? ,parallel?)))
         (sweep1 'port "sweep_port" `((parallel? ,parallel?)))
         (sweep1 'port "sweep_dirty_port" `((as-dirty? #t)
                                            (parallel? ,parallel?)))
         (sweep1 'closure "sweep_continuation" `((code-relocated? ,(not parallel?))
                                                 (assume-continuation? #t)
                                                 (parallel? ,parallel?)))
         (sweep1 'code "sweep_code_object" `((parallel? ,parallel?))))
       (print-code (generate "size_object"
                             `((mode size))))
       (print-code (generate "mark_object"
                             `((mode mark)
                               (counts? ,count?)
                               (parallel? ,parallel?))))
       (print-code (generate "object_directly_refers_to_self"
                             `((mode self-test))))
       (print-code (code "static void mark_untyped_data_object(thread_gc *tgc, ptr p, uptr p_sz, seginfo *si)"
                         (code-block
                          (ensure-segment-mark-mask "si" "")
                          (mark-statement '(one-bit no-sweep)
                                          (cons
                                           (list 'used (make-eq-hashtable))
                                           '((basetype type-untyped)))))))
       (when measure?
         (print-code (generate "measure" `((mode measure))))))))

  (define (gen-heapcheck ofn)
    (guard
     (x [#t (raise x)])
     (parameterize ([current-output-port (open-output-file ofn 'replace)])
       (print-code (generate "check_object"
                             `((mode check))))
       (print-code (generate "size_object"
                             `((mode size)))))))

  ;; Render via mkequates to record a mapping from selectors to C
  ;; macros:
  (let-values ([(op get) (open-bytevector-output-port (native-transcoder))])
    (mkequates.h op))
  
  (set! mkgc-ocd.inc (lambda (ofn) (gen-gc ofn #f #f #f)))
  (set! mkgc-oce.inc (lambda (ofn) (gen-gc ofn #t #t #f))) ; not currently parallel (but could be "parallel" for ownership preservation)
  (set! mkgc-par.inc (lambda (ofn) (gen-gc ofn #f #f #t)))
  (set! mkheapcheck.inc (lambda (ofn) (gen-heapcheck ofn))))
