diff options
Diffstat (limited to 'src/InterpreterExpressions.ml')
-rw-r--r-- | src/InterpreterExpressions.ml | 191 |
1 files changed, 133 insertions, 58 deletions
diff --git a/src/InterpreterExpressions.ml b/src/InterpreterExpressions.ml index 57ee0526..04ad1b3c 100644 --- a/src/InterpreterExpressions.ml +++ b/src/InterpreterExpressions.ml @@ -7,6 +7,7 @@ open Errors module C = Contexts module Subst = Substitute module L = Logging +module PV = Print.Values open TypesUtils open ValuesUtils module Inv = Invariants @@ -49,50 +50,93 @@ let expand_primitively_copyable_at_place (config : C.config) (* Apply *) expand cf ctx +(** Read a place (CPS-style function). + + We also check that the value *doesn't contain bottoms or inactivated + borrows. + *) +let read_place (config : C.config) (access : access_kind) (p : E.place) + (cf : V.typed_value -> m_fun) : m_fun = + fun ctx -> + let v = read_place_unwrap config access p ctx in + (* Check that there are no bottoms in the value *) + assert (not (bottom_in_value ctx.ended_regions v)); + (* Check that there are no inactivated borrows in the value *) + assert (not (inactivated_in_value v)); + (* Call the continuation *) + cf v ctx + (** Small utility. + Prepare the access to a place in a right-value (typically an operand) by + reorganizing the environment. + + We reorganize the environment so that: + - we can access the place (we prepare *along* the path) + - the value at the place itself doesn't contain loans (the `access_kind` + controls whether we only end mutable loans, or also shared loans). + + We also check, after the reorganization, that the value at the place + *doesn't contain any bottom nor inactivated borrows*. + [expand_prim_copy]: if true, expand the symbolic values which are primitively copyable and contain borrows. *) -let prepare_rplace (config : C.config) (expand_prim_copy : bool) - (access : access_kind) (p : E.place) (cf : V.typed_value -> m_fun) : m_fun = +let access_rplace_reorganize_and_read (config : C.config) + (expand_prim_copy : bool) (access : access_kind) (p : E.place) + (cf : V.typed_value -> m_fun) : m_fun = fun ctx -> + (* Make sure we can evaluate the path *) let cc = update_ctx_along_read_place config access p in + (* End the proper loans at the place itself *) let cc = comp cc (end_loans_at_place config access p) in + (* Expand the copyable values which contain borrows (which are necessarily shared + * borrows) *) let cc = if expand_prim_copy then comp cc (expand_primitively_copyable_at_place config access p) else cc in - let read_place cf : m_fun = - fun ctx -> - let v = read_place_unwrap config access p ctx in - cf v ctx - in + (* Read the place - note that this checks that the value doesn't contain bottoms *) + let read_place = read_place config access p in + (* Compose *) comp cc read_place cf ctx -(** Convert a constant operand value to a typed value *) -let typecheck_constant_value (ty : T.ety) (cv : V.constant_value) : V.typed_value = - (* Check the type while converting - - * we actually need some information contained in the type *) +let access_rplace_reorganize (config : C.config) (expand_prim_copy : bool) + (access : access_kind) (p : E.place) : cm_fun = + fun cf ctx -> + access_rplace_reorganize_and_read config expand_prim_copy access p + (fun _v -> cf) + ctx + +(** Convert an operand constant operand value to a typed value *) +let typecheck_constant_value (ty : T.ety) + (cv : V.constant_value) : V.typed_value = + (* Check the type while converting - we actually need some information + * contained in the type *) + log#ldebug + (lazy + ("typecheck_constant_value:" ^ "\n- cv: " + ^ PV.constant_value_to_string cv)); match (ty, cv) with (* Scalar, boolean... *) - | T.Bool, (Bool v) -> { V.value = V.Concrete (Bool v); ty } - | T.Char, (Char v) -> { V.value = V.Concrete (Char v); ty } - | T.Str, (String v) -> { V.value = V.Concrete (String v); ty } - | T.Integer int_ty, (V.Scalar v) -> + | T.Bool, Bool v -> { V.value = V.Concrete (Bool v); ty } + | T.Char, Char v -> { V.value = V.Concrete (Char v); ty } + | T.Str, String v -> { V.value = V.Concrete (String v); ty } + | T.Integer int_ty, V.Scalar v -> (* Check the type and the ranges *) assert (int_ty = v.int_ty); assert (check_scalar_value_in_range v); { V.value = V.Concrete (V.Scalar v); ty } (* Remaining cases (invalid) - listing as much as we can on purpose (allows to catch errors at compilation if the definitions change) *) - | _, _ -> failwith "Improperly typed constant value" + | _, _ -> + failwith "Improperly typed constant value" -(** Prepare the evaluation of an operand. +(** Reorganize the environment in preparation for the evaluation of an operand. - Evaluating an operand requires updating the context to get access to a - given place (by ending borrows, expanding symbolic values...) then + Evaluating an operand requires reorganizing the environment to get access + to a given place (by ending borrows, expanding symbolic values...) then applying the operand operation (move, copy, etc.). Sometimes, we want to decouple the two operations. @@ -106,7 +150,7 @@ let typecheck_constant_value (ty : T.ety) (cv : V.constant_value) : V.typed_valu dest <- f(move x, move y); ... ``` - Because of the way end_borrow is implemented, when giving back the borrow + Because of the way `end_borrow` is implemented, when giving back the borrow `l0` upon evaluating `move y`, we won't notice that `shared_borrow l0` has disappeared from the environment (it has been moved and not assigned yet, and so is hanging in "thin air"). @@ -114,58 +158,56 @@ let typecheck_constant_value (ty : T.ety) (cv : V.constant_value) : V.typed_valu By first "preparing" the operands evaluation, we make sure no such thing happens. To be more precise, we make sure all the updates to borrows triggered by access *and* move operations have already been applied. + + Rk.: in the formalization, we always have an explicit "reorganization" step + in the rule premises, before the actual operand evaluation. - As a side note: doing this is actually not completely necessary because when + Rk.: doing this is actually not completely necessary because when generating MIR, rustc introduces intermediate assignments for all the function parameters. Still, it is better for soundness purposes, and corresponds to - what we do in the formal semantics. + what we do in the formalization (because we don't enforce constraints + in the formalization). *) -let eval_operand_prepare (config : C.config) (op : E.operand) - (cf : V.typed_value -> m_fun) : m_fun = - fun ctx -> - let prepare (cf : V.typed_value -> m_fun) : m_fun = - fun ctx -> +let prepare_eval_operand_reorganize (config : C.config) (op : E.operand) : + cm_fun = + fun cf ctx -> + let prepare : cm_fun = + fun cf ctx -> match op with | Expressions.Constant (ty, cv) -> - cf (typecheck_constant_value ty cv) ctx + typecheck_constant_value ty cv |> ignore; + cf ctx | Expressions.Copy p -> (* Access the value *) let access = Read in (* Expand the symbolic values, if necessary *) let expand_prim_copy = true in - prepare_rplace config expand_prim_copy access p cf ctx + access_rplace_reorganize config expand_prim_copy access p cf ctx | Expressions.Move p -> (* Access the value *) let access = Move in let expand_prim_copy = false in - prepare_rplace config expand_prim_copy access p cf ctx + access_rplace_reorganize config expand_prim_copy access p cf ctx in - (* Sanity check *) - let check cf v : m_fun = - fun ctx -> - assert (not (bottom_in_value ctx.ended_regions v)); - cf v ctx - in - (* Compose and apply *) - comp prepare check cf ctx + (* Apply *) + prepare cf ctx -(** Evaluate an operand. *) -let eval_operand (config : C.config) (op : E.operand) +(** Evaluate an operand, without reorganizing the context before *) +let eval_operand_no_reorganize (config : C.config) (op : E.operand) (cf : V.typed_value -> m_fun) : m_fun = fun ctx -> (* Debug *) log#ldebug (lazy - ("eval_operand: op: " ^ operand_to_string ctx op ^ "\n- ctx:\n" - ^ eval_ctx_to_string ctx ^ "\n")); + ("eval_operand_no_reorganize: op: " ^ operand_to_string ctx op + ^ "\n- ctx:\n" ^ eval_ctx_to_string ctx ^ "\n")); (* Evaluate *) match op with | Expressions.Constant (ty, cv) -> cf (typecheck_constant_value ty cv) ctx | Expressions.Copy p -> (* Access the value *) let access = Read in - let expand_prim_copy = true in - let cc = prepare_rplace config expand_prim_copy access p in + let cc = read_place config access p in (* Copy the value *) let copy cf v : m_fun = fun ctx -> @@ -175,8 +217,8 @@ let eval_operand (config : C.config) (op : E.operand) Option.is_none (find_first_primitively_copyable_sv_with_borrows ctx.type_context.type_infos v)); - let allow_adt_copy = false in (* Actually perform the copy *) + let allow_adt_copy = false in let ctx, v = copy_value allow_adt_copy config ctx v in (* Continue *) cf v ctx @@ -186,11 +228,11 @@ let eval_operand (config : C.config) (op : E.operand) | Expressions.Move p -> (* Access the value *) let access = Move in - let expand_prim_copy = false in - let cc = prepare_rplace config expand_prim_copy access p in + let cc = read_place config access p in (* Move the value *) let move cf v : m_fun = fun ctx -> + (* Check that there are no bottoms in the value we are about to move *) assert (not (bottom_in_value ctx.ended_regions v)); let bottom : V.typed_value = { V.value = Bottom; ty = v.ty } in match write_place config access p bottom ctx with @@ -200,24 +242,49 @@ let eval_operand (config : C.config) (op : E.operand) (* Compose and apply *) comp cc move cf ctx +(** Evaluate an operand. + + Reorganize the context, then evaluate the operand. + + **Warning**: this function shouldn't be used to evaluate a list of + operands (for a function call, for instance): we must do *one* reorganization + of the environment, before evaluating all the operands at once. + Use [`eval_operands`] instead. + *) +let eval_operand (config : C.config) (op : E.operand) + (cf : V.typed_value -> m_fun) : m_fun = + fun ctx -> + (* Debug *) + log#ldebug + (lazy + ("eval_operand: op: " ^ operand_to_string ctx op ^ "\n- ctx:\n" + ^ eval_ctx_to_string ctx ^ "\n")); + (* We reorganize the context, then evaluate the operand *) + comp + (prepare_eval_operand_reorganize config op) + (eval_operand_no_reorganize config op) + cf ctx + (** Small utility. - See [eval_operand_prepare]. + See [prepare_eval_operand_reorganize]. *) -let eval_operands_prepare (config : C.config) (ops : E.operand list) - (cf : V.typed_value list -> m_fun) : m_fun = - fold_left_apply_continuation (eval_operand_prepare config) ops cf +let prepare_eval_operands_reorganize (config : C.config) (ops : E.operand list) + : cm_fun = + fold_left_apply_continuation (prepare_eval_operand_reorganize config) ops (** Evaluate several operands. *) let eval_operands (config : C.config) (ops : E.operand list) (cf : V.typed_value list -> m_fun) : m_fun = fun ctx -> (* Prepare the operands *) - let prepare = eval_operands_prepare config ops in + let prepare = prepare_eval_operands_reorganize config ops in (* Evaluate the operands *) - let eval = fold_left_apply_continuation (eval_operand config) ops in + let eval = + fold_left_list_apply_continuation (eval_operand_no_reorganize config) ops + in (* Compose and apply *) - comp prepare (fun cf (_ : V.typed_value list) -> eval cf) cf ctx + comp prepare eval cf ctx let eval_two_operands (config : C.config) (op1 : E.operand) (op2 : E.operand) (cf : V.typed_value * V.typed_value -> m_fun) : m_fun = @@ -436,7 +503,9 @@ let eval_rvalue_discriminant_concrete (config : C.config) (p : E.place) (* Access the value *) let access = Read in let expand_prim_copy = false in - let prepare = prepare_rplace config expand_prim_copy access p in + let prepare = + access_rplace_reorganize_and_read config expand_prim_copy access p + in (* Read the value *) let read (cf : V.typed_value -> m_fun) (v : V.typed_value) : m_fun = (* The value may be shared: we need to ignore the shared loans *) @@ -474,7 +543,9 @@ let eval_rvalue_discriminant (config : C.config) (p : E.place) (* Access the value *) let access = Read in let expand_prim_copy = false in - let prepare = prepare_rplace config expand_prim_copy access p in + let prepare = + access_rplace_reorganize_and_read config expand_prim_copy access p + in (* Read the value *) let read (cf : V.typed_value -> m_fun) (v : V.typed_value) : m_fun = fun ctx -> @@ -506,7 +577,9 @@ let eval_rvalue_ref (config : C.config) (p : E.place) (bkind : E.borrow_kind) (* Access the value *) let access = if bkind = E.Shared then Read else Write in let expand_prim_copy = false in - let prepare = prepare_rplace config expand_prim_copy access p in + let prepare = + access_rplace_reorganize_and_read config expand_prim_copy access p + in (* Evaluate the borrowing operation *) let eval (cf : V.typed_value -> m_fun) (v : V.typed_value) : m_fun = fun ctx -> @@ -547,7 +620,9 @@ let eval_rvalue_ref (config : C.config) (p : E.place) (bkind : E.borrow_kind) (* Access the value *) let access = Write in let expand_prim_copy = false in - let prepare = prepare_rplace config expand_prim_copy access p in + let prepare = + access_rplace_reorganize_and_read config expand_prim_copy access p + in (* Evaluate the borrowing operation *) let eval (cf : V.typed_value -> m_fun) (v : V.typed_value) : m_fun = fun ctx -> |