summaryrefslogtreecommitdiff
path: root/src/PureMicroPasses.ml
diff options
context:
space:
mode:
Diffstat (limited to 'src/PureMicroPasses.ml')
-rw-r--r--src/PureMicroPasses.ml1375
1 files changed, 0 insertions, 1375 deletions
diff --git a/src/PureMicroPasses.ml b/src/PureMicroPasses.ml
deleted file mode 100644
index 3edae38a..00000000
--- a/src/PureMicroPasses.ml
+++ /dev/null
@@ -1,1375 +0,0 @@
-(** The following module defines micro-passes which operate on the pure AST *)
-
-open Pure
-open PureUtils
-open TranslateCore
-module V = Values
-
-(** The local logger *)
-let log = L.pure_micro_passes_log
-
-(** A configuration to control the application of the passes *)
-type config = {
- decompose_monadic_let_bindings : bool;
- (** Some provers like F* don't support the decomposition of return values
- in monadic let-bindings:
- {[
- // NOT supported in F*
- let (x, y) <-- f ();
- ...
- ]}
-
- In such situations, we might want to introduce an intermediate
- assignment:
- {[
- let tmp <-- f ();
- let (x, y) = tmp in
- ...
- ]}
- *)
- unfold_monadic_let_bindings : bool;
- (** Controls the unfolding of monadic let-bindings to explicit matches:
-
- [y <-- f x; ...]
-
- becomes:
-
- [match f x with | Failure -> Failure | Return y -> ...]
-
-
- This is useful when extracting to F*: the support for monadic
- definitions is not super powerful.
- Note that when {!field:unfold_monadic_let_bindings} is true, setting
- {!field:decompose_monadic_let_bindings} to true and only makes the code
- more verbose.
- *)
- filter_useless_monadic_calls : bool;
- (** Controls whether we try to filter the calls to monadic functions
- (which can fail) when their outputs are not used.
-
- See the comments for {!expression_contains_child_call_in_all_paths}
- for additional explanations.
-
- TODO: rename to {!filter_useless_monadic_calls}
- *)
- filter_useless_functions : bool;
- (** If {!filter_useless_monadic_calls} is activated, some functions
- become useless: if this option is true, we don't extract them.
-
- The calls to functions which always get filtered are:
- - the forward functions with unit return value
- - the backward functions which don't output anything (backward
- functions coming from rust functions with no mutable borrows
- as input values - note that if a function doesn't take mutable
- borrows as inputs, it can't return mutable borrows; we actually
- dynamically check for that).
- *)
-}
-
-(** Small utility.
-
- We sometimes have to insert new fresh variables in a function body, in which
- case we need to make their indices greater than the indices of all the variables
- in the body.
- TODO: things would be simpler if we used a better representation of the
- variables indices...
- *)
-let get_body_min_var_counter (body : fun_body) : VarId.generator =
- (* Find the max id in the input variables - some of them may have been
- * filtered from the body *)
- let min_input_id =
- List.fold_left
- (fun id (var : var) -> VarId.max id var.id)
- VarId.zero body.inputs
- in
- let obj =
- object
- inherit [_] reduce_expression
- method zero _ = min_input_id
- method plus id0 id1 _ = VarId.max (id0 ()) (id1 ())
- (* Get the maximum *)
-
- (** For the patterns *)
- method! visit_var _ v _ = v.id
-
- (** For the rvalues *)
- method! visit_Var _ vid _ = vid
- end
- in
- (* Find the max counter in the body *)
- let id = obj#visit_expression () body.body.e () in
- VarId.generator_from_incr_id id
-
-(** "pretty-name context": see [compute_pretty_names] *)
-type pn_ctx = {
- pure_vars : string VarId.Map.t;
- (** Information about the pure variables used in the synthesized program *)
- llbc_vars : string V.VarId.Map.t;
- (** Information about the LLBC variables used in the original program *)
-}
-
-(** This function computes pretty names for the variables in the pure AST. It
- relies on the "meta"-place information in the AST to generate naming
- constraints, and then uses those to compute the names.
-
- The way it works is as follows:
- - we only modify the names of the unnamed variables
- - whenever we see an rvalue/pattern which is exactly an unnamed variable,
- and this value is linked to some meta-place information which contains
- a name and an empty path, we consider we should use this name
- - we try to propagate naming constraints on the pure variables use in the
- synthesized programs, and also on the LLBC variables from the original
- program (information about the LLBC variables is stored in the meta-places)
-
-
- Something important is that, for every variable we find, the name of this
- variable can be influenced by the information we find *below* in the AST.
-
- For instance, the following situations happen:
-
- - let's say we evaluate:
- {[
- match (ls : List<T>) {
- List::Cons(x, hd) => {
- ...
- }
- }
- ]}
-
- Actually, in MIR, we get:
- {[
- tmp := discriminant(ls);
- switch tmp {
- 0 => {
- x := (ls as Cons).0; // (i)
- hd := (ls as Cons).1; // (ii)
- ...
- }
- }
- ]}
- If [ls] maps to a symbolic value [s0] upon evaluating the match in symbolic
- mode, we expand this value upon evaluating [tmp = discriminant(ls)].
- However, at this point, we don't know which should be the names of
- the symbolic values we introduce for the fields of [Cons]!
-
- Let's imagine we have (for the [Cons] branch): [s0 ~~> Cons s1 s2].
- The assigments at (i) and (ii) lead to the following binding in the
- evaluation context:
- {[
- x -> s1
- hd -> s2
- ]}
-
- When generating the symbolic AST, we save as meta-information that we
- assign [s1] to the place [x] and [s2] to the place [hd]. This way,
- we learn we can use the names [x] and [hd] for the variables which are
- introduced by the match:
- {[
- match ls with
- | Cons x hd -> ...
- | ...
- ]}
- - Assignments:
- [let x [@mplace=lp] = v [@mplace = rp] in ...]
-
- We propagate naming information across the assignments. This is important
- because many reassignments using temporary, anonymous variables are
- introduced during desugaring.
-
- - Given back values (introduced by backward functions):
- Let's say we have the following Rust code:
- {[
- let py = id(&mut x);
- *py = 2;
- assert!(x == 2);
- ]}
-
- After desugaring, we get the following MIR:
- {[
- ^0 = &mut x; // anonymous variable
- py = id(move ^0);
- *py += 2;
- assert!(x == 2);
- ]}
-
- We want this to be translated as:
- {[
- let py = id_fwd x in
- let py1 = py + 2 in
- let x1 = id_back x py1 in // <-- x1 is "given back": doesn't appear in the original MIR
- assert(x1 = 2);
- ]}
-
- We want to notice that the value given back by [id_back] is given back for "x",
- so we should use "x" as the basename (hence the resulting name "x1"). However,
- this is non-trivial, because after desugaring the input argument given to [id]
- is not [&mut x] but [move ^0] (i.e., it comes from a temporary, anonymous
- variable). For this reason, we use the meta-place [&mut x] as the meta-place
- for the given back value (this is done during the synthesis), and propagate
- naming information *also* on the LLBC variables (which are referenced by the
- meta-places).
-
- This way, because of [^0 = &mut x], we can propagate the name "x" to the place
- [^0], then to the given back variable across the function call.
-
- *)
-let compute_pretty_names (def : fun_decl) : fun_decl =
- (* Small helpers *)
- (*
- * When we do branchings, we need to merge (the constraints saved in) the
- * contexts returned by the different branches.
- *
- * Note that by doing so, some mappings from var id to name
- * in one context may be overriden by the ones in the other context.
- *
- * This should be ok because:
- * - generally, the overriden variables should have been introduced *inside*
- * the branches, in which case we don't care
- * - or they were introduced before, in which case the naming should generally
- * be consistent? In the worse case, it isn't, but it leads only to less
- * readable code, not to unsoundness. This case should be pretty rare,
- * also.
- *)
- let merge_ctxs (ctx0 : pn_ctx) (ctx1 : pn_ctx) : pn_ctx =
- let pure_vars =
- VarId.Map.fold
- (fun id name ctx -> VarId.Map.add id name ctx)
- ctx0.pure_vars ctx1.pure_vars
- in
- let llbc_vars =
- V.VarId.Map.fold
- (fun id name ctx -> V.VarId.Map.add id name ctx)
- ctx0.llbc_vars ctx1.llbc_vars
- in
- { pure_vars; llbc_vars }
- in
- let empty_ctx =
- { pure_vars = VarId.Map.empty; llbc_vars = V.VarId.Map.empty }
- in
- let merge_ctxs_ls (ctxs : pn_ctx list) : pn_ctx =
- List.fold_left (fun ctx0 ctx1 -> merge_ctxs ctx0 ctx1) empty_ctx ctxs
- in
-
- (*
- * The way we do is as follows:
- * - we explore the expressions
- * - we register the variables introduced by the let-bindings
- * - we use the naming information we find (through the variables and the
- * meta-places) to update our context (i.e., maps from variable ids to
- * names)
- * - we use this information to update the names of the variables used in the
- * expressions
- *)
-
- (* Register a variable for constraints propagation - used when an variable is
- * introduced (left-hand side of a left binding) *)
- let register_var (ctx : pn_ctx) (v : var) : pn_ctx =
- assert (not (VarId.Map.mem v.id ctx.pure_vars));
- match v.basename with
- | None -> ctx
- | Some name ->
- let pure_vars = VarId.Map.add v.id name ctx.pure_vars in
- { ctx with pure_vars }
- in
- (* Update a variable - used to update an expression after we computed constraints *)
- let update_var (ctx : pn_ctx) (v : var) (mp : mplace option) : var =
- match v.basename with
- | Some _ -> v
- | None -> (
- match VarId.Map.find_opt v.id ctx.pure_vars with
- | Some basename -> { v with basename = Some basename }
- | None ->
- if Option.is_some mp then
- match
- V.VarId.Map.find_opt (Option.get mp).var_id ctx.llbc_vars
- with
- | None -> v
- | Some basename -> { v with basename = Some basename }
- else v)
- in
- (* Update an pattern - used to update an expression after we computed constraints *)
- let update_typed_pattern ctx (lv : typed_pattern) : typed_pattern =
- let obj =
- object
- inherit [_] map_typed_pattern
- method! visit_PatVar _ v mp = PatVar (update_var ctx v mp, mp)
- end
- in
- obj#visit_typed_pattern () lv
- in
-
- (* Register an mplace the first time we find one *)
- let register_mplace (mp : mplace) (ctx : pn_ctx) : pn_ctx =
- match (V.VarId.Map.find_opt mp.var_id ctx.llbc_vars, mp.name) with
- | None, Some name ->
- let llbc_vars = V.VarId.Map.add mp.var_id name ctx.llbc_vars in
- { ctx with llbc_vars }
- | _ -> ctx
- in
-
- (* Register the fact that [name] can be used for the pure variable identified
- * by [var_id] (will add this name in the map if the variable is anonymous) *)
- let add_pure_var_constraint (var_id : VarId.id) (name : string) (ctx : pn_ctx)
- : pn_ctx =
- let pure_vars =
- if VarId.Map.mem var_id ctx.pure_vars then ctx.pure_vars
- else VarId.Map.add var_id name ctx.pure_vars
- in
- { ctx with pure_vars }
- in
- (* Similar to [add_pure_var_constraint], but for LLBC variables *)
- let add_llbc_var_constraint (var_id : V.VarId.id) (name : string)
- (ctx : pn_ctx) : pn_ctx =
- let llbc_vars =
- if V.VarId.Map.mem var_id ctx.llbc_vars then ctx.llbc_vars
- else V.VarId.Map.add var_id name ctx.llbc_vars
- in
- { ctx with llbc_vars }
- in
- (* Add a constraint: given a variable id and an associated meta-place, try to
- * extract naming information from the meta-place and save it *)
- let add_constraint (mp : mplace) (var_id : VarId.id) (ctx : pn_ctx) : pn_ctx =
- (* Register the place *)
- let ctx = register_mplace mp ctx in
- (* Update the variable name *)
- match (mp.name, mp.projection) with
- | Some name, [] ->
- (* Check if the variable already has a name - if not: insert the new name *)
- let ctx = add_pure_var_constraint var_id name ctx in
- let ctx = add_llbc_var_constraint mp.var_id name ctx in
- ctx
- | _ -> ctx
- in
- (* Specific case of constraint on rvalues *)
- let add_right_constraint (mp : mplace) (rv : texpression) (ctx : pn_ctx) :
- pn_ctx =
- (* Register the place *)
- let ctx = register_mplace mp ctx in
- (* Add the constraint *)
- match (unmeta rv).e with Var vid -> add_constraint mp vid ctx | _ -> ctx
- in
- (* Specific case of constraint on left values *)
- let add_left_constraint (lv : typed_pattern) (ctx : pn_ctx) : pn_ctx =
- let obj =
- object (self)
- inherit [_] reduce_typed_pattern
- method zero _ = empty_ctx
- method plus ctx0 ctx1 _ = merge_ctxs (ctx0 ()) (ctx1 ())
-
- method! visit_PatVar _ v mp () =
- (* Register the variable *)
- let ctx = register_var (self#zero ()) v in
- (* Register the mplace information if there is such information *)
- match mp with Some mp -> add_constraint mp v.id ctx | None -> ctx
- end
- in
- let ctx1 = obj#visit_typed_pattern () lv () in
- merge_ctxs ctx ctx1
- in
-
- (* This is used to propagate constraint information about places in case of
- * variable reassignments: we try to propagate the information from the
- * rvalue to the left *)
- let add_left_right_constraint (lv : typed_pattern) (re : texpression)
- (ctx : pn_ctx) : pn_ctx =
- (* We propagate constraints across variable reassignments: [^0 = x],
- * if the destination doesn't have naming information *)
- match lv.value with
- | PatVar (({ id = _; basename = None; ty = _ } as lvar), lmp) ->
- if
- (* Check that there is not already a name for the variable *)
- VarId.Map.mem lvar.id ctx.pure_vars
- then ctx
- else
- (* We ignore the left meta-place information: it should have been taken
- * care of by [add_left_constraint]. We try to use the right meta-place
- * information *)
- let add (name : string) (ctx : pn_ctx) : pn_ctx =
- (* Add the constraint for the pure variable *)
- let ctx = add_pure_var_constraint lvar.id name ctx in
- (* Add the constraint for the LLBC variable *)
- match lmp with
- | None -> ctx
- | Some lmp -> add_llbc_var_constraint lmp.var_id name ctx
- in
- (* We try to use the right-place information *)
- let rmp, re = opt_unmeta_mplace re in
- let ctx =
- match rmp with
- | Some { var_id; name; projection = [] } -> (
- if Option.is_some name then add (Option.get name) ctx
- else
- match V.VarId.Map.find_opt var_id ctx.llbc_vars with
- | None -> ctx
- | Some name -> add name ctx)
- | _ -> ctx
- in
- (* We try to use the rvalue information, if it is a variable *)
- let ctx =
- match (unmeta re).e with
- | Var rvar_id -> (
- match VarId.Map.find_opt rvar_id ctx.pure_vars with
- | None -> ctx
- | Some name -> add name ctx)
- | _ -> ctx
- in
- ctx
- | _ -> ctx
- in
-
- (* *)
- let rec update_texpression (e : texpression) (ctx : pn_ctx) :
- pn_ctx * texpression =
- let ty = e.ty in
- let ctx, e =
- match e.e with
- | Var _ -> (* Nothing to do *) (ctx, e.e)
- | Const _ -> (* Nothing to do *) (ctx, e.e)
- | App (app, arg) ->
- let ctx, app = update_texpression app ctx in
- let ctx, arg = update_texpression arg ctx in
- let e = App (app, arg) in
- (ctx, e)
- | Abs (x, e) -> update_abs x e ctx
- | Qualif _ -> (* nothing to do *) (ctx, e.e)
- | Let (monadic, lb, re, e) -> update_let monadic lb re e ctx
- | Switch (scrut, body) -> update_switch_body scrut body ctx
- | Meta (meta, e) -> update_meta meta e ctx
- in
- (ctx, { e; ty })
- (* *)
- and update_abs (x : typed_pattern) (e : texpression) (ctx : pn_ctx) :
- pn_ctx * expression =
- (* We first add the left-constraint *)
- let ctx = add_left_constraint x ctx in
- (* Update the expression, and add additional constraints *)
- let ctx, e = update_texpression e ctx in
- (* Update the abstracted value *)
- let x = update_typed_pattern ctx x in
- (* Put together *)
- (ctx, Abs (x, e))
- (* *)
- and update_let (monadic : bool) (lv : typed_pattern) (re : texpression)
- (e : texpression) (ctx : pn_ctx) : pn_ctx * expression =
- (* We first add the left-constraint *)
- let ctx = add_left_constraint lv ctx in
- (* Then we try to propagate the right-constraints to the left, in case
- * the left constraints didn't give naming information *)
- let ctx = add_left_right_constraint lv re ctx in
- let ctx, re = update_texpression re ctx in
- let ctx, e = update_texpression e ctx in
- let lv = update_typed_pattern ctx lv in
- (ctx, Let (monadic, lv, re, e))
- (* *)
- and update_switch_body (scrut : texpression) (body : switch_body)
- (ctx : pn_ctx) : pn_ctx * expression =
- let ctx, scrut = update_texpression scrut ctx in
-
- let ctx, body =
- match body with
- | If (e_true, e_false) ->
- let ctx1, e_true = update_texpression e_true ctx in
- let ctx2, e_false = update_texpression e_false ctx in
- let ctx = merge_ctxs ctx1 ctx2 in
- (ctx, If (e_true, e_false))
- | Match branches ->
- let ctx_branches_ls =
- List.map
- (fun br ->
- let ctx = add_left_constraint br.pat ctx in
- let ctx, branch = update_texpression br.branch ctx in
- let pat = update_typed_pattern ctx br.pat in
- (ctx, { pat; branch }))
- branches
- in
- let ctxs, branches = List.split ctx_branches_ls in
- let ctx = merge_ctxs_ls ctxs in
- (ctx, Match branches)
- in
- (ctx, Switch (scrut, body))
- (* *)
- and update_meta (meta : meta) (e : texpression) (ctx : pn_ctx) :
- pn_ctx * expression =
- let ctx =
- match meta with
- | Assignment (mp, rvalue, rmp) ->
- let ctx = add_right_constraint mp rvalue ctx in
- let ctx =
- match (mp.projection, rmp) with
- | [], Some { var_id; name; projection = [] } -> (
- let name =
- match name with
- | Some name -> Some name
- | None -> V.VarId.Map.find_opt var_id ctx.llbc_vars
- in
- match name with
- | None -> ctx
- | Some name -> add_llbc_var_constraint mp.var_id name ctx)
- | _ -> ctx
- in
- ctx
- | MPlace mp -> add_right_constraint mp e ctx
- in
- let ctx, e = update_texpression e ctx in
- let e = mk_meta meta e in
- (ctx, e.e)
- in
-
- let body =
- match def.body with
- | None -> None
- | Some body ->
- let input_names =
- List.filter_map
- (fun (v : var) ->
- match v.basename with
- | None -> None
- | Some name -> Some (v.id, name))
- body.inputs
- in
- let ctx =
- {
- pure_vars = VarId.Map.of_list input_names;
- llbc_vars = V.VarId.Map.empty;
- }
- in
- let _, body_exp = update_texpression body.body ctx in
- Some { body with body = body_exp }
- in
- { def with body }
-
-(** Remove the meta-information *)
-let remove_meta (def : fun_decl) : fun_decl =
- match def.body with
- | None -> def
- | Some body ->
- let body = { body with body = PureUtils.remove_meta body.body } in
- { def with body = Some body }
-
-(** Inline the useless variable (re-)assignments:
-
- A lot of intermediate variable assignments are introduced through the
- compilation to MIR and by the translation itself (and the variable used
- on the left is often unnamed).
-
- Note that many of them are just variable "reassignments": [let x = y in ...].
- Some others come from ??
-
- TODO: how do we call that when we introduce intermediate variable assignments
- for the arguments of a function call?
-
- [inline_named]: if [true], inline all the assignments of the form
- [let VAR = VAR in ...], otherwise inline only the ones where the variable
- on the left is anonymous.
-
- [inline_pure]: if [true], inline all the pure assignments where the variable
- on the left is anonymous, but the assignments where the r-expression is
- a non-primitive function call (i.e.: inline the binops, ADT constructions,
- etc.).
-
- TODO: we have a smallish issue which is that rvalues should be merged with
- expressions... For now, this forces us to substitute whenever we can, but
- leave the let-bindings where they are, and eliminated them in a subsequent
- pass (if they are useless).
- *)
-let inline_useless_var_reassignments (inline_named : bool) (inline_pure : bool)
- (def : fun_decl) : fun_decl =
- let obj =
- object (self)
- inherit [_] map_expression as super
-
- (** Visit the let-bindings to filter the useless ones (and update
- the substitution map while doing so *)
- method! visit_Let (env : texpression VarId.Map.t) monadic lv re e =
- (* In order to filter, we need to check first that:
- * - the let-binding is not monadic
- * - the left-value is a variable
- *)
- match (monadic, lv.value) with
- | false, PatVar (lv_var, _) ->
- (* We can filter if: *)
- (* 1. the left variable is unnamed or [inline_named] is true *)
- let filter_left =
- match (inline_named, lv_var.basename) with
- | true, _ | _, None -> true
- | _ -> false
- in
- (* And either:
- * 2.1 the right-expression is a variable or a global *)
- let var_or_global = is_var re || is_global re in
- (* Or:
- * 2.2 the right-expression is a constant value, an ADT value,
- * a projection or a primitive function call *and* the flag
- * [inline_pure] is set *)
- let pure_re =
- is_const re
- ||
- let app, _ = destruct_apps re in
- match app.e with
- | Qualif qualif -> (
- match qualif.id with
- | AdtCons _ -> true (* ADT constructor *)
- | Proj _ -> true (* Projector *)
- | Func (Unop _ | Binop _) ->
- true (* primitive function call *)
- | Func (Regular _) -> false (* non-primitive function call *)
- | _ -> false)
- | _ -> false
- in
- let filter =
- filter_left && (var_or_global || (inline_pure && pure_re))
- in
-
- (* Update the rhs (we may perform substitutions inside, and it is
- * better to do them *before* we inline it *)
- let re = self#visit_texpression env re in
- (* Update the substitution environment *)
- let env = if filter then VarId.Map.add lv_var.id re env else env in
- (* Update the next expression *)
- let e = self#visit_texpression env e in
- (* Reconstruct the [let], only if the binding is not filtered *)
- if filter then e.e else Let (monadic, lv, re, e)
- | _ -> super#visit_Let env monadic lv re e
-
- (** Substitute the variables *)
- method! visit_Var (env : texpression VarId.Map.t) (vid : VarId.id) =
- match VarId.Map.find_opt vid env with
- | None -> (* No substitution *) super#visit_Var env vid
- | Some ne ->
- (* Substitute - note that we need to reexplore, because
- * there may be stacked substitutions, if we have:
- * var0 --> var1
- * var1 --> var2.
- *)
- self#visit_expression env ne.e
- end
- in
- match def.body with
- | None -> def
- | Some body ->
- let body =
- { body with body = obj#visit_texpression VarId.Map.empty body.body }
- in
- { def with body = Some body }
-
-(** Given a forward or backward function call, is there, for every execution
- path, a child backward function called later with exactly the same input
- list prefix? We use this to filter useless function calls: if there are
- such child calls, we can remove this one (in case its outputs are not
- used).
- We do this check because we can't simply remove function calls whose
- outputs are not used, as they might fail. However, if a function fails,
- its children backward functions then fail on the same inputs (ignoring
- the additional inputs those receive).
-
- For instance, if we have:
- {[
- fn f<'a>(x : &'a mut T);
- ]}
-
- We often have things like this in the synthesized code:
- {[
- _ <-- f x;
- ...
- nx <-- f@back'a x y;
- ...
- ]}
-
- In this situation, we can remove the call [f x].
- *)
-let expression_contains_child_call_in_all_paths (ctx : trans_ctx)
- (fun_id0 : fun_id) (tys0 : ty list) (args0 : texpression list)
- (e : texpression) : bool =
- let check_call (fun_id1 : fun_id) (tys1 : ty list) (args1 : texpression list)
- : bool =
- (* Check the fun_ids, to see if call1's function is a child of call0's function *)
- match (fun_id0, fun_id1) with
- | Regular (id0, rg_id0), Regular (id1, rg_id1) ->
- (* Both are "regular" calls: check if they come from the same rust function *)
- if id0 = id1 then
- (* Same rust functions: check the regions hierarchy *)
- let call1_is_child =
- match (rg_id0, rg_id1) with
- | None, _ ->
- (* The function used in call0 is the forward function: the one
- * used in call1 is necessarily a child *)
- true
- | Some _, None ->
- (* Opposite of previous case *)
- false
- | Some rg_id0, Some rg_id1 ->
- if rg_id0 = rg_id1 then true
- else
- (* We need to use the regions hierarchy *)
- (* First, lookup the signature of the LLBC function *)
- let sg =
- LlbcAstUtils.lookup_fun_sig id0 ctx.fun_context.fun_decls
- in
- (* Compute the set of ancestors of the function in call1 *)
- let call1_ancestors =
- LlbcAstUtils.list_parent_region_groups sg rg_id1
- in
- (* Check if the function used in call0 is inside *)
- T.RegionGroupId.Set.mem rg_id0 call1_ancestors
- in
- (* If call1 is a child, then we need to check if the input arguments
- * used in call0 are a prefix of the input arguments used in call1
- * (note call1 being a child, it will likely consume strictly more
- * given back values).
- * *)
- if call1_is_child then
- let call1_args =
- Collections.List.prefix (List.length args0) args1
- in
- let args = List.combine args0 call1_args in
- (* Note that the input values are expressions, *which may contain
- * meta-values* (which we need to ignore). *)
- let input_eq (v0, v1) =
- PureUtils.remove_meta v0 = PureUtils.remove_meta v1
- in
- (* Compare the input types and the prefix of the input arguments *)
- tys0 = tys1 && List.for_all input_eq args
- else (* Not a child *)
- false
- else (* Not the same function *)
- false
- | _ -> false
- in
-
- let visitor =
- object (self)
- inherit [_] reduce_expression
- method zero _ = false
- method plus b0 b1 _ = b0 () && b1 ()
-
- method! visit_texpression env e =
- match e.e with
- | Var _ | Const _ -> fun _ -> false
- | Let (_, _, re, e) -> (
- match opt_destruct_function_call re with
- | None -> fun () -> self#visit_texpression env e ()
- | Some (func1, tys1, args1) ->
- let call_is_child = check_call func1 tys1 args1 in
- if call_is_child then fun () -> true
- else fun () -> self#visit_texpression env e ())
- | App _ -> (
- fun () ->
- match opt_destruct_function_call e with
- | Some (func1, tys1, args1) -> check_call func1 tys1 args1
- | None -> false)
- | Abs (_, e) -> self#visit_texpression env e
- | Qualif _ ->
- (* Note that this case includes functions without arguments *)
- fun () -> false
- | Meta (_, e) -> self#visit_texpression env e
- | Switch (_, body) -> self#visit_switch_body env body
-
- method! visit_switch_body env body =
- match body with
- | If (e1, e2) ->
- fun () ->
- self#visit_texpression env e1 ()
- && self#visit_texpression env e2 ()
- | Match branches ->
- fun () ->
- List.for_all
- (fun br -> self#visit_texpression env br.branch ())
- branches
- end
- in
- visitor#visit_texpression () e ()
-
-(** Filter the useless assignments (removes the useless variables, filters
- the function calls) *)
-let filter_useless (filter_monadic_calls : bool) (ctx : trans_ctx)
- (def : fun_decl) : fun_decl =
- (* We first need a transformation on *left-values*, which filters the useless
- * variables and tells us whether the value contains any variable which has
- * not been replaced by [_] (in which case we need to keep the assignment,
- * etc.).
- *
- * This is implemented as a map-reduce.
- *
- * Returns: ( filtered_left_value, *all_dummies* )
- *
- * [all_dummies]:
- * If the returned boolean is true, it means that all the variables appearing
- * in the filtered left-value are *dummies* (meaning that if this left-value
- * appears at the left of a let-binding, this binding might potentially be
- * removed).
- *)
- let lv_visitor =
- object
- inherit [_] mapreduce_typed_pattern
- method zero _ = true
- method plus b0 b1 _ = b0 () && b1 ()
-
- method! visit_PatVar env v mp =
- if VarId.Set.mem v.id env then (PatVar (v, mp), fun _ -> false)
- else (PatDummy, fun _ -> true)
- end
- in
- let filter_typed_pattern (used_vars : VarId.Set.t) (lv : typed_pattern) :
- typed_pattern * bool =
- let lv, all_dummies = lv_visitor#visit_typed_pattern used_vars lv in
- (lv, all_dummies ())
- in
-
- (* We then implement the transformation on *expressions* through a mapreduce.
- * Note that the transformation is bottom-up.
- * The map filters the useless assignments, the reduce computes the set of
- * used variables.
- *)
- let expr_visitor =
- object (self)
- inherit [_] mapreduce_expression as super
- method zero _ = VarId.Set.empty
- method plus s0 s1 _ = VarId.Set.union (s0 ()) (s1 ())
-
- (** Whenever we visit a variable, we need to register the used variable *)
- method! visit_Var _ vid = (Var vid, fun _ -> VarId.Set.singleton vid)
-
- method! visit_expression env e =
- match e with
- | Var _ | Const _ | App _ | Qualif _
- | Switch (_, _)
- | Meta (_, _)
- | Abs _ ->
- super#visit_expression env e
- | Let (monadic, lv, re, e) ->
- (* Compute the set of values used in the next expression *)
- let e, used = self#visit_texpression env e in
- let used = used () in
- (* Filter the left values *)
- let lv, all_dummies = filter_typed_pattern used lv in
- (* Small utility - called if we can't filter the let-binding *)
- let dont_filter () =
- let re, used_re = self#visit_texpression env re in
- let used = VarId.Set.union used (used_re ()) in
- (Let (monadic, lv, re, e), fun _ -> used)
- in
- (* Potentially filter the let-binding *)
- if all_dummies then
- if not monadic then
- (* Not a monadic let-binding: simple case *)
- (e.e, fun _ -> used)
- else
- (* Monadic let-binding: trickier.
- * We can filter if the right-expression is a function call,
- * under some conditions. *)
- match (filter_monadic_calls, opt_destruct_function_call re) with
- | true, Some (func, tys, args) ->
- (* We need to check if there is a child call - see
- * the comments for:
- * [expression_contains_child_call_in_all_paths] *)
- let has_child_call =
- expression_contains_child_call_in_all_paths ctx func tys
- args e
- in
- if has_child_call then (* Filter *)
- (e.e, fun _ -> used)
- else (* No child call: don't filter *)
- dont_filter ()
- | _ ->
- (* Not a call or not allowed to filter: we can't filter *)
- dont_filter ()
- else (* There are used variables: don't filter *)
- dont_filter ()
- end
- in
- (* We filter only inside of transparent (i.e., non-opaque) definitions *)
- match def.body with
- | None -> def
- | Some body ->
- (* Visit the body *)
- let body_exp, used_vars = expr_visitor#visit_texpression () body.body in
- (* Visit the parameters - TODO: update: we can filter only if the definition
- * is not recursive (otherwise it might mess up with the decrease clauses:
- * the decrease clauses uses all the inputs given to the function, if some
- * inputs are replaced by '_' we can't give it to the function used in the
- * decreases clause).
- * For now we deactivate the filtering. *)
- let used_vars = used_vars () in
- let inputs_lvs =
- if false then
- List.map
- (fun lv -> fst (filter_typed_pattern used_vars lv))
- body.inputs_lvs
- else body.inputs_lvs
- in
- (* Return *)
- let body = { body with body = body_exp; inputs_lvs } in
- { def with body = Some body }
-
-(** Simplify the aggregated ADTs.
- Ex.:
- {[
- type struct = { f0 : nat; f1 : nat }
-
- Mkstruct x.f0 x.f1 ~~> x
- ]}
-
- TODO: introduce a notation for [{ x with field = ... }], and use it.
- *)
-let simplify_aggregates (ctx : trans_ctx) (def : fun_decl) : fun_decl =
- let expr_visitor =
- object
- inherit [_] map_expression as super
-
- (* Look for a type constructor applied to arguments *)
- method! visit_texpression env e =
- match e.e with
- | App _ -> (
- let app, args = destruct_apps e in
- match app.e with
- | Qualif
- {
- id = AdtCons { adt_id = AdtId adt_id; variant_id = None };
- type_args;
- } ->
- (* This is a struct *)
- (* Retrieve the definiton, to find how many fields there are *)
- let adt_decl =
- TypeDeclId.Map.find adt_id ctx.type_context.type_decls
- in
- let fields =
- match adt_decl.kind with
- | Enum _ | Opaque -> raise (Failure "Unreachable")
- | Struct fields -> fields
- in
- let num_fields = List.length fields in
- (* In order to simplify, there must be as many arguments as
- * there are fields *)
- assert (num_fields > 0);
- if num_fields = List.length args then
- (* We now need to check that all the arguments are of the form:
- * [x.field] for some variable [x], and where the projection
- * is for the proper ADT *)
- let to_var_proj (i : int) (arg : texpression) :
- (ty list * var_id) option =
- match arg.e with
- | App (proj, x) -> (
- match (proj.e, x.e) with
- | ( Qualif
- {
- id =
- Proj { adt_id = AdtId proj_adt_id; field_id };
- type_args = proj_type_args;
- },
- Var v ) ->
- (* We check that this is the proper ADT, and the proper field *)
- if
- proj_adt_id = adt_id
- && FieldId.to_int field_id = i
- then Some (proj_type_args, v)
- else None
- | _ -> None)
- | _ -> None
- in
- let args = List.mapi to_var_proj args in
- let args = List.filter_map (fun x -> x) args in
- (* Check that all the arguments are of the expected form *)
- if List.length args = num_fields then
- (* Check that this is the same variable we project from -
- * note that we checked above that there is at least one field *)
- let (_, x), end_args = Collections.List.pop args in
- if List.for_all (fun (_, y) -> y = x) end_args then (
- (* We can substitute *)
- (* Sanity check: all types correct *)
- assert (
- List.for_all (fun (tys, _) -> tys = type_args) args);
- { e with e = Var x })
- else super#visit_texpression env e
- else super#visit_texpression env e
- else super#visit_texpression env e
- | _ -> super#visit_texpression env e)
- | _ -> super#visit_texpression env e
- end
- in
- match def.body with
- | None -> def
- | Some body ->
- (* Visit the body *)
- let body_exp = expr_visitor#visit_texpression () body.body in
- (* Return *)
- let body = { body with body = body_exp } in
- { def with body = Some body }
-
-(** Return [None] if the function is a backward function with no outputs (so
- that we eliminate the definition which is useless).
-
- Note that the calls to such functions are filtered when translating from
- symbolic to pure. Here, we remove the definitions altogether, because they
- are now useless
- *)
-let filter_if_backward_with_no_outputs (config : config) (def : fun_decl) :
- fun_decl option =
- if
- config.filter_useless_functions && Option.is_some def.back_id
- && def.signature.output = mk_result_ty mk_unit_ty
- then None
- else Some def
-
-(** Return [false] if the forward function is useless and should be filtered.
-
- - a forward function with no output (comes from a Rust function with
- unit return type)
- - the function has mutable borrows as inputs (which is materialized
- by the fact we generated backward functions which were not filtered).
-
- In such situation, every call to the Rust function will be translated to:
- - a call to the forward function which returns nothing
- - calls to the backward functions
- As a failing backward function implies the forward function also fails,
- we can filter the calls to the forward function, which thus becomes
- useless.
- In such situation, we can remove the forward function definition
- altogether.
- *)
-let keep_forward (config : config) (trans : pure_fun_translation) : bool =
- let fwd, backs = trans in
- (* Note that at this point, the output types are no longer seen as tuples:
- * they should be lists of length 1. *)
- if
- config.filter_useless_functions
- && fwd.signature.output = mk_result_ty mk_unit_ty
- && backs <> []
- then false
- else true
-
-(** Convert the unit variables to [()] if they are used as right-values or
- [_] if they are used as left values in patterns. *)
-let unit_vars_to_unit (def : fun_decl) : fun_decl =
- (* The map visitor *)
- let obj =
- object
- inherit [_] map_expression as super
-
- (** Replace in patterns *)
- method! visit_PatVar _ v mp =
- if v.ty = mk_unit_ty then PatDummy else PatVar (v, mp)
-
- (** Replace in "regular" expressions - note that we could limit ourselves
- to variables, but this is more powerful
- *)
- method! visit_texpression env e =
- if e.ty = mk_unit_ty then mk_unit_rvalue
- else super#visit_texpression env e
- end
- in
- (* Update the body *)
- match def.body with
- | None -> def
- | Some body ->
- let body_exp = obj#visit_texpression () body.body in
- (* Update the input parameters *)
- let inputs_lvs = List.map (obj#visit_typed_pattern ()) body.inputs_lvs in
- (* Return *)
- let body = Some { body with body = body_exp; inputs_lvs } in
- { def with body }
-
-(** Eliminate the box functions like [Box::new], [Box::deref], etc. Most of them
- are translated to identity, and [Box::free] is translated to [()].
-
- Note that the box types have already been eliminated during the translation
- from symbolic to pure.
- The reason why we don't eliminate the box functions at the same time is
- that we would need to eliminate them in two different places: when translating
- function calls, and when translating end abstractions. Here, we can do
- something simpler, in one micro-pass.
- *)
-let eliminate_box_functions (_ctx : trans_ctx) (def : fun_decl) : fun_decl =
- (* The map visitor *)
- let obj =
- object
- inherit [_] map_expression as super
-
- method! visit_texpression env e =
- match opt_destruct_function_call e with
- | Some (fun_id, _tys, args) -> (
- match fun_id with
- | Regular (A.Assumed aid, rg_id) -> (
- (* Below, when dealing with the arguments: we consider the very
- * general case, where functions could be boxed (meaning we
- * could have: [box_new f x])
- * *)
- match (aid, rg_id) with
- | A.BoxNew, _ ->
- assert (rg_id = None);
- let arg, args = Collections.List.pop args in
- mk_apps arg args
- | A.BoxDeref, None ->
- (* [Box::deref] forward is the identity *)
- let arg, args = Collections.List.pop args in
- mk_apps arg args
- | A.BoxDeref, Some _ ->
- (* [Box::deref] backward is [()] (doesn't give back anything) *)
- assert (args = []);
- mk_unit_rvalue
- | A.BoxDerefMut, None ->
- (* [Box::deref_mut] forward is the identity *)
- let arg, args = Collections.List.pop args in
- mk_apps arg args
- | A.BoxDerefMut, Some _ ->
- (* [Box::deref_mut] back is almost the identity:
- * let box_deref_mut (x_init : t) (x_back : t) : t = x_back
- * *)
- let arg, args =
- match args with
- | _ :: given_back :: args -> (given_back, args)
- | _ -> failwith "Unreachable"
- in
- mk_apps arg args
- | A.BoxFree, _ ->
- assert (args = []);
- mk_unit_rvalue
- | ( ( A.Replace | A.VecNew | A.VecPush | A.VecInsert | A.VecLen
- | A.VecIndex | A.VecIndexMut ),
- _ ) ->
- super#visit_texpression env e)
- | _ -> super#visit_texpression env e)
- | _ -> super#visit_texpression env e
- end
- in
- (* Update the body *)
- match def.body with
- | None -> def
- | Some body ->
- let body = Some { body with body = obj#visit_texpression () body.body } in
- { def with body }
-
-(** Decompose the monadic let-bindings.
-
- See the explanations in [config].
- *)
-let decompose_monadic_let_bindings (_ctx : trans_ctx) (def : fun_decl) :
- fun_decl =
- match def.body with
- | None -> def
- | Some body ->
- (* Set up the var id generator *)
- let cnt = get_body_min_var_counter body in
- let _, fresh_id = VarId.mk_stateful_generator cnt in
- (* It is a very simple map *)
- let obj =
- object (self)
- inherit [_] map_expression as super
-
- method! visit_Let env monadic lv re next_e =
- if not monadic then super#visit_Let env monadic lv re next_e
- else
- (* If monadic, we need to check if the left-value is a variable:
- * - if yes, don't decompose
- * - if not, make the decomposition in two steps
- *)
- match lv.value with
- | PatVar _ ->
- (* Variable: nothing to do *)
- super#visit_Let env monadic lv re next_e
- | _ ->
- (* Not a variable: decompose *)
- (* Introduce a temporary variable to receive the value of the
- * monadic binding *)
- let vid = fresh_id () in
- let tmp : var = { id = vid; basename = None; ty = lv.ty } in
- let ltmp = mk_typed_pattern_from_var tmp None in
- let rtmp = mk_texpression_from_var tmp in
- (* Visit the next expression *)
- let next_e = self#visit_texpression env next_e in
- (* Create the let-bindings *)
- (mk_let true ltmp re (mk_let false lv rtmp next_e)).e
- end
- in
- (* Update the body *)
- let body = Some { body with body = obj#visit_texpression () body.body } in
- (* Return *)
- { def with body }
-
-(** Unfold the monadic let-bindings to explicit matches. *)
-let unfold_monadic_let_bindings (_ctx : trans_ctx) (def : fun_decl) : fun_decl =
- match def.body with
- | None -> def
- | Some body ->
- (* It is a very simple map *)
- let obj =
- object (_self)
- inherit [_] map_expression as super
-
- method! visit_Let env monadic lv re e =
- (* We simply do the following transformation:
- {[
- pat <-- re; e
-
- ~~>
-
- match re with
- | Fail err -> Fail err
- | Return pat -> e
- ]}
- *)
- (* TODO: we should use a monad "kind" instead of a boolean *)
- if not monadic then super#visit_Let env monadic lv re e
- else
- (* We don't do the same thing if we use a state-error monad or simply
- an error monad.
- Note that some functions always live in the error monad (arithmetic
- operations, for instance).
- *)
- (* TODO: this information should be computed in SymbolicToPure and
- * store in an enum ("monadic" should be an enum, not a bool). *)
- let re_ty = Option.get (opt_destruct_result re.ty) in
- assert (lv.ty = re_ty);
- let fail_pat = mk_result_fail_pattern lv.ty in
- let fail_value = mk_result_fail_texpression e.ty in
- let fail_branch = { pat = fail_pat; branch = fail_value } in
- let success_pat = mk_result_return_pattern lv in
- let success_branch = { pat = success_pat; branch = e } in
- let switch_body = Match [ fail_branch; success_branch ] in
- let e = Switch (re, switch_body) in
- (* Continue *)
- super#visit_expression env e
- end
- in
- (* Update the body *)
- let body_e = obj#visit_texpression () body.body in
- let body = { body with body = body_e } in
- (* Return *)
- { def with body = Some body }
-
-(** Apply all the micro-passes to a function.
-
- Will return [None] if the function is a backward function with no outputs.
-
- [ctx]: used only for printing.
- *)
-let apply_passes_to_def (config : config) (ctx : trans_ctx) (def : fun_decl) :
- fun_decl option =
- (* Debug *)
- log#ldebug
- (lazy
- ("PureMicroPasses.apply_passes_to_def: "
- ^ Print.fun_name_to_string def.basename
- ^ " ("
- ^ Print.option_to_string T.RegionGroupId.to_string def.back_id
- ^ ")"));
-
- (* First, find names for the variables which are unnamed *)
- let def = compute_pretty_names def in
- log#ldebug
- (lazy ("compute_pretty_name:\n\n" ^ fun_decl_to_string ctx def ^ "\n"));
-
- (* TODO: we might want to leverage more the assignment meta-data, for
- * aggregates for instance. *)
-
- (* TODO: reorder the branches of the matches/switches *)
-
- (* The meta-information is now useless: remove it.
- * Rk.: some passes below use the fact that we removed the meta-data
- * (otherwise we would have to "unmeta" expressions before matching) *)
- let def = remove_meta def in
- log#ldebug (lazy ("remove_meta:\n\n" ^ fun_decl_to_string ctx def ^ "\n"));
-
- (* Remove the backward functions with no outputs.
- * Note that the calls to those functions should already have been removed,
- * when translating from symbolic to pure. Here, we remove the definitions
- * altogether, because they are now useless *)
- let def = filter_if_backward_with_no_outputs config def in
-
- match def with
- | None -> None
- | Some def ->
- (* Convert the unit variables to [()] if they are used as right-values or
- * [_] if they are used as left values. *)
- let def = unit_vars_to_unit def in
- log#ldebug
- (lazy ("unit_vars_to_unit:\n\n" ^ fun_decl_to_string ctx def ^ "\n"));
-
- (* Inline the useless variable reassignments *)
- let inline_named_vars = true in
- let inline_pure = true in
- let def =
- inline_useless_var_reassignments inline_named_vars inline_pure def
- in
- log#ldebug
- (lazy
- ("inline_useless_var_assignments:\n\n" ^ fun_decl_to_string ctx def
- ^ "\n"));
-
- (* Eliminate the box functions - note that the "box" types were eliminated
- * during the symbolic to pure phase: see the comments for [eliminate_box_functions] *)
- let def = eliminate_box_functions ctx def in
- log#ldebug
- (lazy
- ("eliminate_box_functions:\n\n" ^ fun_decl_to_string ctx def ^ "\n"));
-
- (* Filter the useless variables, assignments, function calls, etc. *)
- let def = filter_useless config.filter_useless_monadic_calls ctx def in
- log#ldebug
- (lazy ("filter_useless:\n\n" ^ fun_decl_to_string ctx def ^ "\n"));
-
- (* Simplify the aggregated ADTs.
- Ex.:
- {[
- type struct = { f0 : nat; f1 : nat }
-
- Mkstruct x.f0 x.f1 ~~> x
- ]}
- *)
- let def = simplify_aggregates ctx def in
- log#ldebug
- (lazy ("simplify_aggregates:\n\n" ^ fun_decl_to_string ctx def ^ "\n"));
-
- (* Decompose the monadic let-bindings - F* specific
- * TODO: remove? *)
- let def =
- if config.decompose_monadic_let_bindings then (
- let def = decompose_monadic_let_bindings ctx def in
- log#ldebug
- (lazy
- ("decompose_monadic_let_bindings:\n\n"
- ^ fun_decl_to_string ctx def ^ "\n"));
- def)
- else (
- log#ldebug
- (lazy
- "ignoring decompose_monadic_let_bindings due to the configuration\n");
- def)
- in
-
- (* Unfold the monadic let-bindings *)
- let def =
- if config.unfold_monadic_let_bindings then (
- let def = unfold_monadic_let_bindings ctx def in
- log#ldebug
- (lazy
- ("unfold_monadic_let_bindings:\n\n" ^ fun_decl_to_string ctx def
- ^ "\n"));
- def)
- else (
- log#ldebug
- (lazy
- "ignoring unfold_monadic_let_bindings due to the configuration\n");
- def)
- in
-
- (* We are done *)
- Some def
-
-(** Return the forward/backward translations on which we applied the micro-passes.
-
- Also returns a boolean indicating whether the forward function should be kept
- or not (because useful/useless - [true] means we need to keep the forward
- function).
- Note that we don't "filter" the forward function and return a boolean instead,
- because this function contains useful information to extract the backward
- functions: keeping it is not necessary but more convenient.
- *)
-let apply_passes_to_pure_fun_translation (config : config) (ctx : trans_ctx)
- (trans : pure_fun_translation) : bool * pure_fun_translation =
- (* Apply the passes to the individual functions *)
- let forward, backwards = trans in
- let forward = Option.get (apply_passes_to_def config ctx forward) in
- let backwards = List.filter_map (apply_passes_to_def config ctx) backwards in
- let trans = (forward, backwards) in
- (* Compute whether we need to filter the forward function or not *)
- (keep_forward config trans, trans)