; Latency-distribution bench fixture — explicit-mode variant.
;
; Companion to bench_latency_implicit. Same algorithm, with
; `(borrow T)` / `(own T)` annotations so that under --alloc=rc the
; codegen emits proper inc/dec instrumentation: the persistent tree
; cache is borrowed (no inc/dec on pin_root's hot path), the per-op
; IntList is owned by sum_list (param drop at fn return frees the
; chain). The drop-iterative annotation on IntList keeps the per-op
; deallocation O(1)-stack regardless of list length.
;
; This is the "RC-fair" arm of the latency bench; together with
; bench_latency_implicit (under --alloc=gc) it tests the hypothesis
; "Boehm has unbounded p99 per-operation latency under continuous
; alloc pressure with a large persistent live working set; RC under
; explicit-mode has p99 within a small constant factor of the
; median".
;
; Workload (must match bench_latency_implicit's parameters exactly
; for the comparison to be fair):
;   - Live cache: balanced binary tree of depth 19 (524_287 nodes,
;     ~16 MB), borrowed throughout the loop.
;   - Per-op: build a CHUNK_LEN-cell IntList of 0..CHUNK_LEN-1, sum
;     it, drop it. CHUNK_LEN, NUM_OPS, PRINT_K hardcoded to match
;     the implicit variant.
;   - Same stdout shape: one READY (8888), N_PRINT timing markers
;     each containing the per-op sum (validatable, always equal),
;     one DONE (9999).

(module bench_latency_explicit

  (data Tree
    (doc "Balanced binary tree, 32-byte cells. Borrowed across the bench loop; tree-depth recursion at scope close is bounded (depth 19) so no drop-iterative needed.")
    (ctor TLeaf)
    (ctor TNode (con Int) (con Tree) (con Tree)))

  (data IntList
    (doc "Singly-linked Int list. Per-op chains are CHUNK_LEN long; (drop-iterative) keeps Own-param drop O(1) stack regardless of length.")
    (ctor LNil)
    (ctor LCons (con Int) (con IntList))
    (drop-iterative))

  ; ---------- Live cache: balanced tree of given depth ----------

  (fn build_tree
    (doc "Build a balanced tree of given depth, every value = 1. Returns owned Tree; main holds it across the loop and the final drop fires at main's scope close.")
    (type
      (fn-type
        (params (con Int))
        (ret (own (con Tree)))))
    (params depth)
    (body
      (if (app == depth 0)
        (term-ctor Tree TLeaf)
        (term-ctor Tree TNode
          1
          (app build_tree (app - depth 1))
          (app build_tree (app - depth 1))))))

  (fn pin_root
    (doc "Constant-time tree liveness pin — read root tag, return 1 (TNode) or 0 (TLeaf). Borrows t so the persistent cache is not inc/dec'd on every op.")
    (type
      (fn-type
        (params (borrow (con Tree)))
        (ret (con Int))))
    (params t)
    (body
      (match t
        (case (pat-ctor TLeaf) 0)
        (case (pat-ctor TNode v l r) 1))))

  ; ---------- Per-op work: build/sum an N-cell list ----------

  (fn cons_n_acc
    (doc "Tail-recursive list builder. Returns owned chain; caller is sum_list which owns and drops it.")
    (type
      (fn-type
        (params (con Int) (own (con IntList)))
        (ret (own (con IntList)))))
    (params n acc)
    (body
      (if (app == n 0)
        acc
        (tail-app cons_n_acc
          (app - n 1)
          (term-ctor IntList LCons (app - n 1) acc)))))

  (fn cons_n
    (doc "Build [0,1,...,n-1] :: IntList. Returns owned chain.")
    (type
      (fn-type
        (params (con Int))
        (ret (own (con IntList)))))
    (params n)
    (body
      (app cons_n_acc n (term-ctor IntList LNil))))

  (fn sum_list_acc
    (doc "Tail-recursive sum. Owns xs; consumes it via the LCons arm's t binder (move-into-tail-call).")
    (type
      (fn-type
        (params (own (con IntList)) (con Int))
        (ret (con Int))))
    (params xs acc)
    (body
      (match xs
        (case (pat-ctor LNil) acc)
        (case (pat-ctor LCons h t)
          (tail-app sum_list_acc t (app + acc h))))))

  (fn sum_list
    (doc "Sum every element. Owns xs, hands it to sum_list_acc which consumes it.")
    (type
      (fn-type
        (params (own (con IntList)))
        (ret (con Int))))
    (params xs)
    (body
      (app sum_list_acc xs 0)))

  (fn one_op
    (doc "One bench operation: build+sum a fresh CHUNK_LEN-cell list, pin the tree's root, return their sum so the value chain stays observable. Tree is borrowed; no inc/dec on the hot path against the persistent cache.")
    (type
      (fn-type
        (params (con Int) (borrow (con Tree)))
        (ret (con Int))))
    (params chunk_len t)
    (body
      (app + (app sum_list (app cons_n chunk_len)) (app pin_root t))))

  ; ---------- Bench loop ----------

  (fn loop
    (doc "Tail-recursive bench loop. Tree is borrowed across all iterations.")
    (type
      (fn-type
        (params (con Int) (con Int) (con Int) (con Int) (borrow (con Tree)))
        (ret (con Unit))
        (effects IO)))
    (params remaining print_countdown chunk_len print_k t)
    (body
      (if (app == remaining 0)
        (do io/print_int 9999)
        (if (app == print_countdown 0)
          (seq
            (do io/print_int (app one_op chunk_len t))
            (tail-app loop
              (app - remaining 1)
              (app - print_k 1)
              chunk_len
              print_k
              t))
          (let _v (app one_op chunk_len t)
            (tail-app loop
              (app - remaining 1)
              (app - print_countdown 1)
              chunk_len
              print_k
              t))))))

  (fn main
    (doc "Top-level: build tree, signal READY (8888), run loop, signal DONE (9999 emitted by loop).")
    (type (fn-type (params) (ret (con Unit)) (effects IO)))
    (params)
    (body
      (let t (app build_tree 19)
        (let _root (app pin_root t)
          (seq
            (do io/print_int 8888)
            (app loop 20000 0 500 20 t)))))))
