type instruction =
| Store of int * int64 | Load of int * int | Fence
| Move of int * int64 | Branch of int * int64 * int
| Enter | Leave | Exchange of int * int * int64
type goal = Overlap | Outcome of {
registers : (int * int * int64) list;
memory : (int * int64) list;
drained : bool;
}
type model = Sc | Tso
type location = int * int
type t = {
name : string;
names : string array;
bound : int;
threads : instruction array array;
candidates : location list;
goal : goal;
}
let model = function "sc" -> Sc | "tso" -> Tso | _ -> invalid_arg "unknown memory model"
let model_name = function Sc -> "sc" | Tso -> "tso"
let location value = match Sexp.list value with
| [t; pc] -> Sexp.integer t, Sexp.integer pc
| _ -> invalid_arg "invalid fence location"
let instruction value =
let open Sexp in
match value with
| Store (a, v) -> List [Atom "store"; i a; w v]
| Load (a, r) -> List [Atom "load"; i a; i r]
| Fence -> List [Atom "fence"]
| Move (r, v) -> List [Atom "move"; i r; w v]
| Branch (r, v, target) -> List [Atom "branch"; i r; w v; i target]
| Enter -> List [Atom "enter"]
| Leave -> List [Atom "leave"]
| Exchange (a, r, v) -> List [Atom "exchange"; i a; i r; w v]
let decode_instruction value =
let open Sexp in
match list value with
| [Atom "store"; a; v] -> Store (integer a, word v)
| [Atom "load"; a; r] -> Load (integer a, integer r)
| [Atom "fence"] -> Fence
| [Atom "move"; r; v] -> Move (integer r, word v)
| [Atom "branch"; r; v; target] -> Branch (integer r, word v, integer target)
| [Atom "enter"] -> Enter
| [Atom "leave"] -> Leave
| [Atom "exchange"; a; r; v] -> Exchange (integer a, integer r, word v)
| _ -> invalid_arg "invalid instruction"
let validate case =
let check condition message = if not condition then invalid_arg message in
check (Array.length case.threads = 2) "exactly two threads required";
check (Array.length case.names > 0 && Array.length case.names <= 16) "address count outside 1..16";
check (case.bound > 0 && case.bound <= 64) "step bound outside 1..64";
let address a = check (a >= 0 && a < Array.length case.names) "address outside model" in
let register r = check (r >= 0 && r < 4) "register outside model" in
Array.iter (fun program ->
check (Array.length program > 0 && Array.length program <= 64) "thread length outside 1..64";
Array.iter (function
| Store (a, _) -> address a
| Load (a, r) | Exchange (a, r, _) -> address a; register r
| Move (r, _) -> register r
| Branch (r, _, target) -> register r;
check (target >= 0 && target <= Array.length program) "branch target outside thread"
| Fence | Enter | Leave -> ()) program) case.threads;
let thread t = check (t >= 0 && t < 2) "thread outside model" in
List.iter (fun (t, pc) -> thread t;
check (pc >= 0 && pc < Array.length case.threads.(t)) "fence outside thread") case.candidates;
check (List.length case.candidates = List.length (List.sort_uniq compare case.candidates)) "duplicate fence";
match case.goal with
| Overlap -> ()
| Outcome goal ->
List.iter (fun (t, r, _) -> thread t; register r) goal.registers;
List.iter (fun (a, _) -> address a) goal.memory
let singleton = function [value] -> value | _ -> invalid_arg "expected one field value"
let fields allowed values =
List.iter (function
| Sexp.List (Sexp.Atom name :: _) when List.mem name allowed -> ()
| Sexp.List (Sexp.Atom name :: _) -> invalid_arg ("unknown field " ^ name)
| _ -> invalid_arg "malformed field") values
let decode value =
let open Sexp in
let entries = match value with
| List (Atom "case" :: fields) -> fields
| _ -> invalid_arg "expected case"
in
fields ["name"; "names"; "bound"; "threads"; "candidates"; "goal"] entries;
let get key = singleton (field key entries) in
let goal = match field "goal" entries with
| [Atom "overlap"] -> Overlap
| [List (Atom "outcome" :: entries)] ->
fields ["registers"; "memory"; "drained"] entries;
let registers = List.map (fun value -> match list value with
| [t; r; v] -> integer t, integer r, word v
| _ -> invalid_arg "invalid register goal") (field "registers" entries) in
let memory = List.map (fun value -> match list value with
| [a; v] -> integer a, word v
| _ -> invalid_arg "invalid memory goal") (field "memory" entries) in
let drained = match field "drained" entries with
| [Atom "true"] -> true | [Atom "false"] -> false
| _ -> invalid_arg "invalid drain flag" in
Outcome {registers; memory; drained}
| _ -> invalid_arg "invalid goal"
in
let case = {
name = atom (get "name");
names = Array.of_list (List.map atom (field "names" entries));
bound = integer (get "bound");
threads = Array.of_list (List.map (fun thread ->
Array.of_list (List.map decode_instruction (list thread))) (field "threads" entries));
candidates = List.map location (field "candidates" entries);
goal;
} in
validate case;
case
let encode case =
let open Sexp in
let goal = match case.goal with
| Overlap -> Atom "overlap"
| Outcome goal -> List [Atom "outcome";
List (Atom "registers" :: List.map (fun (t, r, v) -> List [i t; i r; w v]) goal.registers);
List (Atom "memory" :: List.map (fun (a, v) -> List [i a; w v]) goal.memory);
List [Atom "drained"; Atom (string_of_bool goal.drained)]] in
List [Atom "case"; List [Atom "name"; Atom case.name];
List (Atom "names" :: Array.to_list (Array.map a case.names));
List [Atom "bound"; i case.bound];
List (Atom "threads" :: Array.to_list (Array.map (fun thread ->
List (Array.to_list (Array.map instruction thread))) case.threads));
List (Atom "candidates" :: List.map (fun (t, pc) -> List [i t; i pc]) case.candidates);
List [Atom "goal"; goal]]
let load path = decode (Sexp.one (Store.read path))