(* TEST
 modules = "opt.ml barrier.ml hist.ml shared.ml run.ml outcome.ml";
 multicore;
 no-tsan; (* tsan detects data races and fails *)
 not-bsd;
 {
   not-windows;
   bytecode;
 }{
   native;
 }
*)

(* Memory model: test the _publish idiom *)

module O = Opt.Make(struct let pgm = "forbidden"end)
module Config = O.Config
let verbose = Config.verbose

module OK = Outcome.OK
module NO = Outcome.NO

(* Types of shared memory locations *)

module IntRef = struct
  let null = ref (-1)
  type t = int ref
  let empty _ = ref 0
  let reinit r = r := 0
end

module IntRefRef = struct
  type t = int ref ref
  let empty _ = ref (IntRef.null)
  let reinit r = r := IntRef.null
end

module OutPointer = struct
  type t = bool
  let compare = Bool.compare
  let pp = function
    | true -> "null"
    | false -> "p"
end

(* Publication tests *)

module Publish = struct

(* Publication of int ref: the initial value of published
 *  pointer must be the one reader gets by derreferencing pointer:
 *    T0        |       T1
 *  ------------+-------------
 *  q := ref 1  | let p = !q in
 *              | let v = !p in
 *              | ..
 * If p is the fresh reference allocated by T0, then v must be 1.
 *)

  module IntRef = struct

    module O0 = OutPointer

    module O1 = Outcome.Int

    module Key =
      Outcome.Make(O0)(O1)
        (struct
          let name = "Publish+intref"
          let tag0 = "q"
          let tag1 = "r0"
          let ok p r0 = not p && r0 <> 1
        end)(NO)

    module Env = Shared.One(IntRefRef)

    (* publish *)
    type out0 = unit
    let code0 rr = rr := ref 1

    (* Dereference twice *)
    type out1 = bool * int
    let code1 rr =
      let r = !rr in
      let v = !r in
      r == IntRef.null,v

    let out2key _ () (p,v) = Key.make p v

  end

  (* Publication of string reference, with subsequent updates.
   * Notice that if publication is not properly protected,
   * the test may crash by attempting to print junk *)
  module String = struct

    let null = "*null*"
    module StringRef = struct
      type t =  string ref
      let empty _ = ref null
      let reinit r = r := null
    end

    let sz = 8
    let updates = List.init sz (Printf.sprintf "%d")
    module StringSet = Set.Make(String)


    let valid =
      let set = StringSet.of_list (null::updates) in
      fun s -> StringSet.mem s set

    module Key = struct

      type t = string

      let compare = String.compare

      let ok s = not (valid s)

      let allowed = false

      let name = "Publish+string"

      let pp chan s = Printf.fprintf chan "r0=%s" s

    end

    module Env = Shared.One(StringRef)

    (* publish *)
    type out0 = unit
    let code0 q =
      q := Printf.sprintf "%d" 0 ; (* publish *)
      List.init (sz-1) (fun k -> k+1)
      |>
        List.iter
          (fun v ->
            q := Printf.sprintf "%d" v)

    (* Dereference twice *)
    type out1 = string
    let code1 q = Domain.cpu_relax () ; !q

    let out2key _ () v = v

  end
end

(* One _allowed_ publication tests:
   array index is published, not a reference *)

module MPaddr = struct
  module Key =
    Outcome.Make(Outcome.Int)(Outcome.Int)
      (struct
        let name = "MP+addr"
        let tag0 = "idx"
        let tag1 = "v"
        let ok idx v = idx=1 && v=0
      end)(OK)

  module Env =
    Shared.Make
      (struct
        type t = int array
        let empty _ = Array.make 1 0
        let reinit t = t.(0) <- 0
      end)(IntRef)

  type out0 = unit
  let code0 (t,r) = (* publish int... *)
    t.(0) <- 1 ;
    r := 1

  type out1 = int * int
  let code1 (t,r) = (* Address dependency *)
    let idx = !r in
    let i = idx land 128 in
    let v = t.(i) in
    idx,v

  let out2key _ () (idx,v) = Key.make idx v
end

module Forbid(C:Opt.Config) = struct
  module Run = Run.Make(C)
  module T1 = Run(Publish.IntRef)
  let () = T1.zyva()
  module T2 = Run(Publish.String)
  let () = T2.zyva()
end

module Allow(C:Opt.Config) = struct
  module Run = Run.Make(C)
  module T1 = Run(MPaddr)
  let () = T1.zyva()
end

let () =
  if not verbose then begin
    let module Twice(C:Opt.Config) =
      struct
        module _ = Forbid(C)
        module _ =
          Forbid
            (struct
              let verbose = C.verbose
              let size = C.size / 10
              let nruns = C.nruns * 10
              let navail = C.navail
            end)
      end in
      let module _ = Twice(Config) in ()
  end else begin
    let module _ = Forbid(Config) in
    let module _ = Allow(Config) in
    ()
  end
