open! Import

module E = struct
  type t =
    { clz : int
    ; ctz : int
    }
  [@@deriving compare, sexp_of]
end

module type T = sig
  type t [@@deriving sexp_of]

  val one : t
  val ( lsl ) : t -> int -> t
  val clz : t -> int
  val ctz : t -> int
  val num_bits : int
end

module Make (Int : T) = struct
  let%expect_test "one-hot" =
    let clz_and_ctz int = { E.clz = Int.clz int; ctz = Int.ctz int } in
    for i = 0 to Int.num_bits - 1 do
      [%test_result: E.t]
        ~expect:{ E.clz = Int.num_bits - 1 - i; ctz = i }
        (clz_and_ctz Int.(one lsl i))
    done
  ;;
end

include Make (Nativeint)
include Make (Int63)
include Make (Int63.Private.Emul)

include Make (struct
  include Int

  let%expect_test "zero" =
    (* [clz 0] is guaranteed to be num_bits for int. We compute clz on the tagged
         representation of int's, and the binary representation of the int [0] is
         num_bits 0's followed by a 1 (the tag bit). *)
    [%test_result: int] ~expect:num_bits (clz 0)
  ;;

  (* [ctz 0] is unspecified. On linux it seems to be stable and equal to the system
       word size (which is num_bits + 1).
       ran 2019-02-11 on linux:
       {v
        [%test_result: int] ~expect:(num_bits + 1) (ctz 0)
       v}

       in javascript, it is 32 (which is num_bits):
       ran 2019-02-11 on javascript:
       {v
        [%test_result: int] ~expect:(num_bits) (ctz 0)
       v}
    *)
end)

include Make (struct
  include Int32

  let clz_and_ctz i32 = { E.clz = clz i32; ctz = ctz i32 }

  let%expect_test "extra examples" =
    [%test_result: E.t] ~expect:{ clz = 31; ctz = 0 } (clz_and_ctz 0b1l);
    [%test_result: E.t] ~expect:{ clz = 30; ctz = 1 } (clz_and_ctz 0b10l);
    [%test_result: E.t] ~expect:{ clz = 30; ctz = 0 } (clz_and_ctz 0b11l);
    [%test_result: E.t] ~expect:{ clz = 25; ctz = 1 } (clz_and_ctz 0b1000010l);
    [%test_result: E.t]
      ~expect:{ clz = 8; ctz = 6 }
      (clz_and_ctz 0b100000010000001001000000l);
    [%test_result: E.t]
      ~expect:{ clz = 0; ctz = 31 }
      (clz_and_ctz 0b10000000000000000000000000000000l);
    [%test_result: E.t]
      ~expect:{ clz = 9; ctz = 6 }
      (clz_and_ctz 0b00000000010000000100000001000000l);
    [%test_result: E.t]
      ~expect:{ clz = 0; ctz = 6 }
      (clz_and_ctz 0b10000000010000000100000001000000l)
  ;;
end)

include Make (struct
  include Int64

  let clz_and_ctz i64 = { E.clz = clz i64; ctz = ctz i64 }

  let%expect_test "extra examples" =
    [%test_result: E.t] ~expect:{ clz = 63; ctz = 0 } (clz_and_ctz 0b1L);
    [%test_result: E.t] ~expect:{ clz = 62; ctz = 1 } (clz_and_ctz 0b10L);
    [%test_result: E.t] ~expect:{ clz = 62; ctz = 0 } (clz_and_ctz 0b11L);
    [%test_result: E.t] ~expect:{ clz = 57; ctz = 1 } (clz_and_ctz 0b1000010L);
    [%test_result: E.t]
      ~expect:{ clz = 40; ctz = 6 }
      (clz_and_ctz 0b100000010000001001000000L);
    [%test_result: E.t]
      ~expect:{ clz = 0; ctz = 63 }
      (clz_and_ctz 0b1000000000000000000000000000000000000000000000000000000000000000L);
    [%test_result: E.t]
      ~expect:{ clz = 32; ctz = 31 }
      (clz_and_ctz 0b0000000000000000000000000000000010000000000000000000000000000000L);
    [%test_result: E.t]
      ~expect:{ clz = 32; ctz = 6 }
      (clz_and_ctz 0b0000000000000000000000000000000010000000010000000100000001000000L);
    [%test_result: E.t]
      ~expect:{ clz = 33; ctz = 6 }
      (clz_and_ctz 0b0000000000000000000000000000000001000000010000000100000001000000L)
  ;;
end)
