https://github.com/xi-business/infsat
Raw File
Tip revision: 31552cff3d6d4239fcf05c9c201e9c2fa3678b7a authored by Jan Wroblewski on 10 February 2022, 12:43:54 UTC
Removed unusable benchmark and renamed the other g45 one.
Tip revision: 31552cf
saturation.ml
open Binding
open Cfa
open Environment
open Grammar
open GrammarCommon
open HGrammar
open Profiling
open Proof
open TargetEnvs
open Type
open TypingCommon
open Utilities

type infsat_result = Infinite of string | Finite | Unknown

let string_of_infsat_result = function
  | Infinite _ -> "infinite"
  | Finite -> "finite"
  | Unknown -> "unknown"

exception Max_iterations_reached

exception Positive_cycle_found of cycle_proof

class saturation (hg : HGrammar.hgrammar) (cfa : cfa) = object(self)
  (* Design note: typing with specific contexts occurs in Typing module. This
     module is used to prepare specification of these contexts based on 0CFA output.
     The difference in scope of these modules is that Typing module does not use 0CFA
     depdendency information. *)

  (** Current iteration number kept for debugging purposes only. *)
  val mutable iteration : int = 1

  (** Part that stores types and types specific terms and nonterminals. *)
  val typing = new Typing.typing hg

  (** Duplication Factor graph. *)
  val dfg = new DuplicationFactorGraph.dfg

  (* --- queues --- *)

  (* the queues below are defined in the order they are dequeued (the next one on the list is
     dequeued after the previous one is empty) *)

  (** Each element of this queue has a new typing of hterms that should be propagated.
      Subsequently dequeued typings are for the same hterms as previously, if available. *)
  val prop_hterms_hty_queue : hty TwoLayerQueue.t = TwoLayerQueue.make hg#hterms_count

  (** Each element of this queue is a new typing of a nonterminal that should be propagated
      by recomputing nonterminals and hterms that contain this nonterminal.
      Typings are dequeued in batches, i.e., as a pair of nonterminal and all new typings.
      If no_force_nt_ty_opt is on then the types are ignored and the new type of nonterminal
      is not forced. *)
  val prop_nt_ty_queue : ty BatchQueue.t = BatchQueue.make hg#nt_count

  (** Each element of this queue is a nonterminal to be typed. Enqueuing to this queue is
      idempotent. *)
  val nt_queue = SetQueue.make hg#nt_count

  (** Each element of this queue is a sequence of hterms to be typed. Enqueuing to this queue
      is idempotent. *)
  val hterms_queue = SetQueue.make hg#hterms_count

  (* --- printing --- *)

  method string_of_queues =
    "prop_hterms_hty_queue: " ^
    TwoLayerQueue.string_of_queue string_of_hty prop_hterms_hty_queue
    ^ "\n" ^
    "prop_nt_ty_queue: " ^ BatchQueue.string_of_queue string_of_ty prop_nt_ty_queue ^ "\n" ^
    "nt_queue: " ^ SetQueue.string_of_queue nt_queue ^ "\n" ^
    "hterms_queue: " ^ SetQueue.string_of_queue hterms_queue

  method status : string =
    typing#string_of_nt_ity ^ "\n\n" ^
    typing#string_of_hterms_hty cfa#hterms_are_arg ^ "\n\n" ^
    self#string_of_queues ^ "\n\n" ^
    "Duplication Factor Graph:\n" ^ dfg#to_string hg#nt_name

  method iter_counter : string =
    "Starting iteration " ^ string_of_int iteration ^ "."

  (* --- processing results of typing --- *)

  method register_nt_ty (nt : nt_id) (ty : ty) (env : env) =
    if typing#add_nt_ty nt ty then
      begin
        print_verbose !Flags.verbose_typing @@ lazy (
          "Registering new typing of nonterminal " ^ hg#nt_name nt ^ " : " ^
          string_of_ty ty ^ "."
        );
        BatchQueue.enqueue prop_nt_ty_queue nt ty
      end;
    let proof = {
      derived = (nt, ty);
      used_nts = env.used_nts;
      loc_types = env.loc_types;
      positive = env.positive;
      initial = false
    } in
    (* Adding the new typing to the graph and checking for positive cycle. This should
       be performed even if the typing is not new, since set of used nonterminals may be new. *)
    if dfg#add_vertex proof then
      begin
        print_verbose !Flags.verbose_typing @@ lazy (
          "The duplication factor graph was updated by adding or modifying " ^
          "an edge. Proof for added edges " ^
          "(+ - positive duplication factor):\n" ^
          string_of_proof hg None proof
        );
        match dfg#find_positive_cycle hg#start_nt ty_pr with
        | Some path -> raise_notrace @@ Positive_cycle_found path
        | None -> ()
      end
    else
      print_verbose !Flags.verbose_proofs @@ lazy (
        "No edge was added or modified in the duplicaton factor graph. The proof was " ^
        "(+ - positive duplication factor/multiple uses):\n" ^
        string_of_proof hg None proof
      )

  method register_hterms_hty (id : hterms_id) (hty : hty) =
    if typing#add_hterms_hty id hty then
      begin
        print_verbose !Flags.verbose_typing @@ lazy (
          "Registering new typing of hterms " ^ hg#string_of_hterms id ^ ": " ^
          hg#string_of_hterms id ^ " : " ^
          string_of_hty hty
        );
        TwoLayerQueue.enqueue prop_hterms_hty_queue id hty
      end

  (* --- typing --- *)

  (** Infers types of given nonterminal. Has an option to force types of some hterms.
      Works by generating a list of all possible environments based on bindings and then iterating
      over it while typing the nonterminal. *)
  method infer_nt_ty (nt : nt_id) (forced_hterms_hty : (hterms_id * hty) option)
      (forced_nt_ity : (nt_id * TySet.t) option) (binding : hterms_id binding option) =
    print_verbose !Flags.verbose_queues @@ lazy (
      let binding_info =
        match binding with
        | Some binding ->
          " under binding " ^ string_of_binding string_of_int binding
        | None -> ""
      in
      let forced_hterms_hty_str =
        match forced_hterms_hty with
        | Some (id, hty) ->
          " forced typing of hterms " ^ string_of_int id ^ " " ^ hg#string_of_hterms id ^
          " : " ^ string_of_hty hty
        | None ->
          ""
      in
      let forced_nt_ity_str =
        match forced_nt_ity with
        | Some (nt, ity) ->
          " forced typing of nonterminal " ^ hg#nt_name nt ^
          " : " ^ concat_map " \\/ " string_of_ty (TySet.elements ity)
        | None ->
          ""
      in
      "* Inferring type of nonterminal " ^ hg#nt_name nt ^
      String.concat " and" [binding_info; forced_hterms_hty_str; forced_nt_ity_str] ^ "."
    );
    indent (+1);
    let body = hg#nt_body nt in
    let te =
      match binding with
      | Some binding ->
        let ctx = typing#binding2ctx body None forced_hterms_hty None binding in
        typing#type_check body None ctx false false
      | None ->
        assert (forced_hterms_hty = None);
        assert (forced_nt_ity = None);
        typing#type_check body None Context.empty_ctx false false
    in
    te |> TargetEnvs.iter_fun_ty (hg#nt_arity nt) (fun ty env ->
        self#register_nt_ty nt ty env
      );
    indent (-1)
  
  (** Infers type of given hterm under given bindings. If the type is new, it is registered.
      Has an option to force types of some hterms.
      Works by generating a list of all possible environments based on bindings and then iterating
      over it while typing the nonterminal. *)
  method infer_hterms_hty (id : hterms_id) (forced_hterms_hty : (hterms_id * hty) option)
      (forced_nt_ity : (nt_id * TySet.t) option) (binding : hterms_id binding) =
    print_verbose !Flags.verbose_queues @@ lazy (
      let typing_info =
        match forced_hterms_hty with
        | Some (id, hty) ->
          "forced typing of hterms " ^ hg#string_of_hterms id ^ " " ^
          hg#string_of_hterms id ^ " to " ^ string_of_hty hty
        | None ->
          "no forced typings"
      in
      let hterms_str =
        hg#string_of_hterms id
      in
      "* Inferring type of hterms " ^ hterms_str ^ " under binding " ^
      string_of_binding string_of_int binding ^ " and " ^ typing_info ^ "."
    );
    indent (+1);
    let mask = hg#id2vars id in
    let hterms = hg#id2hterms id in
    let tes = hterms |> List.map (fun hterm ->
        let ctx = typing#binding2ctx hterm (Some mask) forced_hterms_hty None binding in
        typing#type_check hterm None ctx false false
      )
    in
    let hty =
      Array.of_list @@
      List.map (fun te -> TySet.of_ity @@ TargetEnvs.targets_as_args te) @@
      tes
    in
    self#register_hterms_hty id hty;
    indent (-1)

  (* --- processing queues --- *)

  (** Processes prop_hterms_hty_queue if not empty and returns if it was not empty. *)
  method process_prop_hterms_hty_queue : bool =
    try
      let id, hty = TwoLayerQueue.dequeue prop_hterms_hty_queue in
      print_verbose !Flags.verbose_queues @@ lazy (
        "prop_nt_queue: Propagating new types of hterms " ^ string_of_int id ^ " " ^
        hg#string_of_hterms id ^ " : " ^ string_of_hty hty ^ "."
      );
      (* This step is required, because new typing of hterms might be enough to type other hterms,
         but not enough to type a whole nonterminal if it needs typings of more hterms. And these
         newly typed hterms may be enough to type some nonterminal, while it would be impossible
         without their new typing. *)
      cfa#get_hterms_where_hterms_flow id |> SortedHTermsIds.iter (fun recomputed_id ->
          if !Flags.no_force_hterms_hty_opt then
            SetQueue.enqueue hterms_queue recomputed_id
          else
            (* sort_and_delete_duplicates is here to not repeat the work when the same
               binding is present twice (a rare occurence). This will break if these bindings
               stop being tuples of ints due to default compare.
               TODO maybe this is not true and sorting/taking unique is not needed *)
            cfa#get_hterms_bindings recomputed_id |>
            sort_and_delete_duplicates |>
            List.filter (List.exists (fun (_, _, id'') -> id'' = id)) |>
            List.iter @@ self#infer_hterms_hty recomputed_id (Some (id, hty)) None
        );
      (* This step is required, because when a new type is computed for hterms,
         nonterminals are typed with new possible typings of arguments, which may generate
         new nonterminal typings. *)
      cfa#get_nt_bindings_applied_to_hterms id |> List.iter (fun (nt, binding) ->
          self#infer_nt_ty nt (Some (id, hty)) None @@ Some binding
        );
      true
    with
    | TwoLayerQueue.Empty -> false

  (** Processes prop_nt_ty_queue if not empty and returns if it was not empty. *)
  method process_prop_nt_ty_queue : bool =
    try
      let nt, tys = BatchQueue.dequeue prop_nt_ty_queue in
      if !Flags.no_force_nt_ty_opt then
        begin
          print_verbose !Flags.verbose_queues @@ lazy (
            "prop_nt_queue: Propagating all types of nonterminal " ^
            hg#nt_name nt ^ "."
          );
          (* This step is required, because when a new type is computed for a nonterminal, type
             of application of this nonterminal to other terms may yield new possible typings
             and effectively give new type for the terms it is contained in. *)
          cfa#get_nt_containing_nt nt |> SortedNTs.iter (fun nt' ->
              SetQueue.enqueue nt_queue nt'
            );
          (* This step is required, because when a new type is computed for a nonterminal, new
             typings of hterms that contain it may be computed. *)
          cfa#get_hterms_containing_nt nt |> SortedHTermsIds.iter (fun id ->
              SetQueue.enqueue hterms_queue id
            )
        end
      else
        begin
          print_verbose !Flags.verbose_queues @@ lazy (
            "prop_nt_queue: Propagating new types of nonterminal " ^
            hg#nt_name nt ^ " : " ^ string_of_ity (TyList.of_list tys) ^ "."
          );
          let ftys = TySet.of_list tys in
          (* Inferring type of nonterminals that contain this one while enforcing usage of
             new types. *)
          cfa#get_nt_containing_nt nt |> SortedNTs.iter (fun recomputed_nt ->
              cfa#lookup_nt_bindings recomputed_nt |>
              List.map (fun binding -> Some binding) |>
              List.iter @@ self#infer_nt_ty recomputed_nt None (Some (nt, ftys))
            );
          (* Inferring type of hterms that contain this nonterminal while enforcing usage of
             new types. *)
          cfa#get_hterms_containing_nt nt |> SortedHTermsIds.iter (fun recomputed_id ->
              cfa#get_hterms_bindings recomputed_id |>
              sort_and_delete_duplicates |>
              List.iter @@ self#infer_hterms_hty recomputed_id None (Some (nt, ftys))
            );
        end;
      true
    with
    | BatchQueue.Empty -> false

  (** Processes nt_queue if not empty and returns if it was not empty.
      It finds all bindings of a nonterminal and enqueues them to be typed. *)
  method process_nt_queue : bool =
    try
      let nt = SetQueue.dequeue nt_queue in
      if !Flags.no_headvar_opt || cfa#nt_has_headvars nt then
        let bindings = cfa#lookup_nt_bindings nt in
        print_verbose !Flags.verbose_queues @@ lazy (
          "nt_queue: Typing all " ^ string_of_int (List.length bindings) ^
          " bindings of nonterminal " ^ hg#nt_name nt ^ "."
        );
        List.iter (fun binding ->
            print_verbose !Flags.verbose_typing @@ lazy (
              "* Typing nonterminal " ^ hg#nt_name nt ^
              " with binding " ^ string_of_binding string_of_int binding ^ "."
            );
            indent (+1);
            self#infer_nt_ty nt None None @@ Some binding;
            indent (-1)
          ) bindings
      else
        begin
          print_verbose !Flags.verbose_queues @@ lazy (
            "nt_queue: Typing head variable-free nonterminal " ^ hg#nt_name nt ^ "."
          );
          self#infer_nt_ty nt None None None
        end;
      true
    with
    | SetQueue.Empty -> false

  (** Processes hterms_queue if not empty and returns if it was not empty. *)
  method process_hterms_queue : bool =
    try
      let id = SetQueue.dequeue hterms_queue in
      let bindings = cfa#get_hterms_bindings id in
      print_verbose !Flags.verbose_queues @@ lazy (
        "hterms_queue: Typing hterms " ^ hg#string_of_hterms id ^ " with " ^
        string_of_int (List.length bindings) ^ " bindings.";
      );
      bindings |> sort_and_delete_duplicates |>
      List.iter @@ self#infer_hterms_hty id None None;
      true
    with
    | SetQueue.Empty -> false

  (* --- saturation main loop --- *)

  (** Performs a single iteration of the main loop. Processes a single task from one of the queues.
      Returns whether at least one of the queues was not empty. *)
  method process_queues : bool =
    print_verbose !Flags.verbose_queues @@ lazy (
      "\n================ ITERATION " ^ string_of_int iteration ^
      " ================\n" ^
      self#status
    );
    print_verbose (!Flags.verbose_iters && not !Flags.verbose_queues) @@ lazy self#iter_counter;
    self#process_prop_hterms_hty_queue ||
    self#process_prop_nt_ty_queue ||
    self#process_nt_queue ||
    self#process_hterms_queue
  
  (** Performs saturation and returns whether the language is finite. Takes optional safety error
      message if the term is unsafe. When flag force_unsafe is false (default) and the output
      is finite language, it returns Unknown with appropriate message instead. *)
  method saturate (safety_error : string option) : infsat_result =
    let start_t = Sys.time () in
    try
      while self#process_queues do
        if iteration = !Flags.maxiters then
          raise_notrace Max_iterations_reached;
        iteration <- iteration + 1
      done;
      let t = Sys.time () -. start_t in
      print_verbose (not !Flags.quiet) @@ lazy (
        let final = if !Flags.verbose_final then
            self#status
          else
            "Duplication Factor Graph:\n" ^ dfg#to_string hg#nt_name
        in
        final ^ "\n" ^
        "Computed saturation result after " ^ string_of_int iteration ^ " iterations in " ^
        string_of_float t ^ "s.\n" ^
        "The input HORS contains only paths with uniformly bounded number " ^
        "of counted terminals.";
      );
      match !Flags.force_unsafe, safety_error with
      | false, Some error ->
        print_verbose (not !Flags.quiet) @@ lazy (
          "\nHowever, one of the terms is unsafe:\n" ^
          error ^ "\n\n" ^
          "The correctness of this output depends on an unproven hypothesis that the type " ^
          "system used in this work is correct for all term. If you wish to assume that this " ^
          "hypothesis holds, use -f option."
        );
        Unknown
      | _, _ -> Finite
    with
    | Positive_cycle_found cycle_proof ->
      let t = Sys.time () -. start_t in
      reset_indentation ();
      let cycle_proof_str = cycle_proof#to_string hg in
      print_verbose (not !Flags.quiet) @@ lazy (
        let final = if !Flags.verbose_final then
            self#status
          else
            "Duplication Factor Graph:\n" ^ dfg#to_string hg#nt_name
        in
        final ^ "\n" ^
        cycle_proof_str ^ "\n\n" ^
        "Computed saturation result after " ^ string_of_int iteration ^ " iterations in " ^
        string_of_float t ^ "s.\n" ^
        "The input HORS contains paths with arbitrarily many counted terminals."
      );
      Infinite cycle_proof_str
    | Max_iterations_reached ->
      let t = Sys.time () -. start_t in
      print_verbose (not !Flags.quiet) @@ lazy (
        let final = if !Flags.verbose_final then
            self#status
          else
            "Duplication Factor Graph:\n" ^ dfg#to_string hg#nt_name
        in
        final ^ "\n" ^
        "Could not determine saturation result in " ^ string_of_int iteration ^
        " iterations in " ^ string_of_float t ^ "s.\n."
      );
      Unknown

  initializer
    (* initializing queues *)

    (* enqueueing all nonterminals that can be computed without environment *)
    for nt = 0 to hg#nt_count - 1 do
      if hg#nt_arity nt = 0 || not !Flags.no_headvar_opt && not @@ cfa#nt_has_headvars nt then
        SetQueue.enqueue nt_queue nt
    done;
    
    (* enqueuing all hterms that are arguments to a nonterminal *)
    for id = 0 to hg#hterms_count - 1 do
      if cfa#hterms_are_arg id then
        SetQueue.enqueue hterms_queue id
    done
end
back to top