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

(* Memory model test:
 *   Check that all basic forbidden tests are not observed
 *)


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

(********************************************************************)
(* Shared memory, locations x and y of type int ref or int Atomic.t *)
(********************************************************************)

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

module IntAto = struct
  type t = int Atomic.t
  let empty _ = Atomic.make (-1)
  let reinit r = Atomic.set r 0
end


module EnvPP = Shared.Make(IntRef)(IntRef)
module EnvPA = Shared.Make(IntRef)(IntAto)
module EnvAP = Shared.Make(IntAto)(IntRef)
module EnvAA = Shared.Make(IntAto)(IntAto)

(* Outcome make of two integers *)
module OK = Outcome.OK
module NO = Outcome.NO

module IntT = Outcome.Int

module Outcome = Outcome.Make(IntT)(IntT)


(* Store buffer *)

module SB = struct

  module MakeKey(N:sig val name : string end) =
    Outcome
      (struct
        let name = N.name
        let tag0 = "r0"
        let tag1 = "r1"
        let ok r0 r1 = r0=0 && r1=0
      end)

  module PP = struct

    module Key = MakeKey(struct let name = "SB" end)(OK)

    module Env = EnvPP

    type out0 = int

    let code0 (x,y) =
      x  := 1 ;
      !y

    type out1 = int

    let code1 (x,y) =
      y := 1 ;
      !x

    let out2key _ r0 r1 = Key.make r0 r1

  end

  module PA = struct

    module Key = MakeKey(struct let name = "SB+PA" end)(OK)

    module Env = EnvPA

    type out0 = int

    let code0 (x,y) =
      x  := 1 ;
      Atomic.get y

    type out1 = int

    let code1 (x,y) =
      Atomic.set y 1 ;
      !x

    let out2key _ r0 r1 = Key.make r0 r1

  end

  module AA = struct

    module Key = MakeKey(struct let name = "SB+AA" end)(NO)

    module Env = EnvAA

    type out0 = int

    let code0 (x,y) =
      Atomic.set x  1 ;
      Atomic.get y

    type out1 = int

    let code1 (x,y) =
      Atomic.set y 1 ;
      Atomic.get x

    let out2key _ r0 r1 = Key.make r0 r1

  end
end

module R = struct

  module MakeKey(N:sig val name : string end) =
    Outcome
      (struct
        let name = N.name
        let tag0 = "y"
        let tag1 = "r0"
        let ok y r0 = y = 2 && r0 = 0
      end)

  module PP = struct

    module Key = MakeKey(struct let name = "R" end)(OK)

    module Env = EnvPP

    type out0 = unit

    let code0 (x,y) =
      x := 1 ;
      y := 1 ;
      ()

    type out1 = int

    let code1 (x,y) =
      y := 2 ;
      !x

    let out2key (x,y) () r1 = Key.make !y r1

  end

  module PA = struct

    module Key = MakeKey(struct let name = "R+PA" end)(NO)

    module Env = EnvPA

    type out0 = unit

    let code0 (x,y) =
      x := 1 ;
      Atomic.set y 1 ;
      ()

    type out1 = int

    let code1 (x,y) =
      Atomic.set y 2 ;
    !x

    let out2key (x,y) () r1 = Key.make (Atomic.get y) r1

  end

  module AP = struct

    module Key = MakeKey(struct let name = "R+AP" end)(OK)

    module Env = EnvAP

    type out0 = unit

    let code0 (x,y) =
      Atomic.set x 1 ;
      y := 1 ;
      ()

    type out1 = int

    let code1 (x,y) =
      y := 2 ;
      Atomic.get x

    let out2key (x,y) () r1 = Key.make !y r1

  end

  module AA = struct

    module Key = MakeKey(struct let name = "R+AA" end)(NO)

    module Env = EnvAA

    type out0 = unit

    let code0 (x,y) =
      Atomic.set x 1 ;
      Atomic.set y 1 ;
      ()

    type out1 = int

    let code1 (x,y) =
      Atomic.set y 2 ;
      Atomic.get x

    let out2key (x,y) () r1 = Key.make (Atomic.get y) r1

  end

end

module MP = struct

  module MakeKey(N:sig val name : string end) =
    Outcome
      (struct
        let name = N.name
        let tag0 = "r0"
        let tag1 = "r1"
        let ok r0 r1 = r0=1 && r1=0
      end)


  module PP = struct

    module Key = MakeKey(struct let name = "MP" end)(OK)

    module Env = EnvPP

    type out0 = unit

    let code0 (x,y) =
      x := 1 ;
      y := 1

    type out1 = { t0:int; t1:int; }

    let code1 (x,y) =
      let r0 = !y in
      let r1 = !x in
      {t0=r0; t1=r1;}

    let out2key _ () { t0; t1; } = Key.make t0 t1

  end

  module PA = struct

    module Key = MakeKey(struct let name = "MP+PA" end)(NO)

    module Env = EnvPA

    type out0 = unit

    let code0 (x,y) =
      x := 1 ;
      Atomic.set y 1

    type out1 = { t0:int; t1:int; }

    let code1 (x,y) =
      let r0 = Atomic.get y in
      let r1 = !x in
      {t0=r0; t1=r1;}

    let out2key _ () { t0; t1; } = Key.make t0 t1

  end


  module PAPAP = struct
    (* This (forbidden) test is analog to MP+PA above.
     * In fact, it includes PA which tests the following,
     * forbidden "Message passing" execution:
     *
     *       +-------Fr------------+
     *       |                     |
     *      \/                     |
     *     W[x]1    +----> RA[y]1  |
     *       |      |         |    |
     *       |      Rf        |    |
     *      \/      |        \/    |
     *      WA[y]1--+       R[x]0--+
     *
     * However, when one adds a non atomic read `!x` _before_
     * the atomic read `Atomic.get y`, and if the native compiler
     * CSE changes the second  non atomic read `!x` into a
     * temporary (register) read, then the function code1
     * now behaves as:
     *   let r2 = !x in
     *   let r0 = Atomic_get y in
     *   let r1 = r2 in ...
     *
     * In effect, code1 now behaves as if the second non-atomic
     * read !x has moved _before_ the atomic read of `y`, which
     * the model forbids.
     *)
    module Key = MakeKey(struct let name = "MP+PA+PAP" end)(NO)

    module Env = EnvPA

    type out0 = unit

    let code0 (x,y) =
      x := 1 ;
      Atomic.set y 1

    type out1 = { t0:int; t1:int; }

    let code1 (x,y) =
      let r2 = !x in
      let r0 =  Atomic.get y in
      let r1 = !x in
      {t0=r0; t1=r1+r2;}

    let out2key _ () { t0; t1; } = Key.make t0 t1

  end
  module PFetch = struct

    module Key = MakeKey(struct let name = "MP+PFetch" end)(NO)

    module Env = EnvPA

    type out0 = unit

    let code0 (x,y) =
      x := 1 ;
      ignore (Atomic.fetch_and_add y 1)

    type out1 = { t0:int; t1:int; }

    let code1 (x,y) =
      let r0 = Atomic.get y in
      let r1 = !x in
      {t0=r0; t1=r1;}

    let out2key _ () { t0; t1; } = Key.make t0 t1

  end

  module PCas = struct

    module Key = MakeKey(struct let name = "MP+PCas" end)(NO)

    module Env = EnvPA

    type out0 = unit

    let code0 (x,y) =
      x := 1 ;
      while not (Atomic.compare_and_set y 0 1) do () done

    type out1 = { t0:int; t1:int; }

    let code1 (x,y) =
      let r0 = Atomic.get y in
      let r1 = !x in
      {t0=r0; t1=r1;}

    let out2key (_,y) () { t0; t1; } =  assert (Atomic.get y = 1) ; Key.make t0 t1

  end

  module AP = struct

    module Key = MakeKey(struct let name = "MP+AP" end)(NO)

    module Env = EnvAP

    type in_t = int Atomic.t * int ref

    type out0 = unit

    let code0 (x,y) =
      Atomic.set x 1 ;
      y := 1

    type out1 = { t0:int; t1:int; }

    let code1 (x,y) =
      let r0 = !y in
      let r1 = Atomic.get x in
      {t0=r0; t1=r1;}

    let out2key _ () { t0; t1; } = Key.make t0 t1

  end

  module FetchP = struct

    module Key = MakeKey(struct let name = "MP+FetchP" end)(NO)

    module Env = EnvAP

    type in_t = int Atomic.t * int ref

    type out0 = unit

    let code0 (x,y) =
      ignore (Atomic.fetch_and_add x 1) ;
      y := 1

    type out1 = { t0:int; t1:int; }

    let code1 (x,y) =
      let r0 = !y in
      let r1 = Atomic.get x in
      {t0=r0; t1=r1;}

    let out2key (x,_) () { t0; t1; } = Key.make t0 t1

  end

  module CasP = struct

    module Key = MakeKey(struct let name = "MP+CasP" end)(NO)

    module Env = EnvAP

    type in_t = int Atomic.t * int ref

    type out0 = unit

    let code0 (x,y) =
      while not (Atomic.compare_and_set x 0 1) do () done ;
      y := 1

    type out1 = { t0:int; t1:int; }

    let code1 (x,y) =
      let r0 = !y in
      let r1 = Atomic.get x in
      {t0=r0; t1=r1;}

    let out2key (x,_) () { t0; t1; } = assert (Atomic.get x = 1) ; Key.make t0 t1

  end


  module AA = struct

    module Key = MakeKey(struct let name = "MP+AA" end)(NO)

    module Env = EnvAA

    type out0 = unit

    let code0 (x,y) =
      Atomic.set x  1 ;
      Atomic.set y  1 ;
      ()

    type out1 = { t0:int; t1:int; }

    let code1 (x,y) =
      let r0 = Atomic.get y in
      let r1 = Atomic.get x in
      {t0=r0; t1=r1;}

    let out2key _ () { t0; t1; } = Key.make t0 t1

  end
end

module LB = struct
  module Key =
    Outcome
      (struct
        let name = "LB"
        let tag0 = "r0"
        let tag1 = "r1"
        let ok r0 r1 = r0=1 && r1=1
      end)(NO)

  module Env = EnvPP

  type out0 = int

  let code0 (x,y) =
    let r0 = !x in
    y := 1 ;
    r0

  type out1 = int

  let code1 (x,y) =
    let r = !y in
    x := 1 ;
    r

  let out2key _ r0 r1 = Key.make r0 r1

end

module W2 = struct

  module MakeKey(N:sig val name : string end) =
    Outcome
      (struct
        let name = N.name
        let tag0 = "x"
        let tag1 = "y"
        let ok x y = x=2 && y=2
      end)

  module PP = struct

    module Key = MakeKey(struct let name = "2+2W" end)(OK)

    module Env = EnvPP

    open Env

    type out0 = unit

    let code0 (x,y) =
      x := 2 ;
      y := 1

    type out1 = unit

    let code1 (x,y) =
      y := 2 ;
      x := 1

    let out2key (x,y) _ _ = Key.make !x !y

  end

  module PA = struct

    module Key = MakeKey(struct let name = "2+2W+PA" end)(NO)

    module Env = EnvPA

    open Env

    type out0 = unit

    let code0 (x,y) =
      x := 2 ;
      Atomic.set y 1

    type out1 = unit

    let code1 (x,y) =
      Atomic.set y 2 ;
      x := 1

    let out2key (x,y) _ _ = Key.make !x (Atomic.get y)

  end

  module AA = struct

    module Key = MakeKey(struct let name = "2+2W+AA" end)(NO)

    module Env = EnvAA

    open Env

    type out0 = unit

    let code0 (x,y) =
      Atomic.set x 2 ;
      Atomic.set y 1

    type out1 = unit

    let code1 (x,y) =
      Atomic.set y 2 ;
      Atomic.set x 1

    let out2key (x,y) _ _ = Key.make (Atomic.get x) (Atomic.get y)

  end

end

module S = struct

  module MakeKey(N:sig val name : string end) =
    Outcome
      (struct
        let name = N.name
        let tag0 = "x"
        let tag1 = "r0"
        let ok x r0 = x=2 && r0=1
      end)

  module PP = struct

    module Key = MakeKey(struct let name = "S" end)(OK)

    module Env = EnvPP

    type out0 = unit

    let code0 (x,y) =
      x := 2 ;
      y := 1 ;
      ()

    type out1 = int

    let code1 (x,y) =
      let r0 = !y in
      x := 1 ;
      r0

    let out2key (x,_) () r0 = Key.make !x r0

  end

  module PA = struct

    module Key = MakeKey(struct let name = "S+PA" end)(NO)

    module Env = EnvPA

    type out0 = unit

    let code0 (x,y) =
      x := 2 ;
      Atomic.set y 1 ;
      ()

    type out1 = int

    let code1 (x,y) =
      let r0 = Atomic.get y in
      x := 1 ;
      r0

    let out2key (x,_) () r0 = Key.make !x r0

  end

  module AP = struct

    module Key = MakeKey(struct let name = "S+AP" end)(NO)

    module Env = EnvAP

    type out0 = unit

    let code0 (x,y) =
      Atomic.set x 2 ;
      y := 1 ;
      ()

    type out1 = int

    let code1 (x,y) =
      let r0 = !y in
      Atomic.set x 1 ;
      r0

    let out2key (x,_) () r0 = Key.make (Atomic.get x) r0

  end

  module AA = struct

    module Key = MakeKey(struct let name = "S+AA" end)(NO)

    module Env = EnvAA

    type out0 = unit

    let code0 (x,y) =
      Atomic.set x 2 ;
      Atomic.set y 1 ;
      ()

    type out1 = int

    let code1 (x,y) =
      let r0 = Atomic.get y in
      Atomic.set x 1 ;
      r0

    let out2key (x,_) () r0 = Key.make (Atomic.get x) r0

  end


end

module Forbid(C:Opt.Config) = struct
  module Run = Run.Make(C)
  module T1 = Run(SB.AA)
  let () = T1.zyva()
  module T2 = Run(R.PA)
  let () = T2.zyva()
  module T3 = Run(R.AA)
  let () = T3.zyva()
  module T4 = Run(MP.PA)
  let () = T4.zyva()
  module T40 = Run(MP.PFetch)
  let () = T40.zyva()
  module T41 = Run(MP.PCas)
  let () = T41.zyva()
  module T5 = Run(MP.AP)
  let () = T5.zyva()
  module T50 = Run(MP.FetchP)
  let () = T50.zyva()
  module T51 = Run(MP.CasP)
  let () = T51.zyva()
  module T6 = Run(MP.AA)
  let () = T6.zyva()
  module T7 = Run(LB)
  let () = T7.zyva()
  module T8 = Run(W2.PA)
  let () = T8.zyva()
  module T9 = Run(W2.AA)
  let () = T9.zyva()
  module TA = Run(S.PA)
  let () = TA.zyva()
  module TB = Run(S.AP)
  let () = TB.zyva()
  module TC = Run(S.AA)
  let () = TC.zyva()
  module TD = Run(MP.PAPAP)
  let () = TD.zyva()
end

module Allow(C:Opt.Config) = struct
  module Run = Run.Make(C)
  module T1 = Run(SB.PP)
  let () = T1.zyva()
  module T2 = Run(SB.PA)
  let () = T2.zyva()
  module T3 = Run(R.PP)
  let () = T3.zyva()
  module T4 = Run(R.AP)
  let () = T4.zyva()
  module T5 = Run(MP.PP)
  let () = T5.zyva()
  module T6 = Run(W2.PP)
  let () = T6.zyva()
  module T7 = Run(S.PP)
  let () = T7.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
