; Bench fixture: closure-pair allocation under load.
;
; Each iteration of `run_loop` introduces a fresh `let-rec helper`
; whose body captures the outer fn-param `i`. The let-rec name is
; passed as a value to `apply_thrice`, which forces eta-Lam wrapping
; (the 16b.5 / 8b ABI shape) — codegen allocates a `{ thunk, env }`
; closure pair where `env` holds the captured `i`. Under
; --alloc=rc the allocation goes through `ailang_rc_alloc` and the
; per-type drop fn `build_pair_drop_fn` is generated for the
; closure-pair type. (The 18c.4 doubled-braces bug lived in exactly
; this drop fn; this fixture is the canonical exerciser.)
;
; The helper body is (body i) — it ignores its parameter and
; returns the capture. apply_thrice(helper, x) therefore evaluates
; to i regardless of x. Each run_loop iteration thus contributes
; `i` to the accumulator. Total = sum 0..n-1 = n*(n-1)/2.
;
; Sizes (Implicit-mode RC leaks the closure pairs by design — the
; gc/bump arms are the meaningful allocator comparison, the rc arm
; reports alloc-tax-only):
;   n =   10_000   ->   49_995_000   (1k closure-pair allocs)
;   n =  100_000   ->  4_999_950_000 (100k allocs)
;   n =  500_000   -> 124_999_750_000 (500k allocs)

(module bench_closure_chain

  (fn apply_thrice
    (doc "Higher-order: apply f three times to seed.")
    (type
      (fn-type
        (params (fn-type (params (con Int)) (ret (con Int)))
                (con Int))
        (ret (con Int))))
    (params f seed)
    (body
      (app f (app f (app f seed)))))

  (fn run_loop
    (doc "For each i in [n-1, n-2, ..., 0], build a closure capturing i, pass to apply_thrice, accumulate. Tail-recursive on i and acc.")
    (type
      (fn-type
        (params (con Int) (con Int))
        (ret (con Int))))
    (params i acc)
    (body
      (if (app < i 0)
        acc
        (let-rec helper
          (params x)
          (type (fn-type (params (con Int)) (ret (con Int))))
          (body i)
          (in
            (let r (app apply_thrice helper i)
              (tail-app run_loop (app - i 1) (app + acc r))))))))

  (fn run
    (doc "Drive run_loop from i=n-1 down to 0.")
    (type (fn-type (params (con Int)) (ret (con Int))))
    (params n)
    (body (app run_loop (app - n 1) 0)))

  (fn main
    (type (fn-type (params) (ret (con Unit)) (effects IO)))
    (params)
    (body
      (seq (do io/print_int (app run 10000))
      (seq (do io/print_int (app run 100000))
           (do io/print_int (app run 500000)))))))
