summaryrefslogtreecommitdiff
path: root/compiler/Values.ml
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/Values.ml')
-rw-r--r--compiler/Values.ml844
1 files changed, 844 insertions, 0 deletions
diff --git a/compiler/Values.ml b/compiler/Values.ml
new file mode 100644
index 00000000..e404f40d
--- /dev/null
+++ b/compiler/Values.ml
@@ -0,0 +1,844 @@
+open Identifiers
+open Types
+
+(* TODO: I often write "abstract" (value, borrow content, etc.) while I should
+ * write "abstraction" (because those values are not abstract, they simply are
+ * inside abstractions) *)
+
+module VarId = IdGen ()
+module BorrowId = IdGen ()
+module SymbolicValueId = IdGen ()
+module AbstractionId = IdGen ()
+module FunCallId = IdGen ()
+
+(** A variable *)
+
+type big_int = Z.t
+
+let big_int_of_yojson (json : Yojson.Safe.t) : (big_int, string) result =
+ match json with
+ | `Int i -> Ok (Z.of_int i)
+ | `Intlit is -> Ok (Z.of_string is)
+ | _ -> Error "not an integer or an integer literal"
+
+let big_int_to_yojson (i : big_int) = `Intlit (Z.to_string i)
+
+let pp_big_int (fmt : Format.formatter) (bi : big_int) : unit =
+ Format.pp_print_string fmt (Z.to_string bi)
+
+let show_big_int (bi : big_int) : string = Z.to_string bi
+
+(** A scalar value
+
+ Note that we use unbounded integers everywhere.
+ We then harcode the boundaries for the different types.
+ *)
+type scalar_value = { value : big_int; int_ty : integer_type } [@@deriving show]
+
+(** A constant value *)
+type constant_value =
+ | Scalar of scalar_value
+ | Bool of bool
+ | Char of char
+ | String of string
+[@@deriving show]
+
+(** The kind of a symbolic value, which precises how the value was generated *)
+type sv_kind =
+ | FunCallRet (** The value is the return value of a function call *)
+ | FunCallGivenBack
+ (** The value is a borrowed value given back by an abstraction
+ (happens when giving a borrow to a function: when the abstraction
+ introduced to model the function call ends we reintroduce a symbolic
+ value in the context for the value modified by the abstraction through
+ the borrow).
+ *)
+ | SynthInput
+ (** The value is an input value of the function whose body we are
+ currently synthesizing.
+ *)
+ | SynthRetGivenBack
+ (** The value is a borrowed value that the function whose body we are
+ synthesizing returned, and which was given back because we ended
+ one of the lifetimes of this function (we do this to synthesize
+ the backward functions).
+ *)
+ | SynthInputGivenBack
+ (** The value was given back upon ending one of the input abstractions *)
+ | Global (** The value is a global *)
+[@@deriving show]
+
+(** A symbolic value *)
+type symbolic_value = {
+ sv_kind : sv_kind;
+ sv_id : SymbolicValueId.id;
+ sv_ty : rty;
+}
+[@@deriving show]
+
+(** Ancestor for {!typed_value} iter visitor *)
+class ['self] iter_typed_value_base =
+ object (_self : 'self)
+ inherit [_] VisitorsRuntime.iter
+ method visit_constant_value : 'env -> constant_value -> unit = fun _ _ -> ()
+ method visit_erased_region : 'env -> erased_region -> unit = fun _ _ -> ()
+ method visit_symbolic_value : 'env -> symbolic_value -> unit = fun _ _ -> ()
+ method visit_ety : 'env -> ety -> unit = fun _ _ -> ()
+ end
+
+(** Ancestor for {!typed_value} map visitor for *)
+class ['self] map_typed_value_base =
+ object (_self : 'self)
+ inherit [_] VisitorsRuntime.map
+
+ method visit_constant_value : 'env -> constant_value -> constant_value =
+ fun _ cv -> cv
+
+ method visit_erased_region : 'env -> erased_region -> erased_region =
+ fun _ r -> r
+
+ method visit_symbolic_value : 'env -> symbolic_value -> symbolic_value =
+ fun _ sv -> sv
+
+ method visit_ety : 'env -> ety -> ety = fun _ ty -> ty
+ end
+
+(** An untyped value, used in the environments *)
+type value =
+ | Concrete of constant_value (** Concrete (non-symbolic) value *)
+ | Adt of adt_value (** Enumerations and structures *)
+ | Bottom (** No value (uninitialized or moved value) *)
+ | Borrow of borrow_content (** A borrowed value *)
+ | Loan of loan_content (** A loaned value *)
+ | Symbolic of symbolic_value
+ (** Borrow projector over a symbolic value.
+
+ Note that contrary to the abstraction-values case, symbolic values
+ appearing in regular values are interpreted as *borrow* projectors,
+ they can never be *loan* projectors.
+ *)
+
+and adt_value = {
+ variant_id : (VariantId.id option[@opaque]);
+ field_values : typed_value list;
+}
+
+and borrow_content =
+ | SharedBorrow of mvalue * (BorrowId.id[@opaque])
+ (** A shared borrow.
+
+ We remember the shared value which was borrowed as a meta value.
+ This is necessary for synthesis: upon translating to "pure" values,
+ we can't perform any lookup because we don't have an environment
+ anymore. Note that it is ok to keep the shared value and copy
+ the shared value this way, because shared values are immutable
+ for as long as they are shared (i.e., as long as we can use the
+ shared borrow).
+ *)
+ | MutBorrow of (BorrowId.id[@opaque]) * typed_value
+ (** A mutably borrowed value. *)
+ | InactivatedMutBorrow of mvalue * (BorrowId.id[@opaque])
+ (** An inactivated mut borrow.
+
+ This is used to model {{: https://rustc-dev-guide.rust-lang.org/borrow_check/two_phase_borrows.html} two-phase borrows}.
+ When evaluating a two-phase mutable borrow, we first introduce an inactivated
+ borrow which behaves like a shared borrow, until the moment we actually *use*
+ the borrow: at this point, we end all the other shared borrows (or inactivated
+ borrows - though there shouldn't be any other inactivated borrows if the program
+ is well typed) of this value and replace the inactivated borrow with a
+ mutable borrow.
+
+ A simple use case of two-phase borrows:
+ {[
+ let mut v = Vec::new();
+ v.push(v.len());
+ ]}
+
+ This gets desugared to (something similar to) the following MIR:
+ {[
+ v = Vec::new();
+ v1 = &mut v;
+ v2 = &v; // We need this borrow, but v has already been mutably borrowed!
+ l = Vec::len(move v2);
+ Vec::push(move v1, move l); // In practice, v1 gets activated only here
+ ]}
+
+ The meta-value is used for the same purposes as with shared borrows,
+ at the exception that in case of inactivated borrows it is not
+ *necessary* for the synthesis: we keep it only as meta-information.
+ To be more precise:
+ - when generating the synthesized program, we may need to convert
+ shared borrows to pure values
+ - we never need to do so for inactivated borrows: such borrows must
+ be activated at the moment we use them (meaning we convert a *mutable*
+ borrow to a pure value). However, we save meta-data about the assignments,
+ which is used to make the code cleaner: when generating this meta-data,
+ we may need to convert inactivated borrows to pure values, in which
+ situation we convert the meta-value we stored in the inactivated
+ borrow.
+ *)
+
+and loan_content =
+ | SharedLoan of (BorrowId.Set.t[@opaque]) * typed_value
+ | MutLoan of (BorrowId.id[@opaque])
+ (** TODO: we might want to add a set of borrow ids (useful for inactivated
+ borrows, and extremely useful when giving shared values to abstractions).
+ *)
+
+(** "Meta"-value: information we store for the synthesis.
+
+ Note that we never automatically visit the meta-values with the
+ visitors: they really are meta information, and shouldn't be considered
+ as part of the environment during a symbolic execution.
+
+ TODO: we may want to create wrappers, to prevent accidently mixing meta
+ values and regular values.
+ *)
+and mvalue = typed_value
+
+(** "Regular" typed value (we map variables to typed values) *)
+and typed_value = { value : value; ty : ety }
+[@@deriving
+ show,
+ visitors
+ {
+ name = "iter_typed_value_visit_mvalue";
+ variety = "iter";
+ ancestors = [ "iter_typed_value_base" ];
+ nude = true (* Don't inherit {!VisitorsRuntime.iter} *);
+ concrete = true;
+ },
+ visitors
+ {
+ name = "map_typed_value_visit_mvalue";
+ variety = "map";
+ ancestors = [ "map_typed_value_base" ];
+ nude = true (* Don't inherit {!VisitorsRuntime.iter} *);
+ concrete = true;
+ }]
+
+(** We have to override the {!iter_typed_value_visit_mvalue.visit_mvalue} method,
+ to ignore meta-values *)
+class ['self] iter_typed_value =
+ object (_self : 'self)
+ inherit [_] iter_typed_value_visit_mvalue
+ method! visit_mvalue : 'env -> mvalue -> unit = fun _ _ -> ()
+ end
+
+(** We have to override the {!iter_typed_value_visit_mvalue.visit_mvalue} method,
+ to ignore meta-values *)
+class ['self] map_typed_value =
+ object (_self : 'self)
+ inherit [_] map_typed_value_visit_mvalue
+ method! visit_mvalue : 'env -> mvalue -> mvalue = fun _ x -> x
+ end
+
+(** "Meta"-symbolic value.
+
+ See the explanations for {!mvalue}
+
+ TODO: we may want to create wrappers, to prevent mixing meta values
+ and regular values.
+ *)
+type msymbolic_value = symbolic_value [@@deriving show]
+
+(** When giving shared borrows to functions (i.e., inserting shared borrows inside
+ abstractions) we need to reborrow the shared values. When doing so, we lookup
+ the shared values and apply some special projections to the shared value
+ (until we can't go further, i.e., we find symbolic values which may get
+ expanded upon reading them later), which don't generate avalues but
+ sets of borrow ids and symbolic values.
+
+ Note that as shared values can't get modified it is ok to forget the
+ structure of the values we projected, and only keep the set of borrows
+ (and symbolic values).
+
+ TODO: we may actually need to remember the structure, in order to know
+ which borrows are inside which other borrows...
+*)
+type abstract_shared_borrow =
+ | AsbBorrow of (BorrowId.id[@opaque])
+ | AsbProjReborrows of (symbolic_value[@opaque]) * (rty[@opaque])
+[@@deriving show]
+
+(** A set of abstract shared borrows *)
+type abstract_shared_borrows = abstract_shared_borrow list [@@deriving show]
+
+(** Ancestor for {!aproj} iter visitor *)
+class ['self] iter_aproj_base =
+ object (_self : 'self)
+ inherit [_] iter_typed_value
+ method visit_rty : 'env -> rty -> unit = fun _ _ -> ()
+
+ method visit_msymbolic_value : 'env -> msymbolic_value -> unit =
+ fun _ _ -> ()
+ end
+
+(** Ancestor for {!aproj} map visitor *)
+class ['self] map_aproj_base =
+ object (_self : 'self)
+ inherit [_] map_typed_value
+ method visit_rty : 'env -> rty -> rty = fun _ ty -> ty
+
+ method visit_msymbolic_value : 'env -> msymbolic_value -> msymbolic_value =
+ fun _ m -> m
+ end
+
+type aproj =
+ | AProjLoans of symbolic_value * (msymbolic_value * aproj) list
+ (** A projector of loans over a symbolic value.
+
+ Note that the borrows of a symbolic value may be spread between
+ different abstractions, meaning that the projector of loans might
+ receive *several* (symbolic) given back values.
+
+ This is the case in the following example:
+ {[
+ fn f<'a> (...) -> (&'a mut u32, &'a mut u32);
+ fn g<'b, 'c>(p : (&'b mut u32, &'c mut u32));
+
+ let p = f(...);
+ g(move p);
+
+ // Symbolic context after the call to g:
+ // abs'a {'a} { [s@0 <: (&'a mut u32, &'a mut u32)] }
+ //
+ // abs'b {'b} { (s@0 <: (&'b mut u32, &'c mut u32)) }
+ // abs'c {'c} { (s@0 <: (&'b mut u32, &'c mut u32)) }
+ ]}
+
+ Upon evaluating the call to [f], we introduce a symbolic value [s@0]
+ and a projector of loans (projector loans from the region 'c).
+ This projector will later receive two given back values: one for
+ 'a and one for 'b.
+
+ We accumulate those values in the list of projections (note that
+ the meta value stores the value which was given back).
+
+ We can later end the projector of loans if [s@0] is not referenced
+ anywhere in the context below a projector of borrows which intersects
+ this projector of loans.
+ *)
+ | AProjBorrows of symbolic_value * rty
+ (** Note that an AProjBorrows only operates on a value which is not below
+ a shared loan: under a shared loan, we use {!abstract_shared_borrow}.
+
+ Also note that once given to a borrow projection, a symbolic value
+ can't get updated/expanded: this means that we don't need to save
+ any meta-value here.
+ *)
+ | AEndedProjLoans of msymbolic_value * (msymbolic_value * aproj) list
+ (** An ended projector of loans over a symbolic value.
+
+ See the explanations for {!AProjLoans}
+
+ Note that we keep the original symbolic value as a meta-value.
+ *)
+ | AEndedProjBorrows of msymbolic_value
+ (** The only purpose of {!AEndedProjBorrows} is to store, for synthesis
+ purposes, the symbolic value which was generated and given back upon
+ ending the borrow.
+ *)
+ | AIgnoredProjBorrows
+[@@deriving
+ show,
+ visitors
+ {
+ name = "iter_aproj";
+ variety = "iter";
+ ancestors = [ "iter_aproj_base" ];
+ nude = true (* Don't inherit {!VisitorsRuntime.iter} *);
+ concrete = true;
+ },
+ visitors
+ {
+ name = "map_aproj";
+ variety = "map";
+ ancestors = [ "map_aproj_base" ];
+ nude = true (* Don't inherit {!VisitorsRuntime.iter} *);
+ concrete = true;
+ }]
+
+type region = RegionVarId.id Types.region [@@deriving show]
+
+(** Ancestor for {!typed_avalue} iter visitor *)
+class ['self] iter_typed_avalue_base =
+ object (_self : 'self)
+ inherit [_] iter_aproj
+ method visit_id : 'env -> BorrowId.id -> unit = fun _ _ -> ()
+ method visit_region : 'env -> region -> unit = fun _ _ -> ()
+
+ method visit_abstract_shared_borrows
+ : 'env -> abstract_shared_borrows -> unit =
+ fun _ _ -> ()
+ end
+
+(** Ancestor for {!typed_avalue} map visitor *)
+class ['self] map_typed_avalue_base =
+ object (_self : 'self)
+ inherit [_] map_aproj
+ method visit_id : 'env -> BorrowId.id -> BorrowId.id = fun _ id -> id
+ method visit_region : 'env -> region -> region = fun _ r -> r
+
+ method visit_abstract_shared_borrows
+ : 'env -> abstract_shared_borrows -> abstract_shared_borrows =
+ fun _ asb -> asb
+ end
+
+(** Abstraction values are used inside of abstractions to properly model
+ borrowing relations introduced by function calls.
+
+ When calling a function, we lose information about the borrow graph:
+ part of it is thus "abstracted" away.
+*)
+type avalue =
+ | AConcrete of constant_value
+ (** TODO: remove. We actually don't use that for the synthesis, but the
+ meta-values.
+
+ Note that this case is not used in the projections to keep track of the
+ borrow graph (because there are no borrows in "concrete" values!) but
+ to correctly instantiate the backward functions (we may give back some
+ values at different moments: we need to remember what those values were
+ precisely). Also note that even though avalues and values are not the
+ same, once values are projected to avalues, those avalues still have
+ the structure of the original values (this is necessary, again, to
+ correctly instantiate the backward functions)
+ *)
+ | AAdt of adt_avalue
+ | ABottom
+ | ALoan of aloan_content
+ | ABorrow of aborrow_content
+ | ASymbolic of aproj
+ | AIgnored
+ (** A value which doesn't contain borrows, or which borrows we
+ don't own and thus ignore *)
+
+and adt_avalue = {
+ variant_id : (VariantId.id option[@opaque]);
+ field_values : typed_avalue list;
+}
+
+(** A loan content as stored in an abstraction.
+
+ Note that the children avalues are independent of the parent avalues.
+ For instance, the child avalue contained in an {!AMutLoan} will likely
+ contain other, independent loans.
+ Keeping track of the hierarchy is not necessary to maintain the borrow graph
+ (which is the primary role of the abstractions), but it is necessary
+ to properly instantiate the backward functions when generating the pure
+ translation.
+*)
+and aloan_content =
+ | AMutLoan of (BorrowId.id[@opaque]) * typed_avalue
+ (** A mutable loan owned by an abstraction.
+
+ Example:
+ ========
+ {[
+ fn f<'a>(...) -> &'a mut &'a mut u32;
+
+ let px = f(...);
+ ]}
+
+ We get (after some symbolic exansion):
+ {[
+ abs0 {
+ a_mut_loan l0 (a_mut_loan l1)
+ }
+ px -> mut_borrow l0 (mut_borrow @s1)
+ ]}
+ *)
+ | ASharedLoan of (BorrowId.Set.t[@opaque]) * typed_value * typed_avalue
+ (** A shared loan owned by an abstraction.
+
+ Example:
+ ========
+ {[
+ fn f<'a>(...) -> &'a u32;
+
+ let px = f(...);
+ ]}
+
+ We get:
+ {[
+ abs0 { a_shared_loan {l0} @s0 ⊥ }
+ px -> shared_loan l0
+ ]}
+ *)
+ | AEndedMutLoan of {
+ child : typed_avalue;
+ given_back : typed_avalue;
+ given_back_meta : mvalue;
+ }
+ (** An ended mutable loan in an abstraction.
+ We need it because abstractions must keep track of the values
+ we gave back to them, so that we can correctly instantiate
+ backward functions.
+
+ Rk.: *DO NOT* use [visit_AEndedMutLoan]. If we update the order of
+ the arguments and you forget to swap them at the level of
+ [visit_AEndedMutLoan], you will not notice it.
+
+ Example:
+ ========
+ {[
+ abs0 { a_mut_loan l0 ⊥ }
+ x -> mut_borrow l0 (U32 3)
+ ]}
+
+ After ending [l0]:
+
+ {[
+ abs0 { a_ended_mut_loan { given_back = U32 3; child = ⊥; }
+ x -> ⊥
+ ]}
+ *)
+ | AEndedSharedLoan of typed_value * typed_avalue
+ (** Similar to {!AEndedMutLoan} but in this case there are no avalues to
+ give back. We keep the shared value because it now behaves as a
+ "regular" value (which contains borrows we might want to end...).
+ *)
+ | AIgnoredMutLoan of (BorrowId.id[@opaque]) * typed_avalue
+ (** An ignored mutable loan.
+
+ We need to keep track of ignored mutable loans, because we may have
+ to apply projections on the values given back to those loans (say
+ you have a borrow of type [&'a mut &'b mut], in the abstraction 'b,
+ the outer loan is ignored, however you need to keep track of it so
+ that when ending the borrow corresponding to 'a you can correctly
+ project on the inner value).
+
+ Example:
+ ========
+ {[
+ fn f<'a,'b>(...) -> &'a mut &'b mut u32;
+ let x = f(...);
+
+ > abs'a { a_mut_loan l0 (a_ignored_mut_loan l1 ⊥) }
+ > abs'b { a_ignored_mut_loan l0 (a_mut_loan l1 ⊥) }
+ > x -> mut_borrow l0 (mut_borrow l1 @s1)
+ ]}
+ *)
+ | AEndedIgnoredMutLoan of {
+ child : typed_avalue;
+ given_back : typed_avalue;
+ given_back_meta : mvalue;
+ }
+ (** Similar to {!AEndedMutLoan}, for ignored loans.
+
+ Rk.: *DO NOT* use [visit_AEndedIgnoredMutLoan].
+ See the comment for {!AEndedMutLoan}.
+ *)
+ | AIgnoredSharedLoan of typed_avalue
+ (** An ignored shared loan.
+
+ Example:
+ ========
+ {[
+ fn f<'a,'b>(...) -> &'a &'b u32;
+ let x = f(...);
+
+ > abs'a { a_shared_loan {l0} (shared_borrow l1) (a_ignored_shared_loan ⊥) }
+ > abs'b { a_ignored_shared_loan (a_shared_loan {l1} @s1 ⊥) }
+ > x -> shared_borrow l0
+ ]}
+ *)
+
+(** Note that when a borrow content is ended, it is replaced by ⊥ (while
+ we need to track ended loans more precisely, especially because of their
+ children values).
+
+ Note that contrary to {!aloan_content}, here the children avalues are
+ not independent of the parent avalues. For instance, a value
+ [AMutBorrow (_, AMutBorrow (_, ...)] (ignoring the types) really is
+ to be seen like a [mut_borrow ... (mut_borrow ...)].
+
+ TODO: be more precise about the ignored borrows (keep track of the borrow
+ ids)?
+*)
+and aborrow_content =
+ | AMutBorrow of mvalue * (BorrowId.id[@opaque]) * typed_avalue
+ (** A mutable borrow owned by an abstraction.
+
+ Is used when an abstraction "consumes" borrows, when giving borrows
+ as arguments to a function.
+
+ Example:
+ ========
+ {[
+ fn f<'a>(px : &'a mut u32);
+
+ > x -> mut_loan l0
+ > px -> mut_borrow l0 (U32 0)
+
+ f(move px);
+
+ > x -> mut_loan l0
+ > px -> ⊥
+ > abs0 { a_mut_borrow l0 (U32 0) }
+ ]}
+
+ The meta-value stores the initial value on which the projector was
+ applied, which reduced to this mut borrow. This meta-information
+ is only used for the synthesis.
+ TODO: do we really use it actually?
+ *)
+ | ASharedBorrow of (BorrowId.id[@opaque])
+ (** A shared borrow owned by an abstraction.
+
+ Example:
+ ========
+ {[
+ fn f<'a>(px : &'a u32);
+
+ > x -> shared_loan {l0} (U32 0)
+ > px -> shared_borrow l0
+
+ f(move px);
+
+ > x -> shared_loan {l0} (U32 0)
+ > px -> ⊥
+ > abs0 { a_shared_borrow l0 }
+ ]}
+ *)
+ | AIgnoredMutBorrow of BorrowId.id option * typed_avalue
+ (** An ignored mutable borrow.
+
+ We need to keep track of ignored mut borrows because when ending such
+ borrows, we need to project the loans of the given back value to
+ insert them in the proper abstractions.
+
+ Note that we need to do so only for borrows consumed by parent
+ abstractions (hence the optional borrow id).
+
+ TODO: the below explanations are obsolete
+
+ Example:
+ ========
+ {[
+ fn f<'a,'b>(ppx : &'a mut &'b mut u32);
+
+ > x -> mut_loan l0
+ > px -> mut_loan l1
+ > ppx -> mut_borrow l1 (mut_borrow l0 (U32 0))
+
+ f(move ppx);
+
+ > x -> mut_loan l0
+ > px -> mut_loan l1
+ > ppx -> ⊥
+ > abs'a { a_mut_borrow l1 (a_ignored_mut_borrow None (U32 0)) } // TODO: duplication
+ > abs'b {parents={abs'a}} { a_ignored_mut_borrow (Some l1) (a_mut_borrow l0 (U32 0)) }
+
+ ... // abs'a ends
+
+ > x -> mut_loan l0
+ > px -> @s0
+ > ppx -> ⊥
+ > abs'b {
+ > a_ended_ignored_mut_borrow (a_proj_loans (@s0 <: &'b mut u32)) // <-- loan projector
+ > (a_mut_borrow l0 (U32 0))
+ > }
+
+ ... // [@s0] gets expanded to [&mut l2 @s1]
+
+ > x -> mut_loan l0
+ > px -> &mut l2 @s1
+ > ppx -> ⊥
+ > abs'b {
+ > a_ended_ignored_mut_borrow (a_mut_loan l2) // <-- loan l2 is here
+ > (a_mut_borrow l0 (U32 0))
+ > }
+
+ ]}
+
+ Note that we could use AIgnoredMutLoan in the case the borrow id is not
+ None, which would allow us to simplify the rules (to not have rules
+ to specifically handle the case of AIgnoredMutBorrow with Some borrow
+ id) and also remove the AEndedIgnoredMutBorrow variant.
+ For now, the rules are implemented and it allows us to make the avalues
+ more precise and clearer, so we will keep it that way.
+
+ TODO: this is annoying, we are duplicating information. Maybe we
+ could introduce an "Ignored" value? We have to pay attention to
+ two things:
+ - introducing ⊥ when ignoring a value is not always possible, because
+ we check whether the borrowed value contains ⊥ when giving back a
+ borrowed value (if it is the case we give back ⊥, otherwise we
+ introduce a symbolic value). This is necessary when ending nested
+ borrows with the same lifetime: when ending the inner borrow we
+ actually give back a value, however when ending the outer borrow
+ we need to give back ⊥.
+ TODO: actually we don't do that anymore, we check if the borrowed
+ avalue contains ended regions (which is cleaner and more robust).
+ - we may need to remember the precise values given to the
+ abstraction so that we can properly call the backward functions
+ when generating the pure translation.
+ *)
+ | AEndedMutBorrow of msymbolic_value * typed_avalue
+ (** The sole purpose of {!AEndedMutBorrow} is to store the (symbolic) value
+ that we gave back as a meta-value, to help with the synthesis.
+
+ We also remember the child {!avalue} because this structural information
+ is useful for the synthesis (but not for the symbolic execution):
+ in practice the child value should only contain ended borrows, ignored
+ values, bottom values, etc.
+ *)
+ | AEndedSharedBorrow
+ (** We don't really need {!AEndedSharedBorrow}: we simply want to be
+ precise, and not insert ⊥ when ending borrows.
+ *)
+ | AEndedIgnoredMutBorrow of {
+ child : typed_avalue;
+ given_back_loans_proj : typed_avalue;
+ given_back_meta : msymbolic_value;
+ (** [given_back_meta] is used to store the (symbolic) value we gave back
+ upon ending the borrow.
+
+ Rk.: *DO NOT* use [visit_AEndedIgnoredMutLoan].
+ See the comment for {!AEndedMutLoan}.
+ *)
+ } (** See the explanations for {!AIgnoredMutBorrow} *)
+ | AProjSharedBorrow of abstract_shared_borrows
+ (** A projected shared borrow.
+
+ When giving shared borrows as arguments to function calls, we
+ introduce new borrows to keep track of the fact that the function
+ might reborrow values inside. Note that as shared values are immutable,
+ we don't really need to remember the structure of the shared values.
+
+ Example:
+ ========
+ Below, when calling [f], we need to introduce one shared borrow per
+ borrow in the argument.
+ {[
+ fn f<'a,'b>(pppx : &'a &'b &'c mut u32);
+
+ > x -> mut_loan l0
+ > px -> shared_loan {l1} (mut_borrow l0 (U32 0))
+ > ppx -> shared_loan {l2} (shared_borrow l1)
+ > pppx -> shared_borrow l2
+
+ f(move pppx);
+
+ > x -> mut_loan l0
+ > px -> shared_loan {l1, l3, l4} (mut_borrow l0 (U32 0))
+ > ppx -> shared_loan {l2} (shared_borrow l1)
+ > pppx -> ⊥
+ > abs'a { a_proj_shared_borrow {l2} }
+ > abs'b { a_proj_shared_borrow {l3} } // l3 reborrows l1
+ > abs'c { a_proj_shared_borrow {l4} } // l4 reborrows l0
+ ]}
+ *)
+
+(* TODO: the type of avalues doesn't make sense for loan avalues: they currently
+ are typed as [& (mut) T] instead of [T]...
+*)
+and typed_avalue = { value : avalue; ty : rty }
+[@@deriving
+ show,
+ visitors
+ {
+ name = "iter_typed_avalue";
+ variety = "iter";
+ ancestors = [ "iter_typed_avalue_base" ];
+ nude = true (* Don't inherit {!VisitorsRuntime.iter} *);
+ concrete = true;
+ },
+ visitors
+ {
+ name = "map_typed_avalue";
+ variety = "map";
+ ancestors = [ "map_typed_avalue_base" ];
+ nude = true (* Don't inherit {!VisitorsRuntime.iter} *);
+ concrete = true;
+ }]
+
+(** The kind of an abstraction, which keeps track of its origin *)
+type abs_kind =
+ | FunCall (** The abstraction was introduced because of a function call *)
+ | SynthInput
+ (** The abstraction keeps track of the input values of the function
+ we are currently synthesizing. *)
+ | SynthRet
+ (** The abstraction "absorbed" the value returned by the function we
+ are currently synthesizing *)
+[@@deriving show]
+
+(** Abstractions model the parts in the borrow graph where the borrowing relations
+ have been abstracted because of a function call.
+
+ In order to model the relations between the borrows, we use "abstraction values",
+ which are a special kind of value.
+*)
+type abs = {
+ abs_id : (AbstractionId.id[@opaque]);
+ call_id : (FunCallId.id[@opaque]);
+ (** The identifier of the function call which introduced this
+ abstraction. This is not used by the symbolic execution:
+ this is only used for pretty-printing and debugging, in the
+ symbolic AST, generated by the symbolic execution.
+ *)
+ back_id : (RegionGroupId.id[@opaque]);
+ (** The region group id to which this abstraction is linked.
+
+ In most situations, it gives the id of the backward function (hence
+ the name), but it is a bit more subtle in the case of synth input
+ and synth ret abstractions.
+
+ This is not used by the symbolic execution: it is a utility for
+ the symbolic AST, generated by the symbolic execution.
+ *)
+ kind : (abs_kind[@opaque]);
+ can_end : (bool[@opaque]);
+ (** Controls whether the region can be ended or not.
+
+ This allows to "pin" some regions, and is useful when generating
+ backward functions.
+
+ For instance, if we have: [fn f<'a, 'b>(...) -> (&'a mut T, &'b mut T)],
+ when generating the backward function for 'a, we have to make sure we
+ don't need to end the return region for 'b (if it is the case, it means
+ the function doesn't borrow check).
+ *)
+ parents : (AbstractionId.Set.t[@opaque]); (** The parent abstractions *)
+ original_parents : (AbstractionId.id list[@opaque]);
+ (** The original list of parents, ordered. This is used for synthesis. *)
+ regions : (RegionId.Set.t[@opaque]); (** Regions owned by this abstraction *)
+ ancestors_regions : (RegionId.Set.t[@opaque]);
+ (** Union of the regions owned by this abstraction's ancestors (not
+ including the regions of this abstraction itself) *)
+ avalues : typed_avalue list; (** The values in this abstraction *)
+}
+[@@deriving
+ show,
+ visitors
+ {
+ name = "iter_abs";
+ variety = "iter";
+ ancestors = [ "iter_typed_avalue" ];
+ nude = true (* Don't inherit {!VisitorsRuntime.iter} *);
+ concrete = true;
+ },
+ visitors
+ {
+ name = "map_abs";
+ variety = "map";
+ ancestors = [ "map_typed_avalue" ];
+ nude = true (* Don't inherit {!VisitorsRuntime.iter} *);
+ concrete = true;
+ }]
+
+(** A symbolic expansion
+
+ A symbolic expansion doesn't represent a value, but rather an operation
+ that we apply to values.
+
+ TODO: this should rather be name "expanded_symbolic"
+ *)
+type symbolic_expansion =
+ | SeConcrete of constant_value
+ | SeAdt of (VariantId.id option * symbolic_value list)
+ | SeMutRef of BorrowId.id * symbolic_value
+ | SeSharedRef of BorrowId.Set.t * symbolic_value