type value = Bits of int64 | Truth of bool type answer = Sat of value list | Unsat | Unknown of string type query = { source : string; probes : (string * string) list } let strategy = "(check-sat-using (then simplify solve-eqs bit-blast sat))" let bits = function Bits value -> value | _ -> invalid_arg "expected bitvector" let truth = function Truth value -> value | _ -> invalid_arg "expected boolean" let index value = Printf.sprintf "#x%02x" value let word value = Printf.sprintf "#x%016Lx" value let all = function [] -> "true" | values -> "(and " ^ String.concat " " values ^ ")" let any = function [] -> "false" | values -> "(or " ^ String.concat " " values ^ ")" let equal_state left right = all (List.map (fun field -> Printf.sprintf "(= (%s %s) (%s %s))" field left field right) ["memory"; "registers"; "counters"; "heads"; "tails"; "addresses"; "values"; "positions"; "critical"]) let command source = if String.starts_with ~prefix:"(set-logic QF_BV)" source then strategy else "(check-sat)" let connection ~timeout ~tick action = let input_read, input_write = Unix.pipe ~cloexec:true () in let output_read, output_write = try Unix.pipe ~cloexec:true () with error -> Unix.close input_read; Unix.close input_write; raise error in let executable = Option.value (Sys.getenv_opt "Z3") ~default:"z3" in let pid = try Unix.create_process executable [|executable; "-in"; "-smt2"|] input_read output_write output_write with error -> List.iter Unix.close [input_read; input_write; output_read; output_write]; raise error in Unix.close input_read; Unix.close output_write; let input = Unix.out_channel_of_descr input_write in let pending = ref "" in let deadline = Unix.gettimeofday () +. float_of_int timeout /. 1000. +. 30. in let chunk = Bytes.create 8192 in let read () = if Unix.gettimeofday () > deadline then failwith "solver transport timed out"; tick (); let ready, _, _ = Unix.select [output_read] [] [] 0.1 in if ready = [] then None else let size = Unix.read output_read chunk 0 (Bytes.length chunk) in if size = 0 then Some "" else Some (Bytes.sub_string chunk 0 size) in let rec line () = match String.index_opt !pending '\n' with | Some ending -> let result = String.sub !pending 0 ending in pending := String.sub !pending (ending + 1) (String.length !pending - ending - 1); result | None -> match read () with | None -> line () | Some "" -> failwith ("solver closed its output: " ^ !pending) | Some text -> pending := !pending ^ text; line () in let finish () = let buffer = Buffer.create 4096 in Buffer.add_string buffer !pending; pending := ""; let rec loop () = match read () with | None -> loop () | Some "" -> Buffer.contents buffer | Some text -> Buffer.add_string buffer text; loop () in loop () in let reaped = ref false in Fun.protect ~finally:(fun () -> close_out_noerr input; Unix.close output_read; if not !reaped then begin (try Unix.kill pid Sys.sigkill with Unix.Unix_error (Unix.ESRCH, _, _) -> ()); ignore (Unix.waitpid [] pid) end) (fun () -> let result = action input line finish in let _, status = Unix.waitpid [] pid in reaped := true; if status <> Unix.WEXITED 0 then failwith "solver process failed"; result) let script ?(timeout=300000) ?(tick=fun () -> ()) source = connection ~timeout ~tick (fun input _ finish -> output_string input (source ^ "\n(exit)\n"); flush input; finish ()) let solve ?(timeout=300000) ?(tick=fun () -> ()) query = connection ~timeout ~tick (fun input line finish -> output_string input query.source; List.iteri (fun i (kind, term) -> Printf.fprintf input "(define-fun q%d () %s %s)\n" i kind term) query.probes; output_string input (command query.source ^ "\n"); flush input; let status = String.trim (line ()) in begin match status with | "sat" when query.probes <> [] -> let names = List.init (List.length query.probes) (Printf.sprintf "q%d") in Printf.fprintf input "(get-value (%s))\n" (String.concat " " names) | "unknown" -> output_string input "(get-info :reason-unknown)\n" | "sat" | "unsat" -> () | _ -> failwith ("invalid solver response: " ^ status) end; output_string input "(exit)\n"; flush input; let response = finish () in match status with | "unsat" -> Unsat | "unknown" -> Unknown response | _ when query.probes = [] -> Sat [] | _ -> let entries = Sexp.list (Sexp.one response) in let table = Hashtbl.create (List.length entries) in List.iter (function | Sexp.List [Sexp.Atom name; value] -> let value = match value with | Sexp.Atom "true" -> Truth true | Sexp.Atom "false" -> Truth false | value -> Bits (Sexp.word value) in Hashtbl.add table name value | _ -> failwith "invalid model entry") entries; Sat (List.init (List.length query.probes) (fun i -> Hashtbl.find table (Printf.sprintf "q%d" i)))) let instruction state t = let open Syntax in let call name values = Printf.sprintf "(%s-step %s %s%s)" name state (index t) (if values = [] then "" else " " ^ String.concat " " values) in function | Store (a, v) -> [Printf.sprintf "(bvult (pending %s %s) #x08)" state (index t)], (fun sc -> call "store" [index a; word v; string_of_bool sc]) | Load (a, r) -> [], (fun _ -> call "load" [index a; index r]) | Fence -> [Printf.sprintf "(= (pending %s %s) #x00)" state (index t)], (fun _ -> call "fence" []) | Move (r, v) -> [], (fun _ -> call "move" [index r; word v]) | Branch (r, v, pc) -> [], (fun _ -> call "branch" [index r; word v; index pc]) | Enter -> [], (fun _ -> call "enter" []) | Leave -> [], (fun _ -> call "leave" []) | Exchange (a, r, v) -> [Printf.sprintf "(= (pending %s %s) #x00)" state (index t)], (fun _ -> call "exchange" [index a; index r; word v]) let property (case : Syntax.t) state = match case.goal with | Syntax.Overlap -> Printf.sprintf "(overlap %s)" state | Syntax.Outcome goal -> let finished = List.init 2 (fun t -> Printf.sprintf "(= (pc %s %s) %s)" state (index t) (index (Array.length case.threads.(t)))) in let registers = List.map (fun (t, r, v) -> Printf.sprintf "(= (reg %s %s %s) %s)" state (index t) (index r) (word v)) goal.registers in let memory = List.map (fun (a, v) -> Printf.sprintf "(= (at (memory %s) %s) %s)" state (index a) (word v)) goal.memory in let drained = if not goal.drained then [] else List.init 2 (fun t -> Printf.sprintf "(= (pending %s %s) #x00)" state (index t)) in all (finished @ registers @ memory @ drained) let observations (case : Syntax.t) state = List.init 2 (fun t -> "Index", Printf.sprintf "(pc %s %s)" state (index t)) @ List.init 8 (fun i -> "Word", Printf.sprintf "(reg %s %s %s)" state (index (i / 4)) (index (i mod 4))) @ List.init (Array.length case.names) (fun a -> "Word", Printf.sprintf "(at (memory %s) %s)" state (index a)) @ List.init 2 (fun t -> "Index", Printf.sprintf "(pending %s %s)" state (index t)) @ List.init 2 (fun t -> "Index", Printf.sprintf "(at (critical %s) %s)" state (index t)) let compile ?(timeout=300000) ?fixed ?(require_goal=true) ?(blocks=[]) case model fences bound = Syntax.validate case; if bound < 1 || bound > 64 then invalid_arg "step bound outside 1..64"; List.iter (fun (t, pc) -> if t < 0 || t > 1 || pc < 0 || pc >= Array.length case.Syntax.threads.(t) then invalid_arg "invalid fence location") fences; Option.iter (fun actors -> if Array.length actors <> bound || Array.exists (fun a -> a < 0 || a > 4) actors then invalid_arg "invalid fixed schedule") fixed; let buffer = Buffer.create 65536 in Buffer.add_string buffer (Layout.generate ~timeout); Buffer.add_string buffer (Store.read (Filename.concat (Store.root ()) "smt/machine.smt2")); let add line = Buffer.add_string buffer (line ^ "\n") in add ("(declare-const s0 State)\n(assert " ^ equal_state "s0" "initial" ^ ")"); for step = 0 to bound - 1 do let state = Printf.sprintf "s%d" step in let next = Printf.sprintf "s%d" (step + 1) in let actor = Printf.sprintf "a%d" step in add (Printf.sprintf "(declare-const %s State)\n(declare-const %s Index)" next actor); let alternatives = List.concat (List.init 2 (fun t -> let instructions = Array.to_list (Array.mapi (fun pc ins -> let guards, call = instruction state t ins in let guards = if List.mem (t, pc) fences then Printf.sprintf "(= (pending %s %s) #x00)" state (index t) :: guards else guards in all (Printf.sprintf "(= (pc %s %s) %s)" state (index t) (index pc) :: equal_state next (call (model = Syntax.Sc)) :: guards)) case.threads.(t)) in [all [Printf.sprintf "(= %s %s)" actor (index t); any instructions]; all [Printf.sprintf "(= %s %s)" actor (index (t + 2)); Printf.sprintf "(bvugt (pending %s %s) #x00)" state (index t); equal_state next (Printf.sprintf "(drain %s %s)" state (index t))]])) in add ("(assert " ^ any (alternatives @ [all ["(= " ^ actor ^ " #x04)"; equal_state next state]]) ^ ")"); if step > 0 && fixed = None then add (Printf.sprintf "(assert (=> (= %s #x04) (= a%d #x04)))" actor (step - 1)); Option.iter (fun actors -> add (Printf.sprintf "(assert (= %s %s))" actor (index actors.(step)))) fixed done; let final = Printf.sprintf "s%d" bound in if require_goal then add ("(assert " ^ property case final ^ ")"); List.iter (fun (left, right) -> add ("(assert " ^ any [ Printf.sprintf "(distinct (reg %s #x00 #x00) %s)" final (word left); Printf.sprintf "(distinct (reg %s #x01 #x00) %s)" final (word right)] ^ ")")) blocks; let probes = List.init bound (fun i -> "Index", Printf.sprintf "a%d" i) @ List.concat (List.init (bound + 1) (fun i -> observations case (Printf.sprintf "s%d" i))) in {source = Buffer.contents buffer; probes} let candidate ~count ~budget clauses = if count < 0 then invalid_arg "negative fence candidate count"; List.iter (List.iter (fun index -> if index < 0 || index >= count then invalid_arg "fence constraint index outside candidates")) clauses; let lines = ref [Store.read (Filename.concat (Store.root ()) "smt/fences.smt2")] in let add line = lines := line :: !lines in for i = 0 to count - 1 do add (Printf.sprintf "(declare-const f%d Bool)" i) done; let weights = List.init count (Printf.sprintf "(weight f%d)") in add (Printf.sprintf "(assert (<= (+ 0 %s) %d))" (String.concat " " weights) budget); List.iter (fun clause -> add ("(assert " ^ any (List.map (Printf.sprintf "f%d") clause) ^ ")")) clauses; {source = String.concat "\n" (List.rev !lines) ^ "\n"; probes = List.init count (fun i -> "Bool", Printf.sprintf "f%d" i)} let standalone query = query.source ^ command query.source ^ "\n"