(** This file contains simplification passes for our Lustre-like AST *) open Ast open Passes_utils open Utils let pre2vars verbose debug main_fn = let rec all_pre expr = match expr with | EMonOp (ty, MOp_pre, expr) -> all_pre expr | EMonOp _ -> false | EVar _ -> true | _ -> false in let rec pre_push expr : t_expression = match expr with | EVar _ -> EMonOp (type_exp expr, MOp_pre, expr) | EConst _ -> expr (** pre(c) = c for any constant c *) | EMonOp (ty, mop, expr) -> begin match mop with | MOp_pre -> if all_pre expr then EMonOp (ty, mop, EMonOp (ty, mop, expr)) else pre_push (pre_push expr) | _ -> EMonOp (ty, mop, pre_push expr) end | EBinOp (ty, bop, expr, expr') -> let expr = pre_push expr in let expr' = pre_push expr' in EBinOp (ty, bop, expr, expr') | ETriOp (ty, top, expr, expr', expr'') -> let expr = pre_push expr in let expr' = pre_push expr' in let expr'' = pre_push expr'' in ETriOp (ty, top, expr, expr', expr'') | EComp (ty, cop, expr, expr') -> let expr = pre_push expr in let expr' = pre_push expr' in EComp (ty, cop, expr, expr') | EWhen (ty, expr, expr') -> let expr = pre_push expr in let expr' = pre_push expr' in EWhen (ty, expr, expr') | EReset (ty, expr, expr') -> let expr = pre_push expr in let expr' = pre_push expr' in EReset (ty, expr, expr') | ETuple (ty, elist) -> let elist = List.fold_right (fun expr acc -> (pre_push expr) :: acc) elist [] in ETuple (ty, elist) | EApp (ty, node, arg) -> let arg = pre_push arg in EApp (ty, node, arg) in let rec aux (expr: t_expression) = match expr with | EVar _ -> expr | EMonOp (ty, mop, expr) -> begin match mop with | MOp_pre -> pre_push expr | _ -> let expr = aux expr in EMonOp (ty, mop, expr) end | EBinOp (ty, bop, expr, expr') -> let expr = aux expr in let expr' = aux expr' in EBinOp (ty, bop, expr, expr') | ETriOp (ty, top, expr, expr', expr'') -> let expr = aux expr in let expr' = aux expr' in let expr'' = aux expr'' in ETriOp (ty, top, expr, expr', expr'') | EComp (ty, cop, expr, expr') -> let expr = aux expr in let expr' = aux expr' in EComp (ty, cop, expr, expr') | EWhen (ty, expr, expr') -> let expr = aux expr in let expr' = aux expr' in EWhen (ty, expr, expr') | EReset (ty, expr, expr') -> let expr = aux expr in let expr' = aux expr' in EReset (ty, expr, expr') | EConst (ty, c) -> EConst (ty, c) | ETuple (ty, elist) -> let elist = List.fold_right (fun expr acc -> (aux expr) :: acc) elist [] in ETuple (ty, elist) | EApp (ty, node, arg) -> let arg = aux arg in EApp (ty, node, arg) in expression_pass (somify aux) let chkvar_init_unicity verbose debug main_fn : t_nodelist -> t_nodelist option = let aux (node: t_node) : t_node option = let incr_aux h n = match Hashtbl.find_opt h n with | None -> raise (PassExn "todo, should not happened.") | Some num -> Hashtbl.replace h n (num + 1) in let incr_eq h (((_, patt), _): t_equation) = List.iter (fun v -> incr_aux h (name_of_var v)) patt in let rec incr_eqlist h = function | [] -> () | eq :: eqs -> (incr_eq h eq; incr_eqlist h eqs) in let incr_branch h (State (_, eqs, _, _): t_state) = incr_eqlist h eqs in let incr_automata h ((_, states): t_automaton) = let acc = Hashtbl.copy h in List.iter (fun st -> let h_st = Hashtbl.copy h in incr_branch h_st st; Hashtbl.iter (fun varname num' -> match Hashtbl.find_opt acc varname with | None -> failwith "no!" | Some num -> Hashtbl.replace acc varname (Int.max num num') ) h_st) states; Hashtbl.iter (fun v n -> Hashtbl.replace h v n) acc in let check_now h : bool= Hashtbl.fold (fun varname num old_res -> if num > 1 then (verbose (Format.asprintf "%s initialized twice!" varname); false) else old_res) h true in (*let purge_initialized h = Hashtbl.iter (fun varname num -> if num > 0 then (verbose (Format.asprintf "Purging %s" varname); Hashtbl.remove h varname) else ()) h in*) let h = Hashtbl.create Config.maxvar in let add_var n v = match v with | IVar s -> Hashtbl.add h s n | BVar s -> Hashtbl.add h s n | RVar s -> Hashtbl.add h s n in let add_var_in = add_var 1 in let add_var_loc = add_var 0 in List.iter add_var_loc (snd node.n_outputs); List.iter add_var_loc (snd node.n_local_vars); List.iter add_var_in (snd node.n_inputs); (** Usual Equations *) incr_eqlist h node.n_equations; if check_now h = false then None else begin List.iter (* 0. *) (incr_automata h) node.n_automata; if check_now h then Some node else None end (** never purge -> failwith never executed! purge_initialized h; *) in node_pass aux let rec tpl debug ((pat, exp): t_equation) = match exp with | ETuple (_, hexps :: texps) -> debug "An ETuple has been recognized, inlining..."; let p1, p2 = list_select (List.length (type_exp hexps)) (snd pat) in let t1 = List.flatten (List.map type_var p1) in let t2 = List.flatten (List.map type_var p2) in ((t1, p1), hexps) :: (tpl debug ((t2, p2), ETuple (List.flatten (List.map type_exp texps), texps))) | ETuple (_, []) -> [] | _ -> [(pat, exp)] let pass_linearization verbose debug main_fn = let node_lin (node: t_node): t_node option = let rec pre_aux_expression vars expr: t_eqlist * t_varlist * t_expression = match expr with | EVar _ -> [], vars, expr | EMonOp (t, op, e) -> begin match op with | MOp_pre -> let eqs, vars, e = pre_aux_expression vars e in let nvar: string = fresh_var_name vars 6 in let nvar = match t with | [TInt] -> IVar nvar | [TBool] -> BVar nvar | [TReal] -> RVar nvar | _ -> failwith "Should not happened." in let neq_patt: t_varlist = (t, [nvar]) in let neq_expr: t_expression = e in let vars = varlist_concat (t, [nvar]) vars in (neq_patt, neq_expr) :: eqs, vars, EMonOp (t, MOp_pre, EVar (t, nvar)) | _ -> let eqs, vars, e = pre_aux_expression vars e in eqs, vars, EMonOp (t, op, e) end | EBinOp (t, op, e, e') -> let eqs, vars, e = pre_aux_expression vars e in let eqs', vars, e' = pre_aux_expression vars e' in eqs @ eqs', vars, EBinOp (t, op, e, e') | ETriOp (t, op, e, e', e'') -> let eqs, vars, e = pre_aux_expression vars e in let nvar: string = fresh_var_name vars 6 in let nvar: t_var = BVar nvar in let neq_patt: t_varlist = ([TBool], [nvar]) in let neq_expr: t_expression = e in let vars = varlist_concat vars (neq_patt) in let eqs', vars, e' = pre_aux_expression vars e' in let eqs'', vars, e'' = pre_aux_expression vars e'' in (neq_patt, neq_expr) :: eqs @ eqs' @ eqs'', vars, ETriOp (t, op, e, e', e'') | EComp (t, op, e, e') -> let eqs, vars, e = pre_aux_expression vars e in let eqs', vars, e' = pre_aux_expression vars e' in eqs @ eqs', vars, EComp (t, op, e, e') | EWhen (t, e, e') -> let eqs, vars, e = pre_aux_expression vars e in let eqs', vars, e' = pre_aux_expression vars e' in eqs @ eqs', vars, EWhen (t, e, e') | EReset (t, e, e') -> let eqs, vars, e = pre_aux_expression vars e in let eqs', vars, e' = pre_aux_expression vars e' in eqs @ eqs', vars, EReset (t, e, e') | EConst _ -> [], vars, expr | ETuple (t, l) -> let eqs, vars, l = List.fold_right (fun e (eqs, vars, l) -> let eqs', vars, e = pre_aux_expression vars e in eqs' @ eqs, vars, (e :: l)) l ([], vars, []) in eqs, vars, ETuple (t, l) | EApp (t, n, e) -> let eqs, vars, e = pre_aux_expression vars e in eqs, vars, EApp (t, n, e) in let rec pre_aux_equation (vars: t_varlist) ((patt, expr): t_equation) = let eqs, vars, expr = pre_aux_expression vars expr in (patt, expr)::eqs, vars in let rec tpl ((pat, exp): t_equation) = match exp with | ETuple (_, hexps :: texps) -> debug "An ETuple has been recognized, inlining..."; let p1, p2 = list_select (List.length (type_exp hexps)) (snd pat) in let t1 = List.flatten (List.map type_var p1) in let t2 = List.flatten (List.map type_var p2) in ((t1, p1), hexps) :: (tpl ((t2, p2), ETuple (List.flatten (List.map type_exp texps), texps))) | ETuple (_, []) -> [] | _ -> [(pat, exp)] in let new_equations = List.flatten (List.map tpl node.n_equations) in let new_equations, new_locvars = List.fold_left (fun (eqs, vars) eq -> let es, vs = pre_aux_equation vars eq in es @ eqs, vs) ([], node.n_local_vars) new_equations in Some { n_name = node.n_name; n_inputs = node.n_inputs; n_outputs = node.n_outputs; n_local_vars = new_locvars; n_equations = new_equations; n_automata = node.n_automata; } in node_pass node_lin let pass_eq_reordering verbose debug main_fn ast = let rec pick_equations init_vars eqs remaining_equations = match remaining_equations with | [] -> Some eqs | _ -> begin match List.filter (fun (patt, expr) -> List.for_all (fun v -> List.mem v init_vars) (vars_of_expr expr)) remaining_equations with | [] -> raise (PassExn "[equation ordering] The equations cannot be ordered.") | h :: t -> let init_vars = List.fold_left (fun acc vs -> acc @ (vars_of_patt (fst vs))) init_vars (h :: t) in pick_equations init_vars (eqs@(h :: t)) (List.filter (fun eq -> List.for_all (fun e -> eq <> e) (h :: t)) remaining_equations) end in let node_eq_reorganising (node: t_node): t_node option = let init_vars = List.map name_of_var (snd node.n_inputs) in try begin match pick_equations init_vars [] node.n_equations with | None -> None | Some eqs -> Some { node with n_equations = eqs } end with PassExn err -> (verbose err; None) in node_pass node_eq_reorganising ast let pass_typing verbose debug main_fn ast = let htbl = Hashtbl.create (List.length ast) in let () = debug "[typing verification]" in let () = List.iter (fun n -> Hashtbl.add htbl n.n_name (fst n.n_inputs, fst n.n_outputs)) ast in let rec check_varlist vl = let t = fst vl in let l = snd vl in match t, l with | [], [] -> true | TInt :: t, IVar _ :: l -> check_varlist (t, l) | TBool :: t, BVar _ :: l -> check_varlist (t, l) | TReal :: t, RVar _ :: l -> check_varlist (t, l) | _, _ -> false in let rec check_expr vl = function | EVar (t, v) -> t = type_var v | EMonOp (t, _, e) -> check_expr vl e && type_exp e = t | EBinOp (t, _, e, e') -> check_expr vl e && check_expr vl e' && t = type_exp e && t = type_exp e' | ETriOp (t, _, c, e, e') -> check_expr vl e && check_expr vl e' && check_expr vl c && type_exp c = [TBool] && type_exp e = t && type_exp e' = t | EComp (t, _, e, e') -> check_expr vl e && check_expr vl e' && t = [TBool] | EWhen (t, e, e') -> check_expr vl e && check_expr vl e' && t = type_exp e && [TBool] = type_exp e' | EReset (t, e, e') -> check_expr vl e && check_expr vl e' && t = type_exp e && type_exp e' = [TBool] | EConst (t, c) -> type_const c = t | ETuple (t, l) -> List.for_all (check_expr vl) l && t = List.flatten (List.map type_exp l) | EApp (t, n, e) -> check_expr vl e && t = (fst n.n_outputs) && type_exp e = (fst n.n_inputs) in let check_equation vl ((peq, eeq): t_equation) = if check_varlist peq then if check_expr vl eeq then fst peq = type_exp eeq else false else false in let rec check_equations vl = function | [] -> true | eq :: eqs -> if check_equation vl eq then check_equations vl eqs else false in let check_one_node node = check_varlist (node.n_inputs) && check_varlist (node.n_outputs) && check_varlist (node.n_local_vars) && check_equations (varlist_concat node.n_inputs (varlist_concat node.n_outputs node.n_local_vars)) node.n_equations in let rec aux = function | [] -> Some ast | n :: nodes -> if check_one_node n then aux nodes else None in aux ast let check_automata_validity verbos debug main_fn = let check_automaton_branch_vars automaton = let (init, states) = automaton in let left_side = Hashtbl.create 10 in let rec init_left_side eqlist = match eqlist with | [] -> () | (varlist, exp)::q -> begin Hashtbl.add left_side varlist true; init_left_side q; end in let check_state s = match s with | State(name, eqs, cond, next) -> List.for_all (fun (varlist, exp) -> (Hashtbl.mem left_side varlist)) eqs in begin match init with | State(name, eqs, cond, next) -> init_left_side eqs; let validity = List.for_all (fun s -> (check_state s)) states in if not validity then raise (PassExn "Automaton branch has different variables assignment in different branches") end in let aux node = List.iter check_automaton_branch_vars node.n_automata; Some node in node_pass aux let automaton_translation debug automaton = let gathered = Hashtbl.create 10 in let state_to_int = Hashtbl.create 10 in let add_to_table var exp state = if Hashtbl.mem gathered var then let res = Hashtbl.find gathered var in Hashtbl.replace gathered var ((state, exp)::res); else Hashtbl.replace gathered var ([(state, exp)]) in let rec init_state_translation states c = match states with | [] -> () | State(name, _, _, _)::q -> Hashtbl.replace state_to_int name c; (init_state_translation q (c+1)) in let rec find_state name = match Hashtbl.find_opt state_to_int name with | None -> failwith "Unknown state in automaton" | Some v -> v in let rec equation_pass state : t_eqlist -> unit = function | [] -> () | (vars, exp)::q -> begin add_to_table vars exp state; equation_pass state q end in let flatten_state state = match state with | State(name, eq, cond, next) -> (* Flattening is not possible for example a branch where x,y = 1, 2 will be unpacked when in another branch x, y = f(z) will not be unpacked *) (* let new_equations = List.flatten begin List.map (tpl debug) eq end in *) equation_pass name eq; State(name, eq, cond, next) in let rec transition_eq states s = match states with | [] -> EVar([TInt], IVar(s)) | State(name, eqs, cond, next)::q -> let name = find_state name and next = find_state next in ETriOp([TInt], TOp_if, EBinOp([TBool], BOp_and, EComp([TBool], COp_eq, EVar([TInt], IVar(s)), EConst([TInt], CInt(name)) ), cond ), EConst([TInt], CInt(next)), transition_eq q s ) in let default_constant ty = let defaults ty = match ty with | TInt -> EConst([ty], CInt(0)) | TBool -> EConst([ty], CBool(false)) | TReal -> EConst([ty], CReal(0.0)) in match ty with | [TInt] -> EConst(ty, CInt(0)) | [TBool] -> EConst(ty, CBool(false)) | [TReal] -> EConst(ty, CReal(0.0)) | _ -> ETuple(ty, List.map defaults ty) in let rec translate_var s v explist ty = match explist with | [] -> default_constant ty (* TODO *) | (state, exp)::q -> ETriOp(Utils.type_exp exp, TOp_if, EComp([TBool], COp_eq, EVar([TInt], IVar(s)), EConst([TInt], CInt(Hashtbl.find state_to_int state)) ), exp, translate_var s v q ty ) in let flatten_automaton automaton = let (init, states) = automaton in (flatten_state init, List.map flatten_state states) in let (init, states) = flatten_automaton automaton in let s = create_automaton_name () in init_state_translation states 1; let exp_transition = EBinOp([TInt], BOp_arrow, EConst([TInt], CInt(1)), EMonOp([TInt], MOp_pre, transition_eq states s)) in let new_equations = [(([TInt], [IVar(s)]), exp_transition)] in Hashtbl.fold (fun var explist acc -> (var, translate_var s var explist (fst var))::acc) gathered new_equations, IVar(s) let automata_trans_pass debug (node:t_node) : t_node option= let rec aux automaton = match automaton with | [] -> [], [], [] | a::q -> let eq, var = automaton_translation debug a and tail_eq, tail_var, tail_type = aux q in eq@tail_eq, var::tail_var, TInt::tail_type in let eqs, vars, new_ty = aux node.n_automata in let ty, loc_vars = node.n_local_vars in Some { n_name = node.n_name; n_inputs = node.n_inputs; n_outputs = node.n_outputs; n_local_vars = (new_ty@ty, vars@loc_vars); n_equations = eqs@node.n_equations; n_automata = []; (* not needed anymore *) } let automata_translation_pass verbose debug main_fn = node_pass (automata_trans_pass debug) let clock_unification_pass verbose debug main_fn ast = let failure str = raise (PassExn ("Failed to unify clocks: "^str)) in let known_clocks = Hashtbl.create 100 in let find_clock_var var = match Hashtbl.find_opt known_clocks var with | None -> begin match var with | BVar(name) | IVar(name) | RVar(name) -> raise (PassExn ("Cannot find clock of variable "^name) ) end | Some c -> c in let rec compute_clock_exp exp = match exp with | EConst(_, _) -> [Base] | EVar(_, var) -> find_clock_var var | EMonOp(_, MOp_pre, _) -> [Base] | EMonOp(_, _, e) -> compute_clock_exp e | EComp(_, _, e1, e2) | EReset(_, e1, e2) | EBinOp(_, _, e1, e2) -> begin let c1 = compute_clock_exp e1 and c2 = compute_clock_exp e2 in if c1 <> c2 then failure "Binop" else c1 end | EWhen(_, e1, e2) -> begin match compute_clock_exp e1 with | [c1] -> [On (c1, e2)] | _ -> failure "When" end | ETriOp(_, TOp_merge, e1, e2, e3) -> begin let c1 = compute_clock_exp e1 and c2 = compute_clock_exp e2 and c3 = compute_clock_exp e3 in match c1, c2, c3 with | [c1], [On(cl2, e2)], [On(cl3, e3)] -> begin if cl2 <> c1 || cl3 <> c1 then failure "Triop clocks" else match e2, e3 with | EMonOp(_, MOp_not, e), _ when e = e3 -> [c1] | _, EMonOp(_, MOp_not, e) when e = e2 -> [c1] | _ -> failure "Triop condition" end | _ -> failure ("Merge format") end | ETriOp(_, TOp_if, e1, e2, e3) -> let c1 = compute_clock_exp e1 and c2 = compute_clock_exp e2 and c3 = compute_clock_exp e3 in if c2 <> c3 then failure "If clocks" else c2 | ETuple(_, explist) -> List.concat_map compute_clock_exp explist | EApp(_, node, e) -> let rec aux_app clk_list = match clk_list with | [] -> raise (PassExn "Node called with no argument provided") | [cl] -> cl | t::q -> if t = (aux_app q) then t else failure "App diff clocks" and mult_clk cl out_list = match out_list with | [] -> [] | t::q -> cl::(mult_clk cl q) in mult_clk (aux_app (compute_clock_exp e)) (snd node.n_outputs) in let rec compute_eq_clock eq = let rec step vars clks = match vars, clks with | [], [] -> () | [], c::q -> raise (PassExn "Mismatch between clock size") | v::t, c::q -> Hashtbl.replace known_clocks v [c]; step t q | l, [] -> raise (PassExn "Mismatch between clock size") in let (_, vars), exp = eq in let clk = compute_clock_exp exp in step vars clk in let compute_clock_node n = begin Hashtbl.clear known_clocks; List.iter (fun v -> Hashtbl.replace known_clocks v [Base]) ( snd n.n_inputs); (* Initializing inputs to base clock *) List.iter compute_eq_clock n.n_equations; if not (List.for_all (fun v -> (Hashtbl.find known_clocks v) = [Base]) ( snd n.n_outputs)) then failure "Outputs" (*Checking that the node's output are on base clock *) else Some n end in node_pass compute_clock_node ast