open Types open Values open Contexts open Cps open ValuesUtils open TypesUtils open InterpreterUtils open InterpreterBorrowsCore open InterpreterProjectors open Errors (** The local logger *) let log = Logging.borrows_log (** Auxiliary function to end borrows: lookup a borrow in the environment, update it (by returning an updated environment where the borrow has been replaced by {!Bottom})) if we can end the borrow (for instance, it is not an outer borrow...) or return the reason why we couldn't update the borrow. [end_borrow_aux] then simply performs a loop: as long as we need to end (outer) borrows, we end them, before finally ending the borrow we wanted to end in the first place. - [allowed_abs]: if not [None], allows to get a borrow in the given abstraction without ending the whole abstraction first. This parameter allows us to use {!end_borrow_aux} as an auxiliary function for {!end_abstraction_aux} (we end all the borrows in the abstraction one by one before removing the abstraction from the context). - [allow_inner_loans]: if [true], allow to end borrows containing inner loans. This is used to merge borrows with abstractions, to compute loop fixed points for instance. *) let end_borrow_get_borrow (span : Meta.span) (allowed_abs : AbstractionId.id option) (allow_inner_loans : bool) (l : BorrowId.id) (ctx : eval_ctx) : ( eval_ctx * (AbstractionId.id option * g_borrow_content) option, priority_borrows_or_abs ) result = (* We use a reference to communicate the kind of borrow we found, if we * find one *) let replaced_bc : (AbstractionId.id option * g_borrow_content) option ref = ref None in let set_replaced_bc (abs_id : AbstractionId.id option) (bc : g_borrow_content) = sanity_check __FILE__ __LINE__ (Option.is_none !replaced_bc) span; replaced_bc := Some (abs_id, bc) in (* Raise an exception if: * - there are outer borrows * - if we are inside an abstraction * - there are inner loans * this exception is caught in a wrapper function *) let raise_if_priority (outer : AbstractionId.id option * borrow_ids option) (borrowed_value : typed_value option) = (* First, look for outer borrows or abstraction *) let outer_abs, outer_borrows = outer in (match outer_abs with | Some abs -> ( if (* Check if we can end borrows inside this abstraction *) Some abs <> allowed_abs then raise (FoundPriority (OuterAbs abs)) else match outer_borrows with | Some borrows -> raise (FoundPriority (OuterBorrows borrows)) | None -> ()) | None -> ( match outer_borrows with | Some borrows -> raise (FoundPriority (OuterBorrows borrows)) | None -> ())); (* Then check if there are inner loans *) if not allow_inner_loans then match borrowed_value with | None -> () | Some v -> ( match get_first_loan_in_value v with | None -> () | Some c -> ( match c with | VSharedLoan (bids, _) -> raise (FoundPriority (InnerLoans (Borrows bids))) | VMutLoan bid -> raise (FoundPriority (InnerLoans (Borrow bid)))) ) in (* The environment is used to keep track of the outer loans *) let obj = object inherit [_] map_eval_ctx as super (** We reimplement {!visit_Loan} because we may have to update the outer borrows *) method! visit_VLoan (outer : AbstractionId.id option * borrow_ids option) lc = match lc with | VMutLoan bid -> VLoan (super#visit_VMutLoan outer bid) | VSharedLoan (bids, v) -> (* Update the outer borrows before diving into the shared value *) let outer = update_outer_borrows outer (Borrows bids) in VLoan (super#visit_VSharedLoan outer bids v) method! visit_VBorrow outer bc = match bc with | VSharedBorrow l' | VReservedMutBorrow l' -> (* Check if this is the borrow we are looking for *) if l = l' then ( (* Check if there are outer borrows or if we are inside an abstraction *) raise_if_priority outer None; (* Register the update *) set_replaced_bc (fst outer) (Concrete bc); (* Update the value *) VBottom) else super#visit_VBorrow outer bc | VMutBorrow (l', bv) -> (* Check if this is the borrow we are looking for *) if l = l' then ( (* Check if there are outer borrows or if we are inside an abstraction *) raise_if_priority outer (Some bv); (* Register the update *) set_replaced_bc (fst outer) (Concrete bc); (* Update the value *) VBottom) else (* Update the outer borrows before diving into the borrowed value *) let outer = update_outer_borrows outer (Borrow l') in VBorrow (super#visit_VMutBorrow outer l' bv) (** We reimplement {!visit_ALoan} because we may have to update the outer borrows *) method! visit_ALoan outer lc = (* Note that the children avalues are just other, independent loans, * so we don't need to update the outer borrows when diving in. * We keep track of the parents/children relationship only because we * need it to properly instantiate the backward functions when generating * the pure translation. *) match lc with | AMutLoan (pm, _, _) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; (* Nothing special to do *) super#visit_ALoan outer lc | ASharedLoan (pm, bids, v, av) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; (* Explore the shared value - we need to update the outer borrows *) let souter = update_outer_borrows outer (Borrows bids) in let v = super#visit_typed_value souter v in (* Explore the child avalue - we keep the same outer borrows *) let av = super#visit_typed_avalue outer av in (* Reconstruct *) ALoan (ASharedLoan (pm, bids, v, av)) | AEndedMutLoan { given_back = _; child = _; given_back_span = _ } | AEndedSharedLoan _ (* The loan has ended, so no need to update the outer borrows *) | AIgnoredMutLoan _ (* Nothing special to do *) | AEndedIgnoredMutLoan { given_back = _; child = _; given_back_span = _ } (* Nothing special to do *) | AIgnoredSharedLoan _ -> (* Nothing special to do *) super#visit_ALoan outer lc method! visit_ABorrow outer bc = match bc with | AMutBorrow (pm, bid, _) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; (* Check if this is the borrow we are looking for *) if bid = l then ( (* TODO: treat this case differently. We should not introduce a bottom value. *) (* When ending a mut borrow, there are two cases: * - in the general case, we have to end the whole abstraction * (and thus raise an exception to signal that to the caller) * - in some situations, the associated loan is inside the same * abstraction as the borrow. In this situation, we can end * the borrow without ending the whole abstraction, and we * simply move the child avalue around. *) (* Check there are outer borrows, or if we need to end the whole * abstraction *) raise_if_priority outer None; (* Register the update *) set_replaced_bc (fst outer) (Abstract bc); (* Update the value - note that we are necessarily in the second * of the two cases described above. * Also note that, as we are moving the borrowed value inside the * abstraction (and not really giving the value back to the context) * we do not insert {!AEndedMutBorrow} but rather {!ABottom} *) craise __FILE__ __LINE__ span "Unimplemented" (* ABottom *)) else (* Update the outer borrows before diving into the child avalue *) let outer = update_outer_borrows outer (Borrow bid) in super#visit_ABorrow outer bc | ASharedBorrow (pm, bid) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; (* Check if this is the borrow we are looking for *) if bid = l then ( (* Check there are outer borrows, or if we need to end the whole * abstraction *) raise_if_priority outer None; (* Register the update *) set_replaced_bc (fst outer) (Abstract bc); (* Update the value - note that we are necessarily in the second * of the two cases described above *) ABottom) else super#visit_ABorrow outer bc | AIgnoredMutBorrow (_, _) | AEndedMutBorrow _ | AEndedIgnoredMutBorrow { given_back = _; child = _; given_back_span = _ } | AEndedSharedBorrow -> (* Nothing special to do *) super#visit_ABorrow outer bc | AProjSharedBorrow asb -> (* Check if the borrow we are looking for is in the asb *) if borrow_in_asb l asb then ( (* Check there are outer borrows, or if we need to end the whole * abstraction *) raise_if_priority outer None; (* Register the update *) set_replaced_bc (fst outer) (Abstract bc); (* Update the value - note that we are necessarily in the second * of the two cases described above *) let asb = remove_borrow_from_asb span l asb in ABorrow (AProjSharedBorrow asb)) else (* Nothing special to do *) super#visit_ABorrow outer bc method! visit_abs outer abs = (* Update the outer abs *) let outer_abs, outer_borrows = outer in sanity_check __FILE__ __LINE__ (Option.is_none outer_abs) span; sanity_check __FILE__ __LINE__ (Option.is_none outer_borrows) span; let outer = (Some abs.abs_id, None) in super#visit_abs outer abs end in (* Catch the exceptions - raised if there are outer borrows *) try let ctx = obj#visit_eval_ctx (None, None) ctx in Ok (ctx, !replaced_bc) with FoundPriority outers -> Error outers (** Auxiliary function to end borrows. See {!give_back}. When we end a mutable borrow, we need to "give back" the value it contained to its original owner by reinserting it at the proper position. Note that this function checks that there is exactly one loan to which we give the value back. TODO: this was not the case before, so some sanity checks are not useful anymore. *) let give_back_value (config : config) (span : Meta.span) (bid : BorrowId.id) (nv : typed_value) (ctx : eval_ctx) : eval_ctx = (* Sanity check *) exec_assert __FILE__ __LINE__ (not (loans_in_value nv)) span "Can not end a borrow because the value to give back contains bottom"; exec_assert __FILE__ __LINE__ (not (bottom_in_value ctx.ended_regions nv)) span "Can not end a borrow because the value to give back contains bottom"; (* Debug *) log#ldebug (lazy ("give_back_value:\n- bid: " ^ BorrowId.to_string bid ^ "\n- value: " ^ typed_value_to_string ~span:(Some span) ctx nv ^ "\n- context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx ^ "\n")); (* We use a reference to check that we updated exactly one loan *) let replaced : bool ref = ref false in let set_replaced () = sanity_check __FILE__ __LINE__ (not !replaced) span; replaced := true in (* Whenever giving back symbolic values, they shouldn't contain already ended regions *) let check_symbolic_no_ended = true in (* We sometimes need to reborrow values while giving a value back due: prepare that *) let allow_reborrows = true in let fresh_reborrow, apply_registered_reborrows = prepare_reborrows config span allow_reborrows in (* The visitor to give back the values *) let obj = object (self) inherit [_] map_eval_ctx as super (** This is a bit annoying, but as we need the type of the value we are exploring, for sanity checks, we need to implement {!visit_typed_avalue} instead of overriding {!visit_ALoan} *) method! visit_typed_value opt_abs (v : typed_value) : typed_value = match v.value with | VLoan lc -> let value = self#visit_typed_Loan opt_abs v.ty lc in ({ v with value } : typed_value) | _ -> super#visit_typed_value opt_abs v method visit_typed_Loan opt_abs ty lc = match lc with | VSharedLoan (bids, v) -> (* We are giving back a value (i.e., the content of a *mutable* * borrow): nothing special to do *) VLoan (super#visit_VSharedLoan opt_abs bids v) | VMutLoan bid' -> (* Check if this is the loan we are looking for *) if bid' = bid then ( (* Sanity check *) let expected_ty = ty in if nv.ty <> expected_ty then craise __FILE__ __LINE__ span ("Value given back doesn't have the proper type:\n\ - expected: " ^ ty_to_string ctx ty ^ "\n- received: " ^ ty_to_string ctx nv.ty); (* Replace *) set_replaced (); nv.value) else VLoan (super#visit_VMutLoan opt_abs bid') (** This is a bit annoying, but as we need the type of the avalue we are exploring, in order to be able to project the value we give back, we need to reimplement {!visit_typed_avalue} instead of {!visit_ALoan} *) method! visit_typed_avalue opt_abs (av : typed_avalue) : typed_avalue = match av.value with | ALoan lc -> let value = self#visit_typed_ALoan opt_abs av.ty lc in ({ av with value } : typed_avalue) | _ -> super#visit_typed_avalue opt_abs av (** We need to inspect ignored mutable borrows, to insert loan projectors if necessary. *) method! visit_ABorrow (opt_abs : abs option) (bc : aborrow_content) : avalue = match bc with | AIgnoredMutBorrow (bid', child) -> if bid' = Some bid then (* Insert a loans projector - note that if this case happens, * it is necessarily because we ended a parent abstraction, * and the given back value is thus a symbolic value *) match nv.value with | VSymbolic sv -> let abs = Option.get opt_abs in (* Remember the given back value as a meta-value * TODO: it is a bit annoying to have to deconstruct * the value... Think about a more elegant way. *) let given_back_span = as_symbolic span nv.value in (* The loan projector *) let given_back = mk_aproj_loans_value_from_symbolic_value abs.regions sv in (* Continue giving back in the child value *) let child = super#visit_typed_avalue opt_abs child in (* Return *) ABorrow (AEndedIgnoredMutBorrow { given_back; child; given_back_span }) | _ -> craise __FILE__ __LINE__ span "Unreachable" else (* Continue exploring *) ABorrow (super#visit_AIgnoredMutBorrow opt_abs bid' child) | _ -> (* Continue exploring *) super#visit_ABorrow opt_abs bc (** We are not specializing an already existing method, but adding a new method (for projections, we need type information) *) method visit_typed_ALoan (opt_abs : abs option) (ty : rty) (lc : aloan_content) : avalue = (* Preparing a bit *) let regions, ancestors_regions = match opt_abs with | None -> craise __FILE__ __LINE__ span "Unreachable" | Some abs -> (abs.regions, abs.ancestors_regions) in (* Rk.: there is a small issue with the types of the aloan values. * See the comment at the level of definition of {!typed_avalue} *) let borrowed_value_aty = let _, ty, _ = ty_get_ref ty in ty in match lc with | AMutLoan (pm, bid', child) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; if bid' = bid then ( (* This is the loan we are looking for: apply the projection to * the value we give back and replaced this mutable loan with * an ended loan *) (* Register the insertion *) set_replaced (); (* Remember the given back value as a meta-value *) let given_back_span = nv in (* Apply the projection *) let given_back = apply_proj_borrows span check_symbolic_no_ended ctx fresh_reborrow regions ancestors_regions nv borrowed_value_aty in (* Continue giving back in the child value *) let child = super#visit_typed_avalue opt_abs child in (* Return the new value *) ALoan (AEndedMutLoan { child; given_back; given_back_span })) else (* Continue exploring *) super#visit_ALoan opt_abs lc | ASharedLoan (pm, _, _, _) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; (* We are giving back a value to a *mutable* loan: nothing special to do *) super#visit_ALoan opt_abs lc | AEndedMutLoan { child = _; given_back = _; given_back_span = _ } | AEndedSharedLoan (_, _) -> (* Nothing special to do *) super#visit_ALoan opt_abs lc | AIgnoredMutLoan (opt_bid, child) -> (* This loan is ignored, but we may have to project on a subvalue * of the value which is given back *) if opt_bid = Some bid then (* Remember the given back value as a meta-value *) let given_back_span = nv in (* Note that we replace the ignored mut loan by an *ended* ignored * mut loan. Also, this is not the loan we are looking for *per se*: * we don't register the fact that we inserted the value somewhere * (i.e., we don't call {!set_replaced}) *) let given_back = apply_proj_borrows span check_symbolic_no_ended ctx fresh_reborrow regions ancestors_regions nv borrowed_value_aty in (* Continue giving back in the child value *) let child = super#visit_typed_avalue opt_abs child in ALoan (AEndedIgnoredMutLoan { given_back; child; given_back_span }) else super#visit_ALoan opt_abs lc | AEndedIgnoredMutLoan { given_back = _; child = _; given_back_span = _ } | AIgnoredSharedLoan _ -> (* Nothing special to do *) super#visit_ALoan opt_abs lc method! visit_EAbs opt_abs abs = (* We remember in which abstraction we are before diving - * this is necessary for projecting values: we need to know * over which regions to project *) sanity_check __FILE__ __LINE__ (Option.is_none opt_abs) span; super#visit_EAbs (Some abs) abs end in (* Explore the environment *) let ctx = obj#visit_eval_ctx None ctx in (* Check we gave back to exactly one loan *) cassert __FILE__ __LINE__ !replaced span "No loan updated"; (* Apply the reborrows *) apply_registered_reborrows ctx (** Give back a *modified* symbolic value. *) let give_back_symbolic_value (_config : config) (span : Meta.span) (proj_regions : RegionId.Set.t) (proj_ty : rty) (sv : symbolic_value) (nsv : symbolic_value) (ctx : eval_ctx) : eval_ctx = (* Sanity checks *) sanity_check __FILE__ __LINE__ (sv.sv_id <> nsv.sv_id && ty_is_rty proj_ty) span; (* Store the given-back value as a meta-value for synthesis purposes *) let mv = nsv in (* Substitution function, to replace the borrow projectors over symbolic values *) let subst (_abs : abs) local_given_back = (* See the below comments: there is something wrong here *) let _ = raise Utils.Unimplemented in (* Compute the projection over the given back value *) let child_proj = (* TODO: there is something wrong here. Consider this: {[ abs0 {'a} { AProjLoans (s0 : &'a mut T) [] } abs1 {'b} { AProjBorrows (s0 : &'a mut T <: &'b mut T) } ]} Upon ending abs1, we give back some fresh symbolic value [s1], that we reinsert where the loan for [s0] is. However, the mutable borrow in the type [&'a mut T] was ended: we give back a value of type [T]! We thus *mustn't* introduce a projector here. *) (* AProjBorrows (nsv, sv.sv_ty) *) internal_error __FILE__ __LINE__ span in AProjLoans (sv, (mv, child_proj) :: local_given_back) in update_intersecting_aproj_loans span proj_regions proj_ty sv subst ctx (** Auxiliary function to end borrows. See {!give_back}. This function is similar to {!give_back_value} but gives back an {!avalue} (coming from an abstraction). It is used when ending a borrow inside an abstraction, when the corresponding loan is inside the same abstraction (in which case we don't need to end the whole abstraction). REMARK: this function can't be used to give back the values borrowed by end abstraction when ending this abstraction. When doing this, we need to convert the {!avalue} to a {!type:value} by introducing the proper symbolic values. *) let give_back_avalue_to_same_abstraction (_config : config) (span : Meta.span) (bid : BorrowId.id) (nv : typed_avalue) (nsv : typed_value) (ctx : eval_ctx) : eval_ctx = (* We use a reference to check that we updated exactly one loan *) let replaced : bool ref = ref false in let set_replaced () = cassert __FILE__ __LINE__ (not !replaced) span "Exacly one loan should be updated"; replaced := true in let obj = object (self) inherit [_] map_eval_ctx as super (** This is a bit annoying, but as we need the type of the avalue we are exploring, in order to be able to project the value we give back, we need to reimplement {!visit_typed_avalue} instead of {!visit_ALoan}. TODO: it is possible to do this by remembering the type of the last typed avalue we entered. *) method! visit_typed_avalue opt_abs (av : typed_avalue) : typed_avalue = match av.value with | ALoan lc -> let value = self#visit_typed_ALoan opt_abs av.ty lc in ({ av with value } : typed_avalue) | _ -> super#visit_typed_avalue opt_abs av (** We are not specializing an already existing method, but adding a new method (for projections, we need type information). TODO: it is possible to do this by remembering the type of the last typed avalue we entered. *) method visit_typed_ALoan (opt_abs : abs option) (ty : rty) (lc : aloan_content) : avalue = match lc with | AMutLoan (pm, bid', child) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; if bid' = bid then ( (* Sanity check - about why we need to call {!ty_get_ref} * (and don't do the same thing as in {!give_back_value}) * see the comment at the level of the definition of * {!typed_avalue} *) let _, expected_ty, _ = ty_get_ref ty in if nv.ty <> expected_ty then craise __FILE__ __LINE__ span ("Value given back doesn't have the proper type:\n\ - expected: " ^ ty_to_string ctx ty ^ "\n- received: " ^ ty_to_string ctx nv.ty); (* This is the loan we are looking for: apply the projection to * the value we give back and replaced this mutable loan with * an ended loan *) (* Register the insertion *) set_replaced (); (* Return the new value *) ALoan (AEndedMutLoan { given_back = nv; child; given_back_span = nsv })) else (* Continue exploring *) super#visit_ALoan opt_abs lc | ASharedLoan (PNone, _, _, _) (* We are giving back a value to a *mutable* loan: nothing special to do *) | AEndedMutLoan { given_back = _; child = _; given_back_span = _ } | AEndedSharedLoan (_, _) -> (* Nothing special to do *) super#visit_ALoan opt_abs lc | ASharedLoan (_, _, _, _) -> (* We get there if the projection marker is not [PNone] *) internal_error __FILE__ __LINE__ span | AIgnoredMutLoan (bid_opt, child) -> (* This loan is ignored, but we may have to project on a subvalue * of the value which is given back *) if bid_opt = Some bid then ( (* Note that we replace the ignored mut loan by an *ended* ignored * mut loan. Also, this is not the loan we are looking for *per se*: * we don't register the fact that we inserted the value somewhere * (i.e., we don't call {!set_replaced}) *) (* Sanity check *) sanity_check __FILE__ __LINE__ (nv.ty = ty) span; ALoan (AEndedIgnoredMutLoan { given_back = nv; child; given_back_span = nsv })) else super#visit_ALoan opt_abs lc | AEndedIgnoredMutLoan { given_back = _; child = _; given_back_span = _ } | AIgnoredSharedLoan _ -> (* Nothing special to do *) super#visit_ALoan opt_abs lc end in (* Explore the environment *) let ctx = obj#visit_eval_ctx None ctx in (* Check we gave back to exactly one loan *) cassert __FILE__ __LINE__ !replaced span "No loan updated"; (* Return *) ctx (** Auxiliary function to end borrows. See {!give_back}. When we end a shared borrow, we need to remove the borrow id from the list of borrows to the shared value. Note that this function checks that there is exactly one shared loan that we update. TODO: this was not the case before, so some sanity checks are not useful anymore. *) let give_back_shared _config (span : Meta.span) (bid : BorrowId.id) (ctx : eval_ctx) : eval_ctx = (* We use a reference to check that we updated exactly one loan *) let replaced : bool ref = ref false in let set_replaced () = cassert __FILE__ __LINE__ (not !replaced) span "Exactly one loan should be updated"; replaced := true in let obj = object inherit [_] map_eval_ctx as super method! visit_VLoan opt_abs lc = match lc with | VSharedLoan (bids, shared_value) -> if BorrowId.Set.mem bid bids then ( (* This is the loan we are looking for *) set_replaced (); (* If there remains exactly one borrow identifier, we need * to end the loan. Otherwise, we just remove the current * loan identifier *) if BorrowId.Set.cardinal bids = 1 then shared_value.value else VLoan (VSharedLoan (BorrowId.Set.remove bid bids, shared_value))) else (* Not the loan we are looking for: continue exploring *) VLoan (super#visit_VSharedLoan opt_abs bids shared_value) | VMutLoan bid' -> (* We are giving back a *shared* borrow: nothing special to do *) VLoan (super#visit_VMutLoan opt_abs bid') method! visit_ALoan opt_abs lc = match lc with | AMutLoan (pm, bid, av) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; (* Nothing special to do (we are giving back a *shared* borrow) *) ALoan (super#visit_AMutLoan opt_abs pm bid av) | ASharedLoan (pm, bids, shared_value, child) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; if BorrowId.Set.mem bid bids then ( (* This is the loan we are looking for *) set_replaced (); (* If there remains exactly one borrow identifier, we need * to end the loan. Otherwise, we just remove the current * loan identifier *) if BorrowId.Set.cardinal bids = 1 then ALoan (AEndedSharedLoan (shared_value, child)) else ALoan (ASharedLoan (pm, BorrowId.Set.remove bid bids, shared_value, child))) else (* Not the loan we are looking for: continue exploring *) super#visit_ALoan opt_abs lc | AEndedMutLoan { given_back = _; child = _; given_back_span = _ } (* Nothing special to do (the loan has ended) *) | AEndedSharedLoan (_, _) (* Nothing special to do (the loan has ended) *) | AIgnoredMutLoan (_, _) (* Nothing special to do (we are giving back a *shared* borrow) *) | AEndedIgnoredMutLoan { given_back = _; child = _; given_back_span = _ } (* Nothing special to do *) | AIgnoredSharedLoan _ -> (* Nothing special to do *) super#visit_ALoan opt_abs lc end in (* Explore the environment *) let ctx = obj#visit_eval_ctx None ctx in (* Check we gave back to exactly one loan *) cassert __FILE__ __LINE__ !replaced span "No loan updated"; (* Return *) ctx (** When copying values, we duplicate the shared borrows. This is tantamount to reborrowing the shared value. The following function applies this change to an environment by inserting a new borrow id in the set of borrows tracked by a shared value, referenced by the [original_bid] argument. *) let reborrow_shared (span : Meta.span) (original_bid : BorrowId.id) (new_bid : BorrowId.id) (ctx : eval_ctx) : eval_ctx = (* Keep track of changes *) let r = ref false in let set_ref () = sanity_check __FILE__ __LINE__ (not !r) span; r := true in let obj = object inherit [_] map_env as super method! visit_VSharedLoan env bids sv = (* Shared loan: check if the borrow id we are looking for is in the set of borrow ids. If yes, insert the new borrow id, otherwise explore inside the shared value *) if BorrowId.Set.mem original_bid bids then ( set_ref (); let bids' = BorrowId.Set.add new_bid bids in VSharedLoan (bids', sv)) else super#visit_VSharedLoan env bids sv method! visit_ASharedLoan env pm bids v av = sanity_check __FILE__ __LINE__ (pm = PNone) span; (* This case is similar to the {!SharedLoan} case *) if BorrowId.Set.mem original_bid bids then ( set_ref (); let bids' = BorrowId.Set.add new_bid bids in ASharedLoan (pm, bids', v, av)) else super#visit_ASharedLoan env pm bids v av end in let env = obj#visit_env () ctx.env in (* Check that we reborrowed once *) sanity_check __FILE__ __LINE__ !r span; { ctx with env } (** Convert an {!type:avalue} to a {!type:value}. This function is used when ending abstractions: whenever we end a borrow in an abstraction, we convert the borrowed {!avalue} to a fresh symbolic {!type:value}, then give back this {!type:value} to the context. Note that some regions may have ended in the symbolic value we generate. For instance, consider the following function signature: {[ fn f<'a>(x : &'a mut &'a mut u32); ]} When ending the abstraction, the value given back for the outer borrow should be ⊥. In practice, we will give back a symbolic value which can't be expanded (because expanding this symbolic value would require expanding a reference whose region has already ended). *) let convert_avalue_to_given_back_value (span : Meta.span) (av : typed_avalue) : symbolic_value = mk_fresh_symbolic_value span av.ty (** Auxiliary function: see {!end_borrow_aux}. When we end a mutable borrow, we need to "give back" the value it contained to its original owner by reinserting it at the proper position. Rem.: this function is used when we end *one single* borrow (we don't end this borrow as member of the group of borrows belonging to an abstraction). If the borrow is an "abstract" borrow, it means we are ending a borrow inside an abstraction (we end a borrow whose corresponding loan is in the same abstraction - we are allowed to do so without ending the whole abstraction). TODO: we should not treat this case here, and should only consider internal borrows. This kind of internal reshuffling. should be similar to ending abstractions (it is tantamount to ending *sub*-abstractions). *) let give_back (config : config) (span : Meta.span) (l : BorrowId.id) (bc : g_borrow_content) (ctx : eval_ctx) : eval_ctx = (* Debug *) log#ldebug (lazy (let bc = match bc with | Concrete bc -> borrow_content_to_string ~span:(Some span) ctx bc | Abstract bc -> aborrow_content_to_string ~span:(Some span) ctx bc in "give_back:\n- bid: " ^ BorrowId.to_string l ^ "\n- content: " ^ bc ^ "\n- context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx ^ "\n")); (* This is used for sanity checks *) let sanity_ek = { enter_shared_loans = true; enter_mut_borrows = true; enter_abs = true } in match bc with | Concrete (VMutBorrow (l', tv)) -> (* Sanity check *) sanity_check __FILE__ __LINE__ (l' = l) span; sanity_check __FILE__ __LINE__ (not (loans_in_value tv)) span; (* Check that the corresponding loan is somewhere - purely a sanity check *) sanity_check __FILE__ __LINE__ (Option.is_some (lookup_loan_opt span sanity_ek l ctx)) span; (* Update the context *) give_back_value config span l tv ctx | Concrete (VSharedBorrow l' | VReservedMutBorrow l') -> (* Sanity check *) sanity_check __FILE__ __LINE__ (l' = l) span; (* Check that the borrow is somewhere - purely a sanity check *) sanity_check __FILE__ __LINE__ (Option.is_some (lookup_loan_opt span sanity_ek l ctx)) span; (* Update the context *) give_back_shared config span l ctx | Abstract (AMutBorrow (pm, l', av)) -> (* Sanity check *) sanity_check __FILE__ __LINE__ (pm = PNone) span; sanity_check __FILE__ __LINE__ (l' = l) span; (* Check that the corresponding loan is somewhere - purely a sanity check *) sanity_check __FILE__ __LINE__ (Option.is_some (lookup_loan_opt span sanity_ek l ctx)) span; (* Convert the avalue to a (fresh symbolic) value. Rem.: we shouldn't do this here. We should do this in a function which takes care of ending *sub*-abstractions. *) let sv = convert_avalue_to_given_back_value span av in (* Update the context *) give_back_avalue_to_same_abstraction config span l av (mk_typed_value_from_symbolic_value sv) ctx | Abstract (ASharedBorrow (pm, l')) -> (* Sanity check *) sanity_check __FILE__ __LINE__ (pm = PNone) span; sanity_check __FILE__ __LINE__ (l' = l) span; (* Check that the borrow is somewhere - purely a sanity check *) sanity_check __FILE__ __LINE__ (Option.is_some (lookup_loan_opt span sanity_ek l ctx)) span; (* Update the context *) give_back_shared config span l ctx | Abstract (AProjSharedBorrow asb) -> (* Sanity check *) sanity_check __FILE__ __LINE__ (borrow_in_asb l asb) span; (* Update the context *) give_back_shared config span l ctx | Abstract ( AEndedMutBorrow _ | AIgnoredMutBorrow _ | AEndedIgnoredMutBorrow _ | AEndedSharedBorrow ) -> craise __FILE__ __LINE__ span "Unreachable" let check_borrow_disappeared (span : Meta.span) (fun_name : string) (l : BorrowId.id) (ctx0 : eval_ctx) (ctx : eval_ctx) : unit = (match lookup_borrow_opt span ek_all l ctx with | None -> () (* Ok *) | Some _ -> log#ltrace (lazy (fun_name ^ ": " ^ BorrowId.to_string l ^ ": borrow didn't disappear:\n- original context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx0 ^ "\n\n- new context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx)); internal_error __FILE__ __LINE__ span); match lookup_loan_opt span ek_all l ctx with | None -> () (* Ok *) | Some _ -> log#ltrace (lazy (fun_name ^ ": " ^ BorrowId.to_string l ^ ": loan didn't disappear:\n- original context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx0 ^ "\n\n- new context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx)); internal_error __FILE__ __LINE__ span (** End a borrow identified by its borrow id in a context. This function **preserves invariants** provided [allowed_abs] is [None]: if the borrow is inside another borrow/an abstraction, we end the outer borrow/abstraction first, etc. [allowed_abs]: see the comment for {!end_borrow_get_borrow}. [chain]: contains the list of borrows/abstraction ids on which {!end_borrow_aux} and {!end_abstraction_aux} were called, to remember the chain of calls. This is useful for debugging purposes, and also for sanity checks to detect cycles (which shouldn't happen if the environment is well-formed). Rk.: from now onwards, the functions are written in continuation passing style. The reason is that when ending borrows we may end abstractions, which requires generating code for the translation (and we do this in CPS). TODO: we should split this function in two: one function which doesn't perform anything smart and is trusted, and another function for the book-keeping. *) let rec end_borrow_aux (config : config) (span : Meta.span) (chain : borrow_or_abs_ids) (allowed_abs : AbstractionId.id option) (l : BorrowId.id) : cm_fun = fun ctx -> (* Check that we don't loop *) let chain0 = chain in let chain = add_borrow_or_abs_id_to_chain span "end_borrow_aux: " (BorrowId l) chain in log#ldebug (lazy ("end borrow: " ^ BorrowId.to_string l ^ ":\n- original context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx)); (* Utility function for the sanity checks: check that the borrow disappeared * from the context *) let ctx0 = ctx in let check = check_borrow_disappeared span "end borrow" l ctx0 in (* Start by ending the borrow itself (we lookup it up and replace it with [Bottom] *) let allow_inner_loans = false in match end_borrow_get_borrow span allowed_abs allow_inner_loans l ctx with (* Two cases: - error: we found outer borrows (the borrow is inside a borrowed value) or inner loans (the borrow contains loans) - success: we didn't find outer borrows when updating (but maybe we actually didn't find the borrow we were looking for...). The borrow was successfully replaced with [Bottom], and we can proceed to ending the corresponding loan. Note that if [allowed_abs] is [Some abs_id] and the borrow is inside the abstraction identified by [abs_id], the abstraction is ignored (i.e.: {!end_borrow_get_borrow} won't return [Error] because of the abstraction itself). *) | Error priority -> ( (* Debug *) log#ldebug (lazy ("end borrow: " ^ BorrowId.to_string l ^ ": found outer borrows/abs or inner loans:" ^ show_priority_borrows_or_abs priority)); (* End the priority borrows, abstractions, then try again to end the target * borrow (if necessary) *) match priority with | OuterBorrows (Borrows bids) | InnerLoans (Borrows bids) -> (* Note that we might get there with [allowed_abs <> None]: we might * be trying to end a borrow inside an abstraction, but which is actually * inside another borrow *) let allowed_abs' = None in (* End the outer borrows *) let ctx, cc = end_borrows_aux config span chain allowed_abs' bids ctx in (* Retry to end the borrow *) let ctx, cc = comp cc (end_borrow_aux config span chain0 allowed_abs l ctx) in (* Check and continue *) check ctx; (ctx, cc) | OuterBorrows (Borrow bid) | InnerLoans (Borrow bid) -> let allowed_abs' = None in (* End the outer borrow *) let ctx, cc = end_borrow_aux config span chain allowed_abs' bid ctx in (* Retry to end the borrow *) let ctx, cc = comp cc (end_borrow_aux config span chain0 allowed_abs l ctx) in (* Check and continue *) check ctx; (ctx, cc) | OuterAbs abs_id -> (* The borrow is inside an abstraction: end the whole abstraction *) let ctx, end_abs = end_abstraction_aux config span chain abs_id ctx in (* Sanity check *) check ctx; (ctx, end_abs)) | Ok (ctx, None) -> log#ldebug (lazy "End borrow: borrow not found"); (* It is possible that we can't find a borrow in symbolic mode (ending * an abstraction may end several borrows at once *) sanity_check __FILE__ __LINE__ (config.mode = SymbolicMode) span; (* Do a sanity check and continue *) check ctx; (ctx, fun e -> e) (* We found a borrow and replaced it with [Bottom]: give it back (i.e., update the corresponding loan) *) | Ok (ctx, Some (_, bc)) -> (* Sanity check: the borrowed value shouldn't contain loans *) (match bc with | Concrete (VMutBorrow (_, bv)) -> sanity_check __FILE__ __LINE__ (Option.is_none (get_first_loan_in_value bv)) span | _ -> ()); (* Give back the value *) let ctx = give_back config span l bc ctx in (* Do a sanity check and continue *) check ctx; (* Save a snapshot of the environment for the name generation *) let cc = SynthesizeSymbolic.save_snapshot ctx in (* Compose *) (ctx, cc) and end_borrows_aux (config : config) (span : Meta.span) (chain : borrow_or_abs_ids) (allowed_abs : AbstractionId.id option) (lset : BorrowId.Set.t) : cm_fun = fun ctx -> (* This is not necessary, but we prefer to reorder the borrow ids, so that we actually end from the smallest id to the highest id - just a matter of taste, and may make debugging easier *) let ids = BorrowId.Set.fold (fun id ids -> id :: ids) lset [] in fold_left_apply_continuation (fun id ctx -> end_borrow_aux config span chain allowed_abs id ctx) ids ctx and end_abstraction_aux (config : config) (span : Meta.span) (chain : borrow_or_abs_ids) (abs_id : AbstractionId.id) : cm_fun = fun ctx -> (* Check that we don't loop *) let chain = add_borrow_or_abs_id_to_chain span "end_abstraction_aux: " (AbsId abs_id) chain in (* Remember the original context for printing purposes *) let ctx0 = ctx in log#ldebug (lazy ("end_abstraction_aux: " ^ AbstractionId.to_string abs_id ^ "\n- original context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx0)); (* Lookup the abstraction - note that if we end a list of abstractions, ending one abstraction may lead to the current abstraction having preemptively been ended, so the abstraction might not be in the context anymore. *) match ctx_lookup_abs_opt ctx abs_id with | None -> log#ldebug (lazy ("abs not found (already ended): " ^ AbstractionId.to_string abs_id ^ "\n")); (ctx, fun e -> e) | Some abs -> (* Check that we can end the abstraction *) if abs.can_end then () else craise __FILE__ __LINE__ span ("Can't end abstraction " ^ AbstractionId.to_string abs.abs_id ^ " as it is set as non-endable"); (* End the parent abstractions first *) let ctx, cc = end_abstractions_aux config span chain abs.parents ctx in log#ldebug (lazy ("end_abstraction_aux: " ^ AbstractionId.to_string abs_id ^ "\n- context after parent abstractions ended:\n" ^ eval_ctx_to_string ~span:(Some span) ctx)); (* End the loans inside the abstraction *) let ctx, cc = comp cc (end_abstraction_loans config span chain abs_id ctx) in log#ldebug (lazy ("end_abstraction_aux: " ^ AbstractionId.to_string abs_id ^ "\n- context after loans ended:\n" ^ eval_ctx_to_string ~span:(Some span) ctx)); (* End the abstraction itself by redistributing the borrows it contains *) let ctx, cc = comp cc (end_abstraction_borrows config span chain abs_id ctx) in (* End the regions owned by the abstraction - note that we don't need to relookup the abstraction: the set of regions in an abstraction never changes... *) let ctx = let ended_regions = RegionId.Set.union ctx.ended_regions abs.regions in { ctx with ended_regions } in (* Remove all the references to the id of the current abstraction, and remove the abstraction itself. **Rk.**: this is where we synthesize the updated symbolic AST *) let ctx, cc = comp cc (end_abstraction_remove_from_context config span abs_id ctx) in (* Debugging *) log#ldebug (lazy ("end_abstraction_aux: " ^ AbstractionId.to_string abs_id ^ "\n- original context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx0 ^ "\n\n- new context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx)); (* Sanity check: ending an abstraction must preserve the invariants *) Invariants.check_invariants span ctx; (* Save a snapshot of the environment for the name generation *) let cc = cc_comp cc (SynthesizeSymbolic.save_snapshot ctx) in (* Return *) (ctx, cc) and end_abstractions_aux (config : config) (span : Meta.span) (chain : borrow_or_abs_ids) (abs_ids : AbstractionId.Set.t) : cm_fun = fun ctx -> (* This is not necessary, but we prefer to reorder the abstraction ids, * so that we actually end from the smallest id to the highest id - just * a matter of taste, and may make debugging easier *) let abs_ids = AbstractionId.Set.fold (fun id ids -> id :: ids) abs_ids [] in fold_left_apply_continuation (fun id ctx -> end_abstraction_aux config span chain id ctx) abs_ids ctx and end_abstraction_loans (config : config) (span : Meta.span) (chain : borrow_or_abs_ids) (abs_id : AbstractionId.id) : cm_fun = fun ctx -> (* Lookup the abstraction *) let abs = ctx_lookup_abs ctx abs_id in (* End the first loan we find. * * We ignore the "ignored mut/shared loans": as we should have already ended * the parent abstractions, they necessarily come from children. *) let opt_loan = get_first_non_ignored_aloan_in_abstraction span abs in match opt_loan with | None -> (* No loans: nothing to update *) (ctx, fun e -> e) | Some (BorrowIds bids) -> (* There are loans: end the corresponding borrows, then recheck *) let ctx, cc = match bids with | Borrow bid -> end_borrow_aux config span chain None bid ctx | Borrows bids -> end_borrows_aux config span chain None bids ctx in (* Reexplore, looking for loans *) comp cc (end_abstraction_loans config span chain abs_id ctx) | Some (SymbolicValue sv) -> (* There is a proj_loans over a symbolic value: end the proj_borrows which intersect this proj_loans, then end the proj_loans itself *) let ctx, cc = end_proj_loans_symbolic config span chain abs_id abs.regions sv ctx in (* Reexplore, looking for loans *) comp cc (end_abstraction_loans config span chain abs_id ctx) and end_abstraction_borrows (config : config) (span : Meta.span) (chain : borrow_or_abs_ids) (abs_id : AbstractionId.id) : cm_fun = fun ctx -> log#ldebug (lazy ("end_abstraction_borrows: abs_id: " ^ AbstractionId.to_string abs_id)); (* Note that the abstraction mustn't contain any loans *) (* We end the borrows, starting with the *inner* ones. This is important when considering nested borrows which have the same lifetime. TODO: is that really important? Initially, there was a concern about whether we should give back ⊥ or not, but everything is handled by the symbolic value expansion... Also, now we use the AEndedMutBorrow values to store the children avalues (which was not the case before - we initially replaced the ended mut borrows with ⊥). *) (* We explore in-depth and use exceptions. When exploring a borrow, if * the exploration didn't trigger an exception, it means there are no * inner borrows to end: we can thus trigger an exception for the current * borrow. * * TODO: there should be a function in InterpreterBorrowsCore which does just * that. *) let obj = object inherit [_] iter_abs as super method! visit_aborrow_content env bc = (* In-depth exploration *) super#visit_aborrow_content env bc; (* No exception was raise: we can raise an exception for the * current borrow *) match bc with | AMutBorrow _ | ASharedBorrow _ -> (* Raise an exception *) raise (FoundABorrowContent bc) | AProjSharedBorrow asb -> (* Raise an exception only if the asb contains borrows *) if List.exists (fun x -> match x with AsbBorrow _ -> true | _ -> false) asb then raise (FoundABorrowContent bc) else () | AEndedMutBorrow _ | AIgnoredMutBorrow _ | AEndedIgnoredMutBorrow _ | AEndedSharedBorrow -> (* Nothing to do for ignored borrows *) () method! visit_aproj env sproj = (match sproj with | AProjLoans _ -> craise __FILE__ __LINE__ span "Unexpected" | AProjBorrows (sv, proj_ty) -> raise (FoundAProjBorrows (sv, proj_ty)) | AEndedProjLoans _ | AEndedProjBorrows _ | AIgnoredProjBorrows -> ()); super#visit_aproj env sproj (** We may need to end borrows in "regular" values, because of shared values *) method! visit_borrow_content _ bc = match bc with | VSharedBorrow _ | VMutBorrow (_, _) -> raise (FoundBorrowContent bc) | VReservedMutBorrow _ -> craise __FILE__ __LINE__ span "Unreachable" end in (* Lookup the abstraction *) let abs = ctx_lookup_abs ctx abs_id in try (* Explore the abstraction, looking for borrows *) obj#visit_abs () abs; (* No borrows: nothing to update *) (ctx, fun e -> e) with (* There are concrete (i.e., not symbolic) borrows: end them, then reexplore *) | FoundABorrowContent bc -> log#ldebug (lazy ("end_abstraction_borrows: found aborrow content: " ^ aborrow_content_to_string ~span:(Some span) ctx bc)); let ctx = match bc with | AMutBorrow (pm, bid, av) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; (* First, convert the avalue to a (fresh symbolic) value *) let sv = convert_avalue_to_given_back_value span av in (* Replace the mut borrow to register the fact that we ended * it and store with it the freshly generated given back value *) let ended_borrow = ABorrow (AEndedMutBorrow (sv, av)) in let ctx = update_aborrow span ek_all bid ended_borrow ctx in (* Give the value back *) let sv = mk_typed_value_from_symbolic_value sv in give_back_value config span bid sv ctx | ASharedBorrow (pm, bid) -> sanity_check __FILE__ __LINE__ (pm = PNone) span; (* Replace the shared borrow to account for the fact it ended *) let ended_borrow = ABorrow AEndedSharedBorrow in let ctx = update_aborrow span ek_all bid ended_borrow ctx in (* Give back *) give_back_shared config span bid ctx | AProjSharedBorrow asb -> (* Retrieve the borrow ids *) let bids = List.filter_map (fun asb -> match asb with | AsbBorrow bid -> Some bid | AsbProjReborrows (_, _) -> None) asb in (* There should be at least one borrow identifier in the set, which we * can use to identify the whole set *) let repr_bid = List.hd bids in (* Replace the shared borrow with Bottom *) let ctx = update_aborrow span ek_all repr_bid ABottom ctx in (* Give back the shared borrows *) let ctx = List.fold_left (fun ctx bid -> give_back_shared config span bid ctx) ctx bids in (* Continue *) ctx | AEndedMutBorrow _ | AIgnoredMutBorrow _ | AEndedIgnoredMutBorrow _ | AEndedSharedBorrow -> craise __FILE__ __LINE__ span "Unexpected" in (* Reexplore *) end_abstraction_borrows config span chain abs_id ctx (* There are symbolic borrows: end them, then reexplore *) | FoundAProjBorrows (sv, proj_ty) -> log#ldebug (lazy ("end_abstraction_borrows: found aproj borrows: " ^ aproj_to_string ctx (AProjBorrows (sv, proj_ty)))); (* Generate a fresh symbolic value *) let nsv = mk_fresh_symbolic_value span proj_ty in (* Replace the proj_borrows - there should be exactly one *) let ended_borrow = AEndedProjBorrows nsv in let ctx = update_aproj_borrows span abs.abs_id sv ended_borrow ctx in (* Give back the symbolic value *) let ctx = give_back_symbolic_value config span abs.regions proj_ty sv nsv ctx in (* Reexplore *) end_abstraction_borrows config span chain abs_id ctx (* There are concrete (i.e., not symbolic) borrows in shared values: end them, then reexplore *) | FoundBorrowContent bc -> log#ldebug (lazy ("end_abstraction_borrows: found borrow content: " ^ borrow_content_to_string ~span:(Some span) ctx bc)); let ctx = match bc with | VSharedBorrow bid -> ( (* Replace the shared borrow with bottom *) let allow_inner_loans = false in match end_borrow_get_borrow span (Some abs_id) allow_inner_loans bid ctx with | Error _ -> craise __FILE__ __LINE__ span "Unreachable" | Ok (ctx, _) -> (* Give back *) give_back_shared config span bid ctx) | VMutBorrow (bid, v) -> ( (* Replace the mut borrow with bottom *) let allow_inner_loans = false in match end_borrow_get_borrow span (Some abs_id) allow_inner_loans bid ctx with | Error _ -> craise __FILE__ __LINE__ span "Unreachable" | Ok (ctx, _) -> (* Give the value back - note that the mut borrow was below a * shared borrow: the value is thus unchanged *) give_back_value config span bid v ctx) | VReservedMutBorrow _ -> craise __FILE__ __LINE__ span "Unreachable" in (* Reexplore *) end_abstraction_borrows config span chain abs_id ctx (** Remove an abstraction from the context, as well as all its references *) and end_abstraction_remove_from_context (_config : config) (span : Meta.span) (abs_id : AbstractionId.id) : cm_fun = fun ctx -> let ctx, abs = ctx_remove_abs span ctx abs_id in let abs = Option.get abs in (* Synthesize the symbolic AST *) (ctx, SynthesizeSymbolic.synthesize_end_abstraction ctx abs) (** End a proj_loan over a symbolic value by ending the proj_borrows which intersect this proj_loans. Rk.: - if this symbolic value is primitively copiable, then: - either proj_borrows are only present in the concrete context - or there is only one intersecting proj_borrow present in an abstraction - otherwise, this symbolic value is not primitively copiable: - there may be proj_borrows_shared over this value - if we put aside the proj_borrows_shared, there should be exactly one intersecting proj_borrows, either in the concrete context or in an abstraction *) and end_proj_loans_symbolic (config : config) (span : Meta.span) (chain : borrow_or_abs_ids) (abs_id : AbstractionId.id) (regions : RegionId.Set.t) (sv : symbolic_value) : cm_fun = fun ctx -> (* Small helpers for sanity checks *) let check ctx = no_aproj_over_symbolic_in_context span sv ctx in (* Find the first proj_borrows which intersects the proj_loans *) let explore_shared = true in match lookup_intersecting_aproj_borrows_opt span explore_shared regions sv ctx with | None -> (* We couldn't find any in the context: it means that the symbolic value * is in the concrete environment (or that we dropped it, in which case * it is completely absent). We thus simply need to replace the loans * projector with an ended projector. *) let ctx = update_aproj_loans_to_ended span abs_id sv ctx in (* Sanity check *) check ctx; (* Continue *) (ctx, fun e -> e) | Some (SharedProjs projs) -> (* We found projectors over shared values - split between the projectors which belong to the current abstraction and the others. The context looks like this: {[ abs'0 { // The loan was initially like this: // [shared_loan lids (s <: ...) [s]] // but if we get there it means it was already ended: ended_shared_loan (s <: ...) [s] proj_shared_borrows [...; (s <: ...); ...] proj_shared_borrows [...; (s <: ...); ...] ... } abs'1 [ proj_shared_borrows [...; (s <: ...); ...] ... } ... // No [s] outside of abstractions ]} *) let _owned_projs, external_projs = List.partition (fun (abs_id', _) -> abs_id' = abs_id) projs in (* End the external borrow projectors (end their abstractions) *) let ctx, cc = let abs_ids = List.map fst external_projs in let abs_ids = List.fold_left (fun s id -> AbstractionId.Set.add id s) AbstractionId.Set.empty abs_ids in (* End the abstractions and continue *) end_abstractions_aux config span chain abs_ids ctx in (* End the internal borrows projectors and the loans projector *) let ctx = (* All the proj_borrows are owned: simply erase them *) let ctx = remove_intersecting_aproj_borrows_shared span regions sv ctx in (* End the loan itself *) update_aproj_loans_to_ended span abs_id sv ctx in (* Sanity check *) check ctx; (ctx, cc) | Some (NonSharedProj (abs_id', _proj_ty)) -> (* We found one projector of borrows in an abstraction: if it comes * from this abstraction, we can end it directly, otherwise we need * to end the abstraction where it came from first *) if abs_id' = abs_id then ( (* Note that it happens when a function returns a [&mut ...] which gets expanded to [mut_borrow l s], and we end the borrow [l] (so [s] gets reinjected in the parent abstraction without having been modified). The context looks like this: {[ abs'0 { [s <: ...] (s <: ...) } // Note that [s] can't appear in other abstractions or in the // regular environment (because we forbid the duplication of // symbolic values which contain borrows). ]} *) (* End the projector of borrows - TODO: not completely sure what to * replace it with... Maybe we should introduce an ABottomProj? *) let ctx = update_aproj_borrows span abs_id sv AIgnoredProjBorrows ctx in (* Sanity check: no other occurrence of an intersecting projector of borrows *) sanity_check __FILE__ __LINE__ (Option.is_none (lookup_intersecting_aproj_borrows_opt span explore_shared regions sv ctx)) span; (* End the projector of loans *) let ctx = update_aproj_loans_to_ended span abs_id sv ctx in (* Sanity check *) check ctx; (* Continue *) (ctx, fun e -> e)) else (* The borrows proj comes from a different abstraction: end it. *) let ctx, cc = end_abstraction_aux config span chain abs_id' ctx in (* Retry ending the projector of loans *) let ctx, cc = comp cc (end_proj_loans_symbolic config span chain abs_id regions sv ctx) in (* Sanity check *) check ctx; (* Return *) (ctx, cc) let end_borrow config (span : Meta.span) : BorrowId.id -> cm_fun = end_borrow_aux config span [] None let end_borrows config (span : Meta.span) : BorrowId.Set.t -> cm_fun = end_borrows_aux config span [] None let end_abstraction config span = end_abstraction_aux config span [] let end_abstractions config span = end_abstractions_aux config span [] let end_borrow_no_synth config span id ctx = fst (end_borrow config span id ctx) let end_borrows_no_synth config span ids ctx = fst (end_borrows config span ids ctx) let end_abstraction_no_synth config span id ctx = fst (end_abstraction config span id ctx) let end_abstractions_no_synth config span ids ctx = fst (end_abstractions config span ids ctx) (** Helper function: see {!activate_reserved_mut_borrow}. This function updates the shared loan to a mutable loan (we then update the borrow with another helper). Of course, the shared loan must contain exactly one borrow id (the one we give as parameter), otherwise we can't promote it. Also, the shared value mustn't contain any loan. The returned value (previously shared) is checked: - it mustn't contain loans - it mustn't contain {!Bottom} - it mustn't contain reserved borrows TODO: this kind of checks should be put in an auxiliary helper, because they are redundant. The loan to update mustn't be a borrowed value. *) let promote_shared_loan_to_mut_loan (span : Meta.span) (l : BorrowId.id) (ctx : eval_ctx) : typed_value * eval_ctx = (* Debug *) log#ldebug (lazy ("promote_shared_loan_to_mut_loan:\n- loan: " ^ BorrowId.to_string l ^ "\n- context:\n" ^ eval_ctx_to_string ~span:(Some span) ctx ^ "\n")); (* Lookup the shared loan - note that we can't promote a shared loan * in a shared value, but we can do it in a mutably borrowed value. * This is important because we can do: [let y = &two-phase ( *x );] *) let ek = { enter_shared_loans = false; enter_mut_borrows = true; enter_abs = false } in match lookup_loan span ek l ctx with | _, Concrete (VMutLoan _) -> craise __FILE__ __LINE__ span "Expected a shared loan, found a mut loan" | _, Concrete (VSharedLoan (bids, sv)) -> (* Check that there is only one borrow id (l) and update the loan *) cassert __FILE__ __LINE__ (BorrowId.Set.mem l bids && BorrowId.Set.cardinal bids = 1) span "There should only be one borrow id"; (* We need to check that there aren't any loans in the value: we should have gotten rid of those already, but it is better to do a sanity check. *) sanity_check __FILE__ __LINE__ (not (loans_in_value sv)) span; (* Check there isn't {!Bottom} (this is actually an invariant *) cassert __FILE__ __LINE__ (not (bottom_in_value ctx.ended_regions sv)) span "There shouldn't be a bottom"; (* Check there aren't reserved borrows *) cassert __FILE__ __LINE__ (not (reserved_in_value sv)) span "There shouldn't be reserved borrows"; (* Update the loan content *) let ctx = update_loan span ek l (VMutLoan l) ctx in (* Return *) (sv, ctx) | _, Abstract _ -> (* I don't think it is possible to have two-phase borrows involving borrows returned by abstractions. I'm not sure how we could handle that anyway. *) craise __FILE__ __LINE__ span "Can't promote a shared loan to a mutable loan if the loan is inside a \ region abstraction" (** Helper function: see {!activate_reserved_mut_borrow}. This function updates a shared borrow to a mutable borrow (and that is all: it doesn't touch the corresponding loan). *) let replace_reserved_borrow_with_mut_borrow (span : Meta.span) (l : BorrowId.id) (borrowed_value : typed_value) (ctx : eval_ctx) : eval_ctx = (* Lookup the reserved borrow - note that we don't go inside borrows/loans: there can't be reserved borrows inside other borrows/loans *) let ek = { enter_shared_loans = false; enter_mut_borrows = false; enter_abs = false } in match lookup_borrow span ek l ctx with | Concrete (VSharedBorrow _ | VMutBorrow (_, _)) -> craise __FILE__ __LINE__ span "Expected a reserved mutable borrow" | Concrete (VReservedMutBorrow _) -> (* Update it *) update_borrow span ek l (VMutBorrow (l, borrowed_value)) ctx | Abstract _ -> (* This can't happen for sure *) craise __FILE__ __LINE__ span "Can't promote a shared borrow to a mutable borrow if the borrow is \ inside a region abstraction" (** Promote a reserved mut borrow to a mut borrow. *) let rec promote_reserved_mut_borrow (config : config) (span : Meta.span) (l : BorrowId.id) : cm_fun = fun ctx -> (* Lookup the value *) let ek = { enter_shared_loans = false; enter_mut_borrows = true; enter_abs = false } in match lookup_loan span ek l ctx with | _, Concrete (VMutLoan _) -> craise __FILE__ __LINE__ span "Unreachable" | _, Concrete (VSharedLoan (bids, sv)) -> ( (* If there are loans inside the value, end them. Note that there can't be reserved borrows inside the value. If we perform an update, do a recursive call to lookup the updated value *) match get_first_loan_in_value sv with | Some lc -> (* End the loans *) let ctx, cc = match lc with | VSharedLoan (bids, _) -> end_borrows config span bids ctx | VMutLoan bid -> end_borrow config span bid ctx in (* Recursive call *) comp cc (promote_reserved_mut_borrow config span l ctx) | None -> (* No loan to end inside the value *) (* Some sanity checks *) log#ldebug (lazy ("activate_reserved_mut_borrow: resulting value:\n" ^ typed_value_to_string ~span:(Some span) ctx sv)); sanity_check __FILE__ __LINE__ (not (loans_in_value sv)) span; sanity_check __FILE__ __LINE__ (not (bottom_in_value ctx.ended_regions sv)) span; sanity_check __FILE__ __LINE__ (not (reserved_in_value sv)) span; (* End the borrows which borrow from the value, at the exception of the borrow we want to promote *) let bids = BorrowId.Set.remove l bids in let ctx, cc = end_borrows config span bids ctx in (* Promote the loan - TODO: this will fail if the value contains * any loans. In practice, it shouldn't, but we could also * look for loans inside the value and end them before promoting * the borrow. *) let v, ctx = promote_shared_loan_to_mut_loan span l ctx in (* Promote the borrow - the value should have been checked by {!promote_shared_loan_to_mut_loan} *) let ctx = replace_reserved_borrow_with_mut_borrow span l v ctx in (* Return *) (ctx, cc)) | _, Abstract _ -> (* I don't think it is possible to have two-phase borrows involving borrows * returned by abstractions. I'm not sure how we could handle that anyway. *) craise __FILE__ __LINE__ span "Can't activate a reserved mutable borrow referencing a loan inside\n\ \ a region abstraction" let destructure_abs (span : Meta.span) (abs_kind : abs_kind) (can_end : bool) (destructure_shared_values : bool) (ctx : eval_ctx) (abs0 : abs) : abs = (* Accumulator to store the destructured values *) let avalues = ref [] in (* Utility function to store a value in the accumulator *) let push_avalue av = avalues := List.append !avalues [ av ] in (* We use this function to make sure we never register values (i.e., loans or borrows) when we shouldn't - see it as a sanity check: for now, we don't allow nested borrows, which means we should register values from children of borrows. In this condition, we could simply ignore the children altogether. Instead, we explore them and make sure we don't register values while doing so. *) let push_fail _ = craise __FILE__ __LINE__ span "Unreachable" in (* Function to explore an avalue and destructure it *) let rec list_avalues (allow_borrows : bool) (push : typed_avalue -> unit) (av : typed_avalue) : unit = let ty = av.ty in match av.value with | ABottom | AIgnored -> () | AAdt adt -> (* Simply explore the children *) List.iter (list_avalues allow_borrows push) adt.field_values | ALoan lc -> ( (* Explore the loan content *) match lc with | ASharedLoan (pm, bids, sv, child_av) -> (* We don't support nested borrows for now *) cassert __FILE__ __LINE__ (not (value_has_borrows ctx sv.value)) span "Nested borrows are not supported yet"; (* Destructure the shared value *) let avl, sv = if destructure_shared_values then list_values sv else ([], sv) in (* Push a value *) let ignored = mk_aignored span child_av.ty in let value = ALoan (ASharedLoan (pm, bids, sv, ignored)) in push { value; ty }; (* Explore the child *) list_avalues false push_fail child_av; (* Push the avalues introduced because we decomposed the inner loans - note that we pay attention to the order in which we introduce the avalues: we want to push them *after* the outer loan. If we didn't want that, we would have implemented [list_values] in exactly the same way as [list_avalues] (i.e., with a similar signature) *) List.iter push avl | AMutLoan (pm, bid, child_av) -> (* Explore the child *) list_avalues false push_fail child_av; (* Explore the whole loan *) let ignored = mk_aignored span child_av.ty in let value = ALoan (AMutLoan (pm, bid, ignored)) in push { value; ty } | AIgnoredMutLoan (opt_bid, child_av) -> (* We don't support nested borrows for now *) cassert __FILE__ __LINE__ (not (ty_has_borrows ctx.type_ctx.type_infos child_av.ty)) span "Nested borrows are not supported yet"; sanity_check __FILE__ __LINE__ (opt_bid = None) span; (* Simply explore the child *) list_avalues false push_fail child_av | AEndedMutLoan { child = child_av; given_back = _; given_back_span = _ } | AEndedSharedLoan (_, child_av) | AEndedIgnoredMutLoan { child = child_av; given_back = _; given_back_span = _ } | AIgnoredSharedLoan child_av -> (* We don't support nested borrows for now *) cassert __FILE__ __LINE__ (not (ty_has_borrows ctx.type_ctx.type_infos child_av.ty)) span "Nested borrows are not supported yet"; (* Simply explore the child *) list_avalues false push_fail child_av) | ABorrow bc -> ( (* Sanity check - rem.: may be redundant with [push_fail] *) sanity_check __FILE__ __LINE__ allow_borrows span; (* Explore the borrow content *) match bc with | AMutBorrow (pm, bid, child_av) -> (* Explore the child *) list_avalues false push_fail child_av; (* Explore the borrow *) let ignored = mk_aignored span child_av.ty in let value = ABorrow (AMutBorrow (pm, bid, ignored)) in push { value; ty } | ASharedBorrow _ -> (* Nothing specific to do: keep the value as it is *) push av | AIgnoredMutBorrow (opt_bid, child_av) -> (* We don't support nested borrows for now *) cassert __FILE__ __LINE__ (not (ty_has_borrows ctx.type_ctx.type_infos child_av.ty)) span "Nested borrows are not supported yet"; sanity_check __FILE__ __LINE__ (opt_bid = None) span; (* Just explore the child *) list_avalues false push_fail child_av | AEndedIgnoredMutBorrow { child = child_av; given_back = _; given_back_span = _ } -> (* We don't support nested borrows for now *) cassert __FILE__ __LINE__ (not (ty_has_borrows ctx.type_ctx.type_infos child_av.ty)) span "Nested borrows are not supported yet"; (* Just explore the child *) list_avalues false push_fail child_av | AProjSharedBorrow asb -> (* We don't support nested borrows *) cassert __FILE__ __LINE__ (asb = []) span "Nested borrows are not supported yet"; (* Nothing specific to do *) () | AEndedMutBorrow _ | AEndedSharedBorrow -> (* If we get there it means the abstraction ended: it should not be in the context anymore (if we end *one* borrow in an abstraction, we have to end them all and remove the abstraction from the context) *) craise __FILE__ __LINE__ span "Unreachable") | ASymbolic _ -> (* For now, we fore all symbolic values containing borrows to be eagerly expanded *) sanity_check __FILE__ __LINE__ (not (ty_has_borrows ctx.type_ctx.type_infos ty)) span and list_values (v : typed_value) : typed_avalue list * typed_value = let ty = v.ty in match v.value with | VLiteral _ -> ([], v) | VAdt adt -> let avll, field_values = List.split (List.map list_values adt.field_values) in let avl = List.concat avll in let adt = { adt with field_values } in (avl, { v with value = VAdt adt }) | VBottom -> craise __FILE__ __LINE__ span "Unreachable" | VBorrow _ -> (* We don't support nested borrows for now *) craise __FILE__ __LINE__ span "Unreachable" | VLoan lc -> ( match lc with | VSharedLoan (bids, sv) -> let avl, sv = list_values sv in if destructure_shared_values then ( (* Rem.: the shared value can't contain loans nor borrows *) cassert __FILE__ __LINE__ (ty_no_regions ty) span "Nested borrows are not supported yet"; let av : typed_avalue = sanity_check __FILE__ __LINE__ (not (value_has_loans_or_borrows ctx sv.value)) span; (* We introduce fresh ids for the symbolic values *) let mk_value_with_fresh_sids (v : typed_value) : typed_value = let visitor = object inherit [_] map_typed_avalue method! visit_symbolic_value_id _ _ = fresh_symbolic_value_id () end in visitor#visit_typed_value () v in let sv = mk_value_with_fresh_sids sv in (* Create the new avalue *) let value = ALoan (ASharedLoan (PNone, bids, sv, mk_aignored span ty)) in { value; ty } in let avl = List.append [ av ] avl in (avl, sv)) else (avl, { v with value = VLoan (VSharedLoan (bids, sv)) }) | VMutLoan _ -> craise __FILE__ __LINE__ span "Unreachable") | VSymbolic _ -> (* For now, we fore all symbolic values containing borrows to be eagerly expanded *) sanity_check __FILE__ __LINE__ (not (ty_has_borrows ctx.type_ctx.type_infos ty)) span; ([], v) in (* Destructure the avalues *) List.iter (list_avalues true push_avalue) abs0.avalues; let avalues = !avalues in (* Update *) { abs0 with avalues; kind = abs_kind; can_end } let abs_is_destructured (span : Meta.span) (destructure_shared_values : bool) (ctx : eval_ctx) (abs : abs) : bool = let abs' = destructure_abs span abs.kind abs.can_end destructure_shared_values ctx abs in abs = abs' let convert_value_to_abstractions (span : Meta.span) (abs_kind : abs_kind) (can_end : bool) (destructure_shared_values : bool) (ctx : eval_ctx) (v : typed_value) : abs list = (* Convert the value to a list of avalues *) let absl = ref [] in let push_abs (r_id : RegionId.id) (avalues : typed_avalue list) : unit = if avalues = [] then () else (* Create the abs - note that we keep the order of the avalues as it is (unlike the environments) *) let abs = { abs_id = fresh_abstraction_id (); kind = abs_kind; can_end; parents = AbstractionId.Set.empty; original_parents = []; regions = RegionId.Set.singleton r_id; ancestors_regions = RegionId.Set.empty; avalues; } in (* Add to the list of abstractions *) absl := abs :: !absl in (* [group]: group in one abstraction (because we dived into a borrow/loan) We return one typed-value for the shared values: when we encounter a shared loan, we need to compute the value which will be shared. If [destructure_shared_values] is [true], this shared value will be stripped of its shared loans. *) let rec to_avalues (allow_borrows : bool) (inside_borrowed : bool) (group : bool) (r_id : RegionId.id) (v : typed_value) : typed_avalue list * typed_value = (* Debug *) log#ldebug (lazy ("convert_value_to_abstractions: to_avalues:\n- value: " ^ typed_value_to_string ~span:(Some span) ctx v)); let ty = v.ty in match v.value with | VLiteral _ -> ([], v) | VBottom -> (* Can happen: we *do* convert dummy values to abstractions, and dummy values can contain bottoms *) ([], v) | VAdt adt -> (* Two cases, depending on whether we have to group all the borrows/loans inside one abstraction or not *) let avl, field_values = if group then (* Convert to avalues, and transmit to the parent *) let avl, field_values = List.split (List.map (to_avalues allow_borrows inside_borrowed group r_id) adt.field_values) in (List.concat avl, field_values) else (* Create one abstraction per field, and transmit nothing to the parent *) let field_values = List.map (fun fv -> let r_id = fresh_region_id () in let avl, fv = to_avalues allow_borrows inside_borrowed group r_id fv in push_abs r_id avl; fv) (* Slightly tricky: pay attention to the order in which the abstractions are pushed (i.e.: the [List.rev] is important to get a "good" environment, and a nice translation) *) (List.rev adt.field_values) in ([], field_values) in let adt = { adt with field_values } in (avl, { v with value = VAdt adt }) | VBorrow bc -> ( let _, ref_ty, kind = ty_as_ref ty in cassert __FILE__ __LINE__ (ty_no_regions ref_ty) span "Nested borrows are not supported yet"; (* Sanity check *) sanity_check __FILE__ __LINE__ allow_borrows span; (* Convert the borrow content *) match bc with | VSharedBorrow bid -> cassert __FILE__ __LINE__ (ty_no_regions ref_ty) span "Nested borrows are not supported yet"; let ty = TRef (RFVar r_id, ref_ty, kind) in let value = ABorrow (ASharedBorrow (PNone, bid)) in ([ { value; ty } ], v) | VMutBorrow (bid, bv) -> let r_id = if group then r_id else fresh_region_id () in (* We don't support nested borrows for now *) cassert __FILE__ __LINE__ (not (value_has_borrows ctx bv.value)) span "Nested borrows are not supported yet"; (* Create an avalue to push - note that we use [AIgnore] for the inner avalue *) let ty = TRef (RFVar r_id, ref_ty, kind) in let ignored = mk_aignored span ref_ty in let av = ABorrow (AMutBorrow (PNone, bid, ignored)) in let av = { value = av; ty } in (* Continue exploring, looking for loans (and forbidding borrows, because we don't support nested borrows for now) *) let avl, bv = to_avalues false true true r_id bv in let value = { v with value = VBorrow (VMutBorrow (bid, bv)) } in (av :: avl, value) | VReservedMutBorrow _ -> (* This borrow should have been activated *) craise __FILE__ __LINE__ span "Unexpected") | VLoan lc -> ( match lc with | VSharedLoan (bids, sv) -> let r_id = if group then r_id else fresh_region_id () in (* We don't support nested borrows for now *) cassert __FILE__ __LINE__ (not (value_has_borrows ctx sv.value)) span "Nested borrows are not supported yet"; (* Push the avalue - note that we use [AIgnore] for the inner avalue *) (* For avalues, a loan has the borrow type *) cassert __FILE__ __LINE__ (ty_no_regions ty) span "Nested borrows are not supported yet"; let ty = mk_ref_ty (RFVar r_id) ty RShared in let ignored = mk_aignored span ty in (* Rem.: the shared value might contain loans *) let avl, sv = to_avalues false true true r_id sv in let av = ALoan (ASharedLoan (PNone, bids, sv, ignored)) in let av = { value = av; ty } in (* Continue exploring, looking for loans (and forbidding borrows, because we don't support nested borrows for now) *) let value : value = if destructure_shared_values then sv.value else VLoan (VSharedLoan (bids, sv)) in let value = { v with value } in (av :: avl, value) | VMutLoan bid -> (* Push the avalue - note that we use [AIgnore] for the inner avalue *) (* For avalues, a loan has the borrow type *) cassert __FILE__ __LINE__ (ty_no_regions ty) span "Nested borrows are not supported yet"; let ty = mk_ref_ty (RFVar r_id) ty RMut in let ignored = mk_aignored span ty in let av = ALoan (AMutLoan (PNone, bid, ignored)) in let av = { value = av; ty } in ([ av ], v)) | VSymbolic _ -> (* For now, we force all the symbolic values containing borrows to be eagerly expanded, and we don't support nested borrows *) cassert __FILE__ __LINE__ (not (value_has_borrows ctx v.value)) span "Nested borrows are not supported yet"; (* Return nothing *) ([], v) in (* Generate the avalues *) let r_id = fresh_region_id () in let values, _ = to_avalues true false false r_id v in (* Introduce an abstraction for the returned values *) push_abs r_id values; (* Return *) List.rev !absl type marker_borrow_or_loan_id = | BorrowId of proj_marker * borrow_id | LoanId of proj_marker * loan_id type g_loan_content_with_ty = (ety * loan_content, rty * aloan_content) concrete_or_abs type g_borrow_content_with_ty = (ety * borrow_content, rty * aborrow_content) concrete_or_abs type merge_abstraction_info = { loans : MarkerBorrowId.Set.t; borrows : MarkerBorrowId.Set.t; borrows_loans : marker_borrow_or_loan_id list; (** We use a list to preserve the order in which the borrows were found *) loan_to_content : g_loan_content_with_ty MarkerBorrowId.Map.t; borrow_to_content : g_borrow_content_with_ty MarkerBorrowId.Map.t; } (** Small utility to help merging abstractions. We compute the list of loan/borrow contents, maps from borrow/loan ids to borrow/loan contents, etc. Note that this function is very specific to [merge_into_first_abstraction]: we have strong assumptions about the shape of the abstraction, and in particular that: - its values don't contain nested borrows - all the borrows are destructured (for instance, shared loans can't contain shared loans). *) let compute_merge_abstraction_info (span : Meta.span) (ctx : eval_ctx) (avalues : typed_avalue list) : merge_abstraction_info = let loans : MarkerBorrowId.Set.t ref = ref MarkerBorrowId.Set.empty in let borrows : MarkerBorrowId.Set.t ref = ref MarkerBorrowId.Set.empty in let borrows_loans : marker_borrow_or_loan_id list ref = ref [] in let loan_to_content : g_loan_content_with_ty MarkerBorrowId.Map.t ref = ref MarkerBorrowId.Map.empty in let borrow_to_content : g_borrow_content_with_ty MarkerBorrowId.Map.t ref = ref MarkerBorrowId.Map.empty in let push_loan pm id (lc : g_loan_content_with_ty) : unit = sanity_check __FILE__ __LINE__ (not (MarkerBorrowId.Set.mem (pm, id) !loans)) span; loans := MarkerBorrowId.Set.add (pm, id) !loans; sanity_check __FILE__ __LINE__ (not (MarkerBorrowId.Map.mem (pm, id) !loan_to_content)) span; loan_to_content := MarkerBorrowId.Map.add (pm, id) lc !loan_to_content; borrows_loans := LoanId (pm, id) :: !borrows_loans in let push_loans pm ids lc : unit = BorrowId.Set.iter (fun id -> push_loan pm id lc) ids in let push_borrow pm id (bc : g_borrow_content_with_ty) : unit = sanity_check __FILE__ __LINE__ (not (MarkerBorrowId.Set.mem (pm, id) !borrows)) span; borrows := MarkerBorrowId.Set.add (pm, id) !borrows; sanity_check __FILE__ __LINE__ (not (MarkerBorrowId.Map.mem (pm, id) !borrow_to_content)) span; borrow_to_content := MarkerBorrowId.Map.add (pm, id) bc !borrow_to_content; borrows_loans := BorrowId (pm, id) :: !borrows_loans in let iter_avalues = object inherit [_] iter_typed_avalue as super (** We redefine this to track the types *) method! visit_typed_avalue _ v = super#visit_typed_avalue (Some (Abstract v.ty)) v (** We redefine this to track the types *) method! visit_typed_value _ (v : typed_value) = super#visit_typed_value (Some (Concrete v.ty)) v method! visit_loan_content _ _ = (* Could happen if we explore shared values whose sub-values are reborrowed. We use the fact that we destructure the nested shared loans before reducing a context or computing a join. *) craise __FILE__ __LINE__ span "Unreachable" method! visit_borrow_content _ _ = (* Can happen if we explore shared values which contain borrows - i.e., if we have nested borrows (we forbid this for now) *) craise __FILE__ __LINE__ span "Unreachable" method! visit_aloan_content env lc = let ty = match Option.get env with | Concrete _ -> craise __FILE__ __LINE__ span "Unreachable" | Abstract ty -> ty in (* Register the loans *) (match lc with | ASharedLoan (pm, bids, _, _) -> push_loans pm bids (Abstract (ty, lc)) | AMutLoan (pm, bid, _) -> push_loan pm bid (Abstract (ty, lc)) | AEndedMutLoan _ | AEndedSharedLoan _ | AIgnoredMutLoan _ | AEndedIgnoredMutLoan _ | AIgnoredSharedLoan _ -> (* The abstraction has been destructured, so those shouldn't appear *) craise __FILE__ __LINE__ span "Unreachable"); (* Continue *) super#visit_aloan_content env lc method! visit_aborrow_content env bc = let ty = match Option.get env with | Concrete _ -> craise __FILE__ __LINE__ span "Unreachable" | Abstract ty -> ty in (* Explore the borrow content *) (match bc with | AMutBorrow (pm, bid, _) | ASharedBorrow (pm, bid) -> push_borrow pm bid (Abstract (ty, bc)) | AProjSharedBorrow asb -> let register asb = match asb with | AsbBorrow bid -> push_borrow PNone bid (Abstract (ty, bc)) | AsbProjReborrows _ -> (* Can only happen if the symbolic value (potentially) contains borrows - i.e., we have nested borrows *) craise __FILE__ __LINE__ span "Unreachable" in List.iter register asb | AIgnoredMutBorrow _ | AEndedIgnoredMutBorrow _ | AEndedMutBorrow _ | AEndedSharedBorrow -> (* The abstraction has been destructured, so those shouldn't appear *) craise __FILE__ __LINE__ span "Unreachable"); super#visit_aborrow_content env bc method! visit_symbolic_value _ sv = (* Sanity check: no borrows *) sanity_check __FILE__ __LINE__ (not (symbolic_value_has_borrows ctx sv)) span end in List.iter (iter_avalues#visit_typed_avalue None) avalues; { loans = !loans; borrows = !borrows; borrows_loans = List.rev !borrows_loans; loan_to_content = !loan_to_content; borrow_to_content = !borrow_to_content; } type merge_duplicates_funcs = { merge_amut_borrows : borrow_id -> rty -> proj_marker -> typed_avalue -> rty -> proj_marker -> typed_avalue -> typed_avalue; (** Parameters: - [id] - [ty0] - [pm0] - [child0] - [ty1] - [pm1] - [child1] The children should be [AIgnored]. *) merge_ashared_borrows : borrow_id -> rty -> proj_marker -> rty -> proj_marker -> typed_avalue; (** Parameters: - [id] - [ty0] - [pm0] - [ty1] - [pm1] *) merge_amut_loans : loan_id -> rty -> proj_marker -> typed_avalue -> rty -> proj_marker -> typed_avalue -> typed_avalue; (** Parameters: - [id] - [ty0] - [pm0] - [child0] - [ty1] - [pm1] - [child1] The children should be [AIgnored]. *) merge_ashared_loans : loan_id_set -> rty -> proj_marker -> typed_value -> typed_avalue -> rty -> proj_marker -> typed_value -> typed_avalue -> typed_avalue; (** Parameters: - [ids] - [ty0] - [pm0] - [sv0] - [child0] - [ty1] - [pm1] - [sv1] - [child1] *) } (** Small utility: if a value doesn't have any marker, split it into two values with complementary markers. We use this for {!merge_abstractions}. We assume the value has been destructured (there are no nested loans, adts, the children are ignored, etc.). *) let typed_avalue_split_marker (span : Meta.span) (ctx : eval_ctx) (av : typed_avalue) : typed_avalue list = let mk_split pm mk_value = if pm = PNone then [ mk_value PLeft; mk_value PRight ] else [ av ] in match av.value with | AAdt _ | ABottom | ASymbolic _ | AIgnored -> craise __FILE__ __LINE__ span "Unexpected" | ABorrow bc -> ( match bc with | AMutBorrow (pm, bid, child) -> sanity_check __FILE__ __LINE__ (is_aignored child.value) span; let mk_value pm = { av with value = ABorrow (AMutBorrow (pm, bid, child)) } in mk_split pm mk_value | ASharedBorrow (pm, bid) -> let mk_value pm = { av with value = ABorrow (ASharedBorrow (pm, bid)) } in mk_split pm mk_value | _ -> craise __FILE__ __LINE__ span "Unsupported yet") | ALoan lc -> ( match lc with | AMutLoan (pm, bid, child) -> sanity_check __FILE__ __LINE__ (is_aignored child.value) span; let mk_value pm = { av with value = ALoan (AMutLoan (pm, bid, child)) } in mk_split pm mk_value | ASharedLoan (pm, bids, sv, child) -> sanity_check __FILE__ __LINE__ (is_aignored child.value) span; sanity_check __FILE__ __LINE__ (not (value_has_borrows ctx sv.value)) span; let mk_value pm = { av with value = ALoan (ASharedLoan (pm, bids, sv, child)) } in mk_split pm mk_value | _ -> craise __FILE__ __LINE__ span "Unsupported yet") let abs_split_markers (span : Meta.span) (ctx : eval_ctx) (abs : abs) : abs = { abs with avalues = List.concat (List.map (typed_avalue_split_marker span ctx) abs.avalues); } (** Auxiliary function for {!merge_abstractions}. Phase 1 of the merge: we simplify all loan/borrow pairs, if a loan is in the left abstraction and its corresponding borrow is in the right abstraction. Important: this is asymmetric (the loan must be in the left abstraction). Example: {[ abs0 { ML l0, MB l1 } |><| abs1 { MB l0 } ~~> abs1 { MB l1 } ]} But: {[ abs1 { MB l0 } |><| abs0 { ML l0, MB l1 } ~~> abs1 { MB l0, ML l0, MB l1 } ]} We return the list of merged values. *) let merge_abstractions_merge_loan_borrow_pairs (span : Meta.span) (merge_funs : merge_duplicates_funcs option) (ctx : eval_ctx) (abs0 : abs) (abs1 : abs) : typed_avalue list = log#ldebug (lazy "merge_abstractions_merge_loan_borrow_pairs"); (* Split the markers inside the abstractions (if we allow using markers). We do so because it enables simplification later when we are in the following case: {[ abs0 { ML l0 } |><| abs1 { |MB l0|, MB l1 } ]} If we split before merging we get: {[ abs0 { |ML l0|, ︙ML l0︙ } |><| abs1 { |MB l0|, |MB l1|, ︙MB l1︙ } ~~> merge abs2 { ︙ML l0︙, |MB l1|, ︙MB l1︙ } ~~> simplify the complementary markers abs2 { ︙ML l0︙, MB l1 } ]} *) let abs0, abs1 = if merge_funs = None then (abs0, abs1) else (abs_split_markers span ctx abs0, abs_split_markers span ctx abs1) in (* Compute the relevant information *) let { loans = loans0; borrows = borrows0; borrows_loans = borrows_loans0; loan_to_content = loan_to_content0; borrow_to_content = borrow_to_content0; } = compute_merge_abstraction_info span ctx abs0.avalues in let { loans = loans1; borrows = borrows1; borrows_loans = borrows_loans1; loan_to_content = loan_to_content1; borrow_to_content = borrow_to_content1; } = compute_merge_abstraction_info span ctx abs1.avalues in (* Sanity check: no markers appear unless we allow merging duplicates *) if merge_funs = None then ( sanity_check __FILE__ __LINE__ (List.for_all (function LoanId (pm, _) | BorrowId (pm, _) -> pm = PNone) borrows_loans0) span; sanity_check __FILE__ __LINE__ (List.for_all (function LoanId (pm, _) | BorrowId (pm, _) -> pm = PNone) borrows_loans1) span; sanity_check __FILE__ __LINE__ (MarkerBorrowId.Set.disjoint borrows0 borrows1) span; sanity_check __FILE__ __LINE__ (MarkerBorrowId.Set.disjoint loans0 loans1) span); (* Merge. There are several cases: - if a borrow/loan is only in one abstraction, we simply check if we need to filter it (because its associated loan/borrow is in the other abstraction). - if a borrow/loan is present in both abstractions, we need to merge its content. Note that we may need to merge strictly more than two avalues, because of shared loans. For instance, if we have: {[ abs'0 { shared_loan { l0, l1 } ... } abs'1 { shared_loan { l0 } ..., shared_loan { l1 } ... } ]} We ignore this case for now: we check that whenever we merge two shared loans, then their sets of ids are equal, and fail if it is not the case. Remark: a way of solving this problem would be to destructure shared loans so that they always have exactly one id. *) let merged_borrows = ref MarkerBorrowId.Set.empty in let merged_loans = ref MarkerBorrowId.Set.empty in let avalues = ref [] in let push_avalue av = log#ldebug (lazy ("merge_abstractions_merge_loan_borrow_pairs: push_avalue: " ^ typed_avalue_to_string ~span:(Some span) ctx av)); avalues := av :: !avalues in let push_opt_avalue av = match av with None -> () | Some av -> push_avalue av in (* Compute the intersection of: - the loans coming from the left abstraction - the borrows coming from the right abstraction *) let intersect = MarkerBorrowId.Set.inter loans0 borrows1 in (* This function is called when handling shared loans: we have to apply a projection marker to a set of borrow ids. *) let filter_bids (pm : proj_marker) (bids : BorrowId.Set.t) : BorrowId.Set.t = let bids = BorrowId.Set.to_seq bids |> Seq.map (fun x -> (pm, x)) |> MarkerBorrowId.Set.of_seq in let bids = MarkerBorrowId.Set.diff bids intersect in sanity_check __FILE__ __LINE__ (not (MarkerBorrowId.Set.is_empty bids)) span; MarkerBorrowId.Set.to_seq bids |> Seq.map snd |> BorrowId.Set.of_seq in let filter_bid (bid : marker_borrow_id) : marker_borrow_id option = if MarkerBorrowId.Set.mem bid intersect then None else Some bid in let borrow_is_merged id = MarkerBorrowId.Set.mem id !merged_borrows in let set_borrow_as_merged id = merged_borrows := MarkerBorrowId.Set.add id !merged_borrows in let loan_is_merged id = MarkerBorrowId.Set.mem id !merged_loans in let set_loan_as_merged id = merged_loans := MarkerBorrowId.Set.add id !merged_loans in let set_loans_as_merged pm ids = BorrowId.Set.elements ids |> List.map (fun x -> (pm, x)) |> List.iter set_loan_as_merged in (* Note that we first explore the borrows/loans of [abs0], because we want to merge *into* this abstraction, and as a consequence we want to preserve its structure as much as we can *) let borrows_loans = List.append borrows_loans0 borrows_loans1 in (* Iterate over all the borrows/loans ids found in the abstractions *) List.iter (fun bl -> match bl with | BorrowId (pm, bid) -> let bid = (pm, bid) in log#ldebug (lazy ("merge_abstractions: merging borrow " ^ MarkerBorrowId.to_string bid)); (* Check if the borrow has already been merged - this can happen because we go through all the borrows/loans in [abs0] *then* all the borrows/loans in [abs1], and there may be duplicates between the two *) if borrow_is_merged bid then () else ( set_borrow_as_merged bid; (* Check if we need to filter it *) match filter_bid bid with | None -> () | Some bid -> (* Lookup the contents *) let bc0 = MarkerBorrowId.Map.find_opt bid borrow_to_content0 in let bc1 = MarkerBorrowId.Map.find_opt bid borrow_to_content1 in (* Merge *) let av : typed_avalue = match (bc0, bc1) with | None, Some bc | Some bc, None -> ( match bc with | Concrete (_, _) -> (* This can happen only in case of nested borrows - a concrete borrow can only happen inside a shared loan *) craise __FILE__ __LINE__ span "Unreachable" | Abstract (ty, bc) -> { value = ABorrow bc; ty }) | Some _, Some _ -> (* Because of markers, the case where the same borrow is duplicated should be unreachable. Note, this is due to all shared borrows currently taking different ids, this will not be the case anymore when shared loans will take a unique id instead of a set *) craise __FILE__ __LINE__ span "Unreachable" | None, None -> craise __FILE__ __LINE__ span "Unreachable" in push_avalue av) | LoanId (pm, bid) -> let bid = (pm, bid) in if (* Check if the loan has already been treated - it can happen for the same reason as for borrows, and also because shared loans contain sets of borrows (meaning that when taking care of one loan, we can merge several other loans at once). *) loan_is_merged bid then () else ( log#ldebug (lazy ("merge_abstractions: merging loan " ^ MarkerBorrowId.to_string bid)); (* Check if we need to filter it *) match filter_bid bid with | None -> () | Some bid -> (* Lookup the contents *) let lc0 = MarkerBorrowId.Map.find_opt bid loan_to_content0 in let lc1 = MarkerBorrowId.Map.find_opt bid loan_to_content1 in (* Merge *) let av : typed_avalue option = match (lc0, lc1) with | None, Some lc | Some lc, None -> ( match lc with | Concrete _ -> (* This shouldn't happen because the avalues should have been destructured. *) craise __FILE__ __LINE__ span "Unreachable" | Abstract (ty, lc) -> ( match lc with | ASharedLoan (pm, bids, sv, child) -> let bids = filter_bids pm bids in sanity_check __FILE__ __LINE__ (not (BorrowId.Set.is_empty bids)) span; sanity_check __FILE__ __LINE__ (is_aignored child.value) span; sanity_check __FILE__ __LINE__ (not (value_has_loans_or_borrows ctx sv.value)) span; let lc = ASharedLoan (pm, bids, sv, child) in set_loans_as_merged pm bids; Some { value = ALoan lc; ty } | AMutLoan _ -> set_loan_as_merged bid; Some { value = ALoan lc; ty } | AEndedMutLoan _ | AEndedSharedLoan _ | AIgnoredMutLoan _ | AEndedIgnoredMutLoan _ | AIgnoredSharedLoan _ -> (* The abstraction has been destructured, so those shouldn't appear *) craise __FILE__ __LINE__ span "Unreachable")) | Some _, Some _ -> (* With projection markers, shared loans should not be duplicated *) craise __FILE__ __LINE__ span "Unreachable" | None, None -> craise __FILE__ __LINE__ span "Unreachable" in push_opt_avalue av)) borrows_loans; (* Reverse the avalues (we visited the loans/borrows in order, but pushed new values at the beggining of the stack of avalues) *) List.rev !avalues (** Auxiliary function for {!merge_abstractions}. Phase 2 of the merge: we remove markers, by merging pairs of the same element with different markers into one element without markers. Example: {[ |MB l0|, MB l1, ︙MB l0︙ ~~> MB l0, MB l1 ]} *) let merge_abstractions_merge_markers (span : Meta.span) (merge_funs : merge_duplicates_funcs option) (ctx : eval_ctx) (abs_values : typed_avalue list) : typed_avalue list = log#ldebug (lazy ("merge_abstractions_merge_markers:\n- avalues:\n" ^ String.concat ", " (List.map (typed_avalue_to_string ctx) abs_values))); (* We linearly traverse the list of avalues created through the first phase. *) (* Utilities to accumulate the list of values resulting from the merge *) let avalues = ref [] in let push_avalue av = log#ldebug (lazy ("merge_abstractions_merge_markers: push_avalue: " ^ typed_avalue_to_string ~span:(Some span) ctx av)); avalues := av :: !avalues in (* Compute some relevant information *) let { loans = _; borrows = _; borrows_loans; loan_to_content; borrow_to_content; } = compute_merge_abstraction_info span ctx abs_values in (* We will merge elements with the same borrow/loan id, but with different markers. Hence, we only keep track of the id here: if [Borrow PLeft bid] has been merged and we see [Borrow PRight bid], we should ignore [Borrow PRight bid] (because when seeing [Borrow PLeft bid] we stored [Borrow PNone bid] into the list of values to insert in the resulting abstraction). *) let merged_borrows = ref BorrowId.Set.empty in let merged_loans = ref BorrowId.Set.empty in let borrow_is_merged id = BorrowId.Set.mem id !merged_borrows in let set_borrow_as_merged id = merged_borrows := BorrowId.Set.add id !merged_borrows in let loan_is_merged id = BorrowId.Set.mem id !merged_loans in let set_loan_as_merged id = merged_loans := BorrowId.Set.add id !merged_loans in let set_loans_as_merged ids = BorrowId.Set.iter set_loan_as_merged ids in (* Recreates an avalue from a borrow_content. *) let avalue_from_bc = function | Concrete (_, _) -> (* This can happen only in case of nested borrows, and should have been filtered during phase 1 *) craise __FILE__ __LINE__ span "Unreachable" | Abstract (ty, bc) -> { value = ABorrow bc; ty } in (* Recreates an avalue from a loan_content, and adds the set of loan ids as merged. See the comment in the loop below for a detailed explanation *) let avalue_from_lc = function | Concrete (_, _) -> (* This can happen only in case of nested borrows, and should have been filtered during phase 1 *) craise __FILE__ __LINE__ span "Unreachable" | Abstract (ty, bc) -> (match bc with | AMutLoan (_, id, _) -> set_loan_as_merged id | ASharedLoan (_, ids, _, _) -> set_loans_as_merged ids | _ -> craise __FILE__ __LINE__ span "Unreachable"); { value = ALoan bc; ty } in let complementary_markers pm0 pm1 = (pm0 = PLeft && pm1 = PRight) || (pm0 = PRight && pm1 = PLeft) in (* Some utility functions *) (* Merge two aborrow contents - note that those contents must have the same id *) let merge_aborrow_contents (ty0 : rty) (bc0 : aborrow_content) (ty1 : rty) (bc1 : aborrow_content) : typed_avalue = match (bc0, bc1) with | AMutBorrow (pm0, id0, child0), AMutBorrow (pm1, id1, child1) -> (* Sanity-check of the precondition *) sanity_check __FILE__ __LINE__ (id0 = id1) span; sanity_check __FILE__ __LINE__ (complementary_markers pm0 pm1) span; (Option.get merge_funs).merge_amut_borrows id0 ty0 pm0 child0 ty1 pm1 child1 | ASharedBorrow (pm0, id0), ASharedBorrow (pm1, id1) -> (* Sanity-check of the precondition *) sanity_check __FILE__ __LINE__ (id0 = id1) span; sanity_check __FILE__ __LINE__ (complementary_markers pm0 pm1) span; (Option.get merge_funs).merge_ashared_borrows id0 ty0 pm0 ty1 pm1 | AProjSharedBorrow _, AProjSharedBorrow _ -> (* Unreachable because requires nested borrows *) craise __FILE__ __LINE__ span "Unreachable" | _ -> (* Unreachable because those cases are ignored (ended/ignored borrows) or inconsistent *) craise __FILE__ __LINE__ span "Unreachable" in let merge_g_borrow_contents (bc0 : g_borrow_content_with_ty) (bc1 : g_borrow_content_with_ty) : typed_avalue = match (bc0, bc1) with | Concrete _, Concrete _ -> (* This can happen only in case of nested borrows - the borrow has to appear inside a shared loan. *) craise __FILE__ __LINE__ span "Unreachable" | Abstract (ty0, bc0), Abstract (ty1, bc1) -> merge_aborrow_contents ty0 bc0 ty1 bc1 | Concrete _, Abstract _ | Abstract _, Concrete _ -> (* TODO: is it really unreachable? *) craise __FILE__ __LINE__ span "Unreachable" in let loan_content_to_ids (lc : g_loan_content_with_ty) : BorrowId.Set.t = match lc with | Abstract (_, lc) -> ( match lc with | AMutLoan (_, id, _) -> BorrowId.Set.singleton id | ASharedLoan (_, ids, _, _) -> ids | _ -> (* Unreachable because those cases are ignored (ended/ignored borrows) or inconsistent *) craise __FILE__ __LINE__ span "Unreachable") | Concrete _ -> (* Can only happen with nested borrows *) craise __FILE__ __LINE__ span "Unreachable" in let merge_aloan_contents (ty0 : rty) (lc0 : aloan_content) (ty1 : rty) (lc1 : aloan_content) : typed_avalue = match (lc0, lc1) with | AMutLoan (pm0, id0, child0), AMutLoan (pm1, id1, child1) -> (* Sanity-check of the precondition *) sanity_check __FILE__ __LINE__ (id0 = id1) span; sanity_check __FILE__ __LINE__ (complementary_markers pm0 pm1) span; (* Merge *) (Option.get merge_funs).merge_amut_loans id0 ty0 pm0 child0 ty1 pm1 child1 | ASharedLoan (pm0, ids0, sv0, child0), ASharedLoan (pm1, ids1, sv1, child1) -> sanity_check __FILE__ __LINE__ (complementary_markers pm0 pm1) span; (* Check that the sets of ids are the same - if it is not the case, it means we actually need to merge more than 2 avalues: we ignore this case for now *) sanity_check __FILE__ __LINE__ (BorrowId.Set.equal ids0 ids1) span; let ids = ids0 in (* Merge *) (Option.get merge_funs).merge_ashared_loans ids ty0 pm0 sv0 child0 ty1 pm1 sv1 child1 | _ -> (* Unreachable because those cases are ignored (ended/ignored borrows) or inconsistent *) craise __FILE__ __LINE__ span "Unreachable" in (* Note that because we may filter ids from a set of id, this function has to register the merged loan ids: the caller doesn't do it (contrary to the borrow case) *) let merge_g_loan_contents (lc0 : g_loan_content_with_ty) (lc1 : g_loan_content_with_ty) : typed_avalue = match (lc0, lc1) with | Concrete _, Concrete _ -> (* This can not happen: the values should have been destructured *) craise __FILE__ __LINE__ span "Unreachable" | Abstract (ty0, lc0), Abstract (ty1, lc1) -> merge_aloan_contents ty0 lc0 ty1 lc1 | Concrete _, Abstract _ | Abstract _, Concrete _ -> (* TODO: is it really unreachable? *) craise __FILE__ __LINE__ span "Unreachable" in let invert_proj_marker = function | PNone -> craise __FILE__ __LINE__ span "Unreachable" | PLeft -> PRight | PRight -> PLeft in (* We now iter over all the accumulated elements. For each element with a marker (i.e., not [PNone]), we attempt to find the dual element in the rest of the list. If so, we remove both elements, and insert the same element but with no marker. Importantly, attempting the merge when first seeing a marked element allows us to preserve the structure of the abstraction we are merging into (abs0). During phase 1, we traversed the borrow_loans of the abs 0 first, and hence these elements are at the top of the list *) List.iter (function | BorrowId (PNone, bid) -> sanity_check __FILE__ __LINE__ (not (borrow_is_merged bid)) span; (* This element has no marker. We do not filter it, hence we retrieve the contents and inject it into the avalues list *) let bc = MarkerBorrowId.Map.find (PNone, bid) borrow_to_content in push_avalue (avalue_from_bc bc); (* Setting the borrow as merged is not really necessary but we do it for consistency, and this allows us to do some sanity checks. *) set_borrow_as_merged bid | BorrowId (pm, bid) -> (* Check if the borrow has already been merged. If so, it means we already added the merged value to the avalues list, and we can thus skip it *) if borrow_is_merged bid then () else ( (* Not merged: set it as merged *) set_borrow_as_merged bid; (* Lookup the content of the borrow *) let bc0 = MarkerBorrowId.Map.find (pm, bid) borrow_to_content in (* Check if there exists the same borrow but with the complementary marker *) let obc1 = MarkerBorrowId.Map.find_opt (invert_proj_marker pm, bid) borrow_to_content in match obc1 with | None -> (* No dual element found, we keep the current one in the list of avalues, with the same marker *) push_avalue (avalue_from_bc bc0) | Some bc1 -> (* We have borrows with left and right markers in the environment. We merge their values, and push the result to the list of avalues. The merge will also remove the projection marker *) push_avalue (merge_g_borrow_contents bc0 bc1)) | LoanId (PNone, bid) -> (* Since we currently have a set of loan ids associated to a shared_borrow, we can have several loan ids associated to the same element. Hence, we need to ensure that we did not add the corresponding element previously. To do so, we use the loan id merged set for both marked and unmarked values. The assumption is that we should not have the same loan id for both an unmarked element and a marked element. It might be better to sanity-check this. Adding the loan id to the merged set will be done inside avalue_from_lc. Rem: Once we move to a single loan id per shared_loan, this should not be needed anymore. *) if loan_is_merged bid then () else let lc = MarkerBorrowId.Map.find (PNone, bid) loan_to_content in push_avalue (avalue_from_lc lc); (* Mark as merged *) let ids = loan_content_to_ids lc in set_loans_as_merged ids | LoanId (pm, bid) -> ( if (* Check if the loan has already been merged. If so, we skip it. *) loan_is_merged bid then () else let lc0 = MarkerBorrowId.Map.find (pm, bid) loan_to_content in let olc1 = MarkerBorrowId.Map.find_opt (invert_proj_marker pm, bid) loan_to_content in (* Mark as merged *) let ids0 = loan_content_to_ids lc0 in set_loans_as_merged ids0; match olc1 with | None -> (* No dual element found, we keep the current one with the same marker *) push_avalue (avalue_from_lc lc0) | Some lc1 -> push_avalue (merge_g_loan_contents lc0 lc1); (* Mark as merged *) let ids1 = loan_content_to_ids lc1 in set_loans_as_merged ids1)) borrows_loans; let avalues = List.rev !avalues in (* Reorder the avalues. We want the avalues to have the borrows first, then the loans (this structure is more stable when we merge abstractions together, meaning it is easier to find fixed points). *) let is_borrow (av : typed_avalue) : bool = match av.value with | ABorrow _ -> true | ALoan _ -> false | _ -> craise __FILE__ __LINE__ span "Unexpected" in let aborrows, aloans = List.partition is_borrow avalues in List.append aborrows aloans (** Auxiliary function. Merge two abstractions into one, without updating the context. *) let merge_abstractions (span : Meta.span) (abs_kind : abs_kind) (can_end : bool) (merge_funs : merge_duplicates_funcs option) (ctx : eval_ctx) (abs0 : abs) (abs1 : abs) : abs = log#ldebug (lazy ("merge_abstractions:\n- abs0:\n" ^ abs_to_string span ctx abs0 ^ "\n\n- abs1:\n" ^ abs_to_string span ctx abs1)); (* Sanity check: we can't merge an abstraction with itself *) sanity_check __FILE__ __LINE__ (abs0.abs_id <> abs1.abs_id) span; (* Check that the abstractions are destructured (i.e., there are no nested values, etc.) *) if !Config.sanity_checks then ( let destructure_shared_values = true in sanity_check __FILE__ __LINE__ (abs_is_destructured span destructure_shared_values ctx abs0) span; sanity_check __FILE__ __LINE__ (abs_is_destructured span destructure_shared_values ctx abs1) span); (* Phase 1: simplify the loans coming from the left abstraction with the borrows coming from the right abstraction. *) let avalues = merge_abstractions_merge_loan_borrow_pairs span merge_funs ctx abs0 abs1 in (* Phase 2: we now remove markers, by merging pairs of the same element with different markers into one element. To do so, we linearly traverse the list of avalues created through the first phase. *) let avalues = merge_abstractions_merge_markers span merge_funs ctx avalues in (* Create the new abstraction *) let abs_id = fresh_abstraction_id () in (* Note that one of the two abstractions might a parent of the other *) let parents = AbstractionId.Set.diff (AbstractionId.Set.union abs0.parents abs1.parents) (AbstractionId.Set.of_list [ abs0.abs_id; abs1.abs_id ]) in let original_parents = AbstractionId.Set.elements parents in let regions = RegionId.Set.union abs0.regions abs1.regions in let ancestors_regions = RegionId.Set.diff (RegionId.Set.union abs0.regions abs1.regions) regions in let abs = { abs_id; kind = abs_kind; can_end; parents; original_parents; regions; ancestors_regions; avalues; } in (* Sanity check *) sanity_check __FILE__ __LINE__ (abs_is_destructured span true ctx abs) span; (* Return *) abs (** Merge the regions in a context to a single region *) let ctx_merge_regions (ctx : eval_ctx) (rid : RegionId.id) (rids : RegionId.Set.t) : eval_ctx = let rsubst x = if RegionId.Set.mem x rids then rid else x in let env = Substitute.env_subst_rids rsubst ctx.env in { ctx with env } let merge_into_first_abstraction (span : Meta.span) (abs_kind : abs_kind) (can_end : bool) (merge_funs : merge_duplicates_funcs option) (ctx : eval_ctx) (abs_id0 : AbstractionId.id) (abs_id1 : AbstractionId.id) : eval_ctx * AbstractionId.id = (* Lookup the abstractions *) let abs0 = ctx_lookup_abs ctx abs_id0 in let abs1 = ctx_lookup_abs ctx abs_id1 in (* Merge them *) let nabs = merge_abstractions span abs_kind can_end merge_funs ctx abs0 abs1 in (* Update the environment: replace the abstraction 0 with the result of the merge, remove the abstraction 1 *) let ctx = fst (ctx_subst_abs span ctx abs_id0 nabs) in let ctx = fst (ctx_remove_abs span ctx abs_id1) in (* Merge all the regions from the abstraction into one (the first - i.e., the one with the smallest id). Note that we need to do this in the whole environment, as those regions may be referenced as ancestor regions by the other abstractions, and may be present in symbolic values, etc. (this is not the case if there are no nested borrows, but we anticipate). *) let ctx = let regions = nabs.regions in (* Pick the first region id (this is the smallest) *) let rid = RegionId.Set.min_elt regions in (* If there is only one region, do nothing *) if RegionId.Set.cardinal regions = 1 then ctx else let rids = RegionId.Set.remove rid regions in ctx_merge_regions ctx rid rids in (* Return *) (ctx, nabs.abs_id) let reorder_loans_borrows_in_fresh_abs (span : Meta.span) (allow_markers : bool) (old_abs_ids : AbstractionId.Set.t) (ctx : eval_ctx) : eval_ctx = let reorder_in_fresh_abs (abs : abs) : abs = (* Split between the loans and borrows *) let is_borrow (av : typed_avalue) : bool = match av.value with | ABorrow _ -> true | ALoan _ -> false | _ -> craise __FILE__ __LINE__ span "Unexpected" in let aborrows, aloans = List.partition is_borrow abs.avalues in (* Reoder the borrows, and the loans. After experimenting, it seems that a good way of reordering the loans and the borrows to find fixed points is simply to sort them by increasing order of id (taking the smallest id of a set of ids, in case of sets). This is actually not as arbitrary as it might seem, because the ids give us the order in which we introduced those borrows/loans. *) let get_borrow_id (av : typed_avalue) : BorrowId.id = match av.value with | ABorrow (AMutBorrow (pm, bid, _) | ASharedBorrow (pm, bid)) -> sanity_check __FILE__ __LINE__ (allow_markers || pm = PNone) span; bid | _ -> craise __FILE__ __LINE__ span "Unexpected" in let get_loan_id (av : typed_avalue) : BorrowId.id = match av.value with | ALoan (AMutLoan (pm, lid, _)) -> sanity_check __FILE__ __LINE__ (allow_markers || pm = PNone) span; lid | ALoan (ASharedLoan (pm, lids, _, _)) -> sanity_check __FILE__ __LINE__ (allow_markers || pm = PNone) span; BorrowId.Set.min_elt lids | _ -> craise __FILE__ __LINE__ span "Unexpected" in (* We use ordered maps to reorder the borrows and loans *) let reorder (get_bid : typed_avalue -> BorrowId.id) (values : typed_avalue list) : typed_avalue list = List.map snd (BorrowId.Map.bindings (BorrowId.Map.of_list (List.map (fun v -> (get_bid v, v)) values))) in let aborrows = reorder get_borrow_id aborrows in let aloans = reorder get_loan_id aloans in let avalues = List.append aborrows aloans in { abs with avalues } in let reorder_in_abs (abs : abs) = if AbstractionId.Set.mem abs.abs_id old_abs_ids then abs else reorder_in_fresh_abs abs in let env = env_map_abs reorder_in_abs ctx.env in { ctx with env }