diff options
Diffstat (limited to 'compiler/InterpreterStatements.ml')
-rw-r--r-- | compiler/InterpreterStatements.ml | 1370 |
1 files changed, 1370 insertions, 0 deletions
diff --git a/compiler/InterpreterStatements.ml b/compiler/InterpreterStatements.ml new file mode 100644 index 00000000..4e61e683 --- /dev/null +++ b/compiler/InterpreterStatements.ml @@ -0,0 +1,1370 @@ +module T = Types +module V = Values +module E = Expressions +module C = Contexts +module Subst = Substitute +module A = LlbcAst +module L = Logging +open TypesUtils +open ValuesUtils +module Inv = Invariants +module S = SynthesizeSymbolic +open Errors +open Cps +open InterpreterUtils +open InterpreterProjectors +open InterpreterExpansion +open InterpreterPaths +open InterpreterExpressions + +(** The local logger *) +let log = L.statements_log + +(** Drop a value at a given place - TODO: factorize this with [assign_to_place] *) +let drop_value (config : C.config) (p : E.place) : cm_fun = + fun cf ctx -> + log#ldebug + (lazy + ("drop_value: place: " ^ place_to_string ctx p ^ "\n- Initial context:\n" + ^ eval_ctx_to_string ctx)); + (* Prepare the place (by ending the outer loans). + * Note that {!prepare_lplace} will use the [Write] access kind: + * it is ok, because when updating the value with {!Bottom} below, + * we will use the [Move] access *) + let end_borrows = false in + let prepare = prepare_lplace config end_borrows p in + (* Replace the value with {!Bottom} *) + let replace cf (v : V.typed_value) ctx = + (* Move the value at destination (that we will overwrite) to a dummy variable + * to preserve the borrows *) + let mv = read_place_unwrap config Write p ctx in + let ctx = C.ctx_push_dummy_var ctx mv in + (* Update the destination to ⊥ *) + let nv = { v with value = V.Bottom } in + let ctx = write_place_unwrap config Move p nv ctx in + log#ldebug + (lazy + ("drop_value: place: " ^ place_to_string ctx p ^ "\n- Final context:\n" + ^ eval_ctx_to_string ctx)); + cf ctx + in + (* Compose and apply *) + comp prepare replace cf ctx + +(** Push a dummy variable to the environment *) +let push_dummy_var (v : V.typed_value) : cm_fun = + fun cf ctx -> + let ctx = C.ctx_push_dummy_var ctx v in + cf ctx + +(** Pop a dummy variable from the environment *) +let pop_dummy_var (cf : V.typed_value -> m_fun) : m_fun = + fun ctx -> + let ctx, v = C.ctx_pop_dummy_var ctx in + cf v ctx + +(** Push an uninitialized variable to the environment *) +let push_uninitialized_var (var : A.var) : cm_fun = + fun cf ctx -> + let ctx = C.ctx_push_uninitialized_var ctx var in + cf ctx + +(** Push a list of uninitialized variables to the environment *) +let push_uninitialized_vars (vars : A.var list) : cm_fun = + fun cf ctx -> + let ctx = C.ctx_push_uninitialized_vars ctx vars in + cf ctx + +(** Push a variable to the environment *) +let push_var (var : A.var) (v : V.typed_value) : cm_fun = + fun cf ctx -> + let ctx = C.ctx_push_var ctx var v in + cf ctx + +(** Push a list of variables to the environment *) +let push_vars (vars : (A.var * V.typed_value) list) : cm_fun = + fun cf ctx -> + let ctx = C.ctx_push_vars ctx vars in + cf ctx + +(** Assign a value to a given place. + + Note that this function first pushes the value to assign in a dummy variable, + then prepares the destination (by ending borrows, etc.) before popping the + dummy variable and putting in its destination (after having checked that + preparing the destination didn't introduce ⊥). + *) +let assign_to_place (config : C.config) (rv : V.typed_value) (p : E.place) : + cm_fun = + fun cf ctx -> + log#ldebug + (lazy + ("assign_to_place:" ^ "\n- rv: " + ^ typed_value_to_string ctx rv + ^ "\n- p: " ^ place_to_string ctx p ^ "\n- Initial context:\n" + ^ eval_ctx_to_string ctx)); + (* Push the rvalue to a dummy variable, for bookkeeping *) + let cc = push_dummy_var rv in + (* Prepare the destination *) + let end_borrows = false in + let cc = comp cc (prepare_lplace config end_borrows p) in + (* Retrieve the rvalue from the dummy variable *) + let cc = comp cc (fun cf _lv -> pop_dummy_var cf) in + (* Update the destination *) + let move_dest cf (rv : V.typed_value) : m_fun = + fun ctx -> + (* Move the value at destination (that we will overwrite) to a dummy variable + * to preserve the borrows *) + let mv = read_place_unwrap config Write p ctx in + let ctx = C.ctx_push_dummy_var ctx mv in + (* Write to the destination *) + (* Checks - maybe the bookkeeping updated the rvalue and introduced bottoms *) + assert (not (bottom_in_value ctx.ended_regions rv)); + (* Update the destination *) + let ctx = write_place_unwrap config Write p rv ctx in + (* Debug *) + log#ldebug + (lazy + ("assign_to_place:" ^ "\n- rv: " + ^ typed_value_to_string ctx rv + ^ "\n- p: " ^ place_to_string ctx p ^ "\n- Final context:\n" + ^ eval_ctx_to_string ctx)); + (* Continue *) + cf ctx + in + (* Compose and apply *) + comp cc move_dest cf ctx + +(** Evaluate an assertion, when the scrutinee is not symbolic *) +let eval_assertion_concrete (config : C.config) (assertion : A.assertion) : + st_cm_fun = + fun cf ctx -> + (* There won't be any symbolic expansions: fully evaluate the operand *) + let eval_op = eval_operand config assertion.cond in + let eval_assert cf (v : V.typed_value) : m_fun = + fun ctx -> + match v.value with + | Concrete (Bool b) -> + (* Branch *) + if b = assertion.expected then cf Unit ctx else cf Panic ctx + | _ -> + raise + (Failure ("Expected a boolean, got: " ^ typed_value_to_string ctx v)) + in + (* Compose and apply *) + comp eval_op eval_assert cf ctx + +(** Evaluates an assertion. + + In the case the boolean under scrutinee is symbolic, we synthesize + a call to [assert ...] then continue in the success branch (and thus + expand the boolean to [true]). + *) +let eval_assertion (config : C.config) (assertion : A.assertion) : st_cm_fun = + fun cf ctx -> + (* Evaluate the operand *) + let eval_op = eval_operand config assertion.cond in + (* Evaluate the assertion *) + let eval_assert cf (v : V.typed_value) : m_fun = + fun ctx -> + assert (v.ty = T.Bool); + (* We make a choice here: we could completely decouple the concrete and + * symbolic executions here but choose not to. In the case where we + * know the concrete value of the boolean we test, we use this value + * even if we are in symbolic mode. Note that this case should be + * extremely rare... *) + match v.value with + | Concrete (Bool _) -> + (* Delegate to the concrete evaluation function *) + eval_assertion_concrete config assertion cf ctx + | Symbolic sv -> + assert (config.mode = C.SymbolicMode); + assert (sv.V.sv_ty = T.Bool); + (* Expand the symbolic value and call the proper continuation functions + * for the true and false cases - TODO: call an "assert" function instead *) + let cf_true : m_fun = fun ctx -> cf Unit ctx in + let cf_false : m_fun = fun ctx -> cf Panic ctx in + let expand = + expand_symbolic_bool config sv + (S.mk_opt_place_from_op assertion.cond ctx) + cf_true cf_false + in + expand ctx + | _ -> + raise + (Failure ("Expected a boolean, got: " ^ typed_value_to_string ctx v)) + in + (* Compose and apply *) + comp eval_op eval_assert cf ctx + +(** Updates the discriminant of a value at a given place. + + There are two situations: + - either the discriminant is already the proper one (in which case we + don't do anything) + - or it is not the proper one (because the variant is not the proper + one, or the value is actually {!V.Bottom} - this happens when + initializing ADT values), in which case we replace the value with + a variant with all its fields set to {!V.Bottom}. + For instance, something like: [Cons Bottom Bottom]. + *) +let set_discriminant (config : C.config) (p : E.place) + (variant_id : T.VariantId.id) : st_cm_fun = + fun cf ctx -> + log#ldebug + (lazy + ("set_discriminant:" ^ "\n- p: " ^ place_to_string ctx p + ^ "\n- variant id: " + ^ T.VariantId.to_string variant_id + ^ "\n- initial context:\n" ^ eval_ctx_to_string ctx)); + (* Access the value *) + let access = Write in + let cc = update_ctx_along_read_place config access p in + let end_borrows = false in + let cc = comp cc (prepare_lplace config end_borrows p) in + (* Update the value *) + let update_value cf (v : V.typed_value) : m_fun = + fun ctx -> + match (v.V.ty, v.V.value) with + | ( T.Adt (((T.AdtId _ | T.Assumed T.Option) as type_id), regions, types), + V.Adt av ) -> ( + (* There are two situations: + - either the discriminant is already the proper one (in which case we + don't do anything) + - or it is not the proper one, in which case we replace the value with + a variant with all its fields set to {!Bottom} + *) + match av.variant_id with + | None -> raise (Failure "Found a struct value while expected an enum") + | Some variant_id' -> + if variant_id' = variant_id then (* Nothing to do *) + cf Unit ctx + else + (* Replace the value *) + let bottom_v = + match type_id with + | T.AdtId def_id -> + compute_expanded_bottom_adt_value + ctx.type_context.type_decls def_id (Some variant_id) + regions types + | T.Assumed T.Option -> + assert (regions = []); + compute_expanded_bottom_option_value variant_id + (Collections.List.to_cons_nil types) + | _ -> raise (Failure "Unreachable") + in + assign_to_place config bottom_v p (cf Unit) ctx) + | ( T.Adt (((T.AdtId _ | T.Assumed T.Option) as type_id), regions, types), + V.Bottom ) -> + let bottom_v = + match type_id with + | T.AdtId def_id -> + compute_expanded_bottom_adt_value ctx.type_context.type_decls + def_id (Some variant_id) regions types + | T.Assumed T.Option -> + assert (regions = []); + compute_expanded_bottom_option_value variant_id + (Collections.List.to_cons_nil types) + | _ -> raise (Failure "Unreachable") + in + assign_to_place config bottom_v p (cf Unit) ctx + | _, V.Symbolic _ -> + assert (config.mode = SymbolicMode); + (* This is a bit annoying: in theory we should expand the symbolic value + * then set the discriminant, because in the case the discriminant is + * exactly the one we set, the fields are left untouched, and in the + * other cases they are set to Bottom. + * For now, we forbid setting the discriminant of a symbolic value: + * setting a discriminant should only be used to initialize a value, + * or reset an already initialized value, really. *) + raise (Failure "Unexpected value") + | _, (V.Adt _ | V.Bottom) -> raise (Failure "Inconsistent state") + | _, (V.Concrete _ | V.Borrow _ | V.Loan _) -> + raise (Failure "Unexpected value") + in + (* Compose and apply *) + comp cc update_value cf ctx + +(** Push a frame delimiter in the context's environment *) +let ctx_push_frame (ctx : C.eval_ctx) : C.eval_ctx = + { ctx with env = Frame :: ctx.env } + +(** Push a frame delimiter in the context's environment *) +let push_frame : cm_fun = fun cf ctx -> cf (ctx_push_frame ctx) + +(** Small helper: compute the type of the return value for a specific + instantiation of a non-local function. + *) +let get_non_local_function_return_type (fid : A.assumed_fun_id) + (region_params : T.erased_region list) (type_params : T.ety list) : T.ety = + (* [Box::free] has a special treatment *) + match (fid, region_params, type_params) with + | A.BoxFree, [], [ _ ] -> mk_unit_ty + | _ -> + (* Retrieve the function's signature *) + let sg = Assumed.get_assumed_sig fid in + (* Instantiate the return type *) + let tsubst = + Subst.make_type_subst + (List.map (fun v -> v.T.index) sg.type_params) + type_params + in + Subst.erase_regions_substitute_types tsubst sg.output + +let move_return_value (config : C.config) (cf : V.typed_value -> m_fun) : m_fun + = + fun ctx -> + let ret_vid = V.VarId.zero in + let cc = eval_operand config (E.Move (mk_place_from_var_id ret_vid)) in + cc cf ctx + +(** Pop the current frame. + + Drop all the local variables but the return variable, move the return + value out of the return variable, remove all the local variables (but not the + abstractions!) from the context, remove the {!C.Frame} indicator delimiting the + current frame and handle the return value to the continuation. + + TODO: rename (remove the "ctx_") + *) +let ctx_pop_frame (config : C.config) (cf : V.typed_value -> m_fun) : m_fun = + fun ctx -> + (* Debug *) + log#ldebug (lazy ("ctx_pop_frame:\n" ^ eval_ctx_to_string ctx)); + + (* List the local variables, but the return variable *) + let ret_vid = V.VarId.zero in + let rec list_locals env = + match env with + | [] -> raise (Failure "Inconsistent environment") + | C.Abs _ :: env -> list_locals env + | C.Var (None, _) :: env -> list_locals env + | C.Var (Some var, _) :: env -> + let locals = list_locals env in + if var.index <> ret_vid then var.index :: locals else locals + | C.Frame :: _ -> [] + in + let locals : V.VarId.id list = list_locals ctx.env in + (* Debug *) + log#ldebug + (lazy + ("ctx_pop_frame: locals in which to drop the outer loans: [" + ^ String.concat "," (List.map V.VarId.to_string locals) + ^ "]")); + + (* Move the return value out of the return variable *) + let cc = move_return_value config in + (* Sanity check *) + let cc = + comp_check_value cc (fun ret_value ctx -> + assert (not (bottom_in_value ctx.ended_regions ret_value))) + in + + (* Drop the outer *loans* we find in the local variables *) + let cf_drop_loans_in_locals cf (ret_value : V.typed_value) : m_fun = + (* Drop the loans *) + let end_borrows = false in + let locals = List.rev locals in + let cf_drop = + List.fold_left + (fun cf lid -> + drop_outer_borrows_loans_at_lplace config end_borrows + (mk_place_from_var_id lid) cf) + (cf ret_value) locals + in + (* Apply *) + cf_drop + in + let cc = comp cc cf_drop_loans_in_locals in + (* Debug *) + let cc = + comp_check_value cc (fun _ ctx -> + log#ldebug + (lazy + ("ctx_pop_frame: after dropping outer loans in local variables:\n" + ^ eval_ctx_to_string ctx))) + in + + (* Pop the frame - we remove the [Frame] delimiter, and reintroduce all + * the local variables (which may still contain borrow permissions - but + * no outer loans) as dummy variables in the caller frame *) + let rec pop env = + match env with + | [] -> raise (Failure "Inconsistent environment") + | C.Abs abs :: env -> C.Abs abs :: pop env + | C.Var (_, v) :: env -> C.Var (None, v) :: pop env + | C.Frame :: env -> (* Stop here *) env + in + let cf_pop cf (ret_value : V.typed_value) : m_fun = + fun ctx -> + let env = pop ctx.env in + let ctx = { ctx with env } in + cf ret_value ctx + in + (* Compose and apply *) + comp cc cf_pop cf ctx + +(** Pop the current frame and assign the returned value to its destination. *) +let pop_frame_assign (config : C.config) (dest : E.place) : cm_fun = + let cf_pop = ctx_pop_frame config in + let cf_assign cf ret_value : m_fun = + assign_to_place config ret_value dest cf + in + comp cf_pop cf_assign + +(** Auxiliary function - see [eval_non_local_function_call] *) +let eval_replace_concrete (_config : C.config) + (_region_params : T.erased_region list) (_type_params : T.ety list) : cm_fun + = + fun _cf _ctx -> raise Unimplemented + +(** Auxiliary function - see [eval_non_local_function_call] *) +let eval_box_new_concrete (config : C.config) + (region_params : T.erased_region list) (type_params : T.ety list) : cm_fun = + fun cf ctx -> + (* Check and retrieve the arguments *) + match (region_params, type_params, ctx.env) with + | ( [], + [ boxed_ty ], + Var (Some input_var, input_value) :: Var (_ret_var, _) :: C.Frame :: _ ) + -> + (* Required type checking *) + assert (input_value.V.ty = boxed_ty); + + (* Move the input value *) + let cf_move = + eval_operand config (E.Move (mk_place_from_var_id input_var.C.index)) + in + + (* Create the new box *) + let cf_create cf (moved_input_value : V.typed_value) : m_fun = + (* Create the box value *) + let box_ty = T.Adt (T.Assumed T.Box, [], [ boxed_ty ]) in + let box_v = + V.Adt { variant_id = None; field_values = [ moved_input_value ] } + in + let box_v = mk_typed_value box_ty box_v in + + (* Move this value to the return variable *) + let dest = mk_place_from_var_id V.VarId.zero in + let cf_assign = assign_to_place config box_v dest in + + (* Continue *) + cf_assign cf + in + + (* Compose and apply *) + comp cf_move cf_create cf ctx + | _ -> raise (Failure "Inconsistent state") + +(** Auxiliary function which factorizes code to evaluate [std::Deref::deref] + and [std::DerefMut::deref_mut] - see [eval_non_local_function_call] *) +let eval_box_deref_mut_or_shared_concrete (config : C.config) + (region_params : T.erased_region list) (type_params : T.ety list) + (is_mut : bool) : cm_fun = + fun cf ctx -> + (* Check the arguments *) + match (region_params, type_params, ctx.env) with + | ( [], + [ boxed_ty ], + Var (Some input_var, input_value) :: Var (_ret_var, _) :: C.Frame :: _ ) + -> + (* Required type checking. We must have: + - input_value.ty == & (mut) Box<ty> + - boxed_ty == ty + for some ty + *) + (let _, input_ty, ref_kind = ty_get_ref input_value.V.ty in + assert (match ref_kind with T.Shared -> not is_mut | T.Mut -> is_mut); + let input_ty = ty_get_box input_ty in + assert (input_ty = boxed_ty)); + + (* Borrow the boxed value *) + let p = + { E.var_id = input_var.C.index; projection = [ E.Deref; E.DerefBox ] } + in + let borrow_kind = if is_mut then E.Mut else E.Shared in + let rv = E.Ref (p, borrow_kind) in + let cf_borrow = eval_rvalue config rv in + + (* Move the borrow to its destination *) + let cf_move cf res : m_fun = + match res with + | Error EPanic -> + (* We can't get there by borrowing a value *) + raise (Failure "Unreachable") + | Ok borrowed_value -> + (* Move and continue *) + let destp = mk_place_from_var_id V.VarId.zero in + assign_to_place config borrowed_value destp cf + in + + (* Compose and apply *) + comp cf_borrow cf_move cf ctx + | _ -> raise (Failure "Inconsistent state") + +(** Auxiliary function - see [eval_non_local_function_call] *) +let eval_box_deref_concrete (config : C.config) + (region_params : T.erased_region list) (type_params : T.ety list) : cm_fun = + let is_mut = false in + eval_box_deref_mut_or_shared_concrete config region_params type_params is_mut + +(** Auxiliary function - see [eval_non_local_function_call] *) +let eval_box_deref_mut_concrete (config : C.config) + (region_params : T.erased_region list) (type_params : T.ety list) : cm_fun = + let is_mut = true in + eval_box_deref_mut_or_shared_concrete config region_params type_params is_mut + +(** Auxiliary function - see [eval_non_local_function_call]. + + [Box::free] is not handled the same way as the other assumed functions: + - in the regular case, whenever we need to evaluate an assumed function, + we evaluate the operands, push a frame, call a dedicated function + to correctly update the variables in the frame (and mimic the execution + of a body) and finally pop the frame + - in the case of [Box::free]: the value given to this function is often + of the form [Box(⊥)] because we can move the value out of the + box before freeing the box. It makes it invalid to see box_free as a + "regular" function: it is not valid to call a function with arguments + which contain [⊥]. For this reason, we execute [Box::free] as drop_value, + but this is a bit annoying with regards to the semantics... + + Followingly this function doesn't behave like the others: it does not expect + a stack frame to have been pushed, but rather simply behaves like {!drop_value}. + It thus updates the box value (by calling {!drop_value}) and updates + the destination (by setting it to [()]). +*) +let eval_box_free (config : C.config) (region_params : T.erased_region list) + (type_params : T.ety list) (args : E.operand list) (dest : E.place) : cm_fun + = + fun cf ctx -> + match (region_params, type_params, args) with + | [], [ boxed_ty ], [ E.Move input_box_place ] -> + (* Required type checking *) + let input_box = read_place_unwrap config Write input_box_place ctx in + (let input_ty = ty_get_box input_box.V.ty in + assert (input_ty = boxed_ty)); + + (* Drop the value *) + let cc = drop_value config input_box_place in + + (* Update the destination by setting it to [()] *) + let cc = comp cc (assign_to_place config mk_unit_value dest) in + + (* Continue *) + cc cf ctx + | _ -> raise (Failure "Inconsistent state") + +(** Auxiliary function - see [eval_non_local_function_call] *) +let eval_vec_function_concrete (_config : C.config) (_fid : A.assumed_fun_id) + (_region_params : T.erased_region list) (_type_params : T.ety list) : cm_fun + = + fun _cf _ctx -> raise Unimplemented + +(** Evaluate a non-local function call in concrete mode *) +let eval_non_local_function_call_concrete (config : C.config) + (fid : A.assumed_fun_id) (region_params : T.erased_region list) + (type_params : T.ety list) (args : E.operand list) (dest : E.place) : cm_fun + = + (* There are two cases (and this is extremely annoying): + - the function is not box_free + - the function is box_free + See {!eval_box_free} + *) + match fid with + | A.BoxFree -> + (* Degenerate case: box_free *) + eval_box_free config region_params type_params args dest + | _ -> + (* "Normal" case: not box_free *) + (* Evaluate the operands *) + (* let ctx, args_vl = eval_operands config ctx args in *) + let cf_eval_ops = eval_operands config args in + + (* Evaluate the call + * + * Style note: at some point we used {!comp_transmit} to + * transmit the result of {!eval_operands} above down to {!push_vars} + * below, without having to introduce an intermediary function call, + * but it made it less clear where the computed values came from, + * so we reversed the modifications. *) + let cf_eval_call cf (args_vl : V.typed_value list) : m_fun = + (* Push the stack frame: we initialize the frame with the return variable, + and one variable per input argument *) + let cc = push_frame in + + (* Create and push the return variable *) + let ret_vid = V.VarId.zero in + let ret_ty = + get_non_local_function_return_type fid region_params type_params + in + let ret_var = mk_var ret_vid (Some "@return") ret_ty in + let cc = comp cc (push_uninitialized_var ret_var) in + + (* Create and push the input variables *) + let input_vars = + V.VarId.mapi_from1 + (fun id (v : V.typed_value) -> (mk_var id None v.V.ty, v)) + args_vl + in + let cc = comp cc (push_vars input_vars) in + + (* "Execute" the function body. As the functions are assumed, here we call + * custom functions to perform the proper manipulations: we don't have + * access to a body. *) + let cf_eval_body : cm_fun = + match fid with + | A.Replace -> eval_replace_concrete config region_params type_params + | BoxNew -> eval_box_new_concrete config region_params type_params + | BoxDeref -> eval_box_deref_concrete config region_params type_params + | BoxDerefMut -> + eval_box_deref_mut_concrete config region_params type_params + | BoxFree -> + (* Should have been treated above *) raise (Failure "Unreachable") + | VecNew | VecPush | VecInsert | VecLen | VecIndex | VecIndexMut -> + eval_vec_function_concrete config fid region_params type_params + in + + let cc = comp cc cf_eval_body in + + (* Pop the frame *) + let cc = comp cc (pop_frame_assign config dest) in + + (* Continue *) + cc cf + in + (* Compose and apply *) + comp cf_eval_ops cf_eval_call + +(** Instantiate a function signature, introducing fresh abstraction ids and + region ids. This is mostly used in preparation of function calls, when + evaluating in symbolic mode of course. + + Note: there are no region parameters, because they should be erased. + + **Rk.:** this function is **stateful** and generates fresh abstraction ids + for the region groups. + *) +let instantiate_fun_sig (type_params : T.ety list) (sg : A.fun_sig) : + A.inst_fun_sig = + (* Generate fresh abstraction ids and create a substitution from region + * group ids to abstraction ids *) + let rg_abs_ids_bindings = + List.map + (fun rg -> + let abs_id = C.fresh_abstraction_id () in + (rg.T.id, abs_id)) + sg.regions_hierarchy + in + let asubst_map : V.AbstractionId.id T.RegionGroupId.Map.t = + List.fold_left + (fun mp (rg_id, abs_id) -> T.RegionGroupId.Map.add rg_id abs_id mp) + T.RegionGroupId.Map.empty rg_abs_ids_bindings + in + let asubst (rg_id : T.RegionGroupId.id) : V.AbstractionId.id = + T.RegionGroupId.Map.find rg_id asubst_map + in + (* Generate fresh regions and their substitutions *) + let _, rsubst, _ = Subst.fresh_regions_with_substs sg.region_params in + (* Generate the type substitution + * Note that we need the substitution to map the type variables to + * {!rty} types (not {!ety}). In order to do that, we convert the + * type parameters to types with regions. This is possible only + * if those types don't contain any regions. + * This is a current limitation of the analysis: there is still some + * work to do to properly handle full type parametrization. + * *) + let rtype_params = List.map ety_no_regions_to_rty type_params in + let tsubst = + Subst.make_type_subst + (List.map (fun v -> v.T.index) sg.type_params) + rtype_params + in + (* Substitute the signature *) + let inst_sig = Subst.substitute_signature asubst rsubst tsubst sg in + (* Return *) + inst_sig + +(** Helper + + Create abstractions (with no avalues, which have to be inserted afterwards) + from a list of abs region groups. + + [region_can_end]: gives the region groups from which we generate functions + which can end or not. + *) +let create_empty_abstractions_from_abs_region_groups (call_id : V.FunCallId.id) + (kind : V.abs_kind) (rgl : A.abs_region_group list) + (region_can_end : T.RegionGroupId.id -> bool) : V.abs list = + (* We use a reference to progressively create a map from abstraction ids + * to set of ancestor regions. Note that {!abs_to_ancestors_regions} [abs_id] + * returns the union of: + * - the regions of the ancestors of abs_id + * - the regions of abs_id + *) + let abs_to_ancestors_regions : T.RegionId.Set.t V.AbstractionId.Map.t ref = + ref V.AbstractionId.Map.empty + in + (* Auxiliary function to create one abstraction *) + let create_abs (back_id : T.RegionGroupId.id) (rg : A.abs_region_group) : + V.abs = + let abs_id = rg.T.id in + let original_parents = rg.parents in + let parents = + List.fold_left + (fun s pid -> V.AbstractionId.Set.add pid s) + V.AbstractionId.Set.empty rg.parents + in + let regions = + List.fold_left + (fun s rid -> T.RegionId.Set.add rid s) + T.RegionId.Set.empty rg.regions + in + let ancestors_regions = + List.fold_left + (fun acc parent_id -> + T.RegionId.Set.union acc + (V.AbstractionId.Map.find parent_id !abs_to_ancestors_regions)) + T.RegionId.Set.empty rg.parents + in + let ancestors_regions_union_current_regions = + T.RegionId.Set.union ancestors_regions regions + in + let can_end = region_can_end back_id in + abs_to_ancestors_regions := + V.AbstractionId.Map.add abs_id ancestors_regions_union_current_regions + !abs_to_ancestors_regions; + (* Create the abstraction *) + { + V.abs_id; + call_id; + back_id; + kind; + can_end; + parents; + original_parents; + regions; + ancestors_regions; + avalues = []; + } + in + (* Apply *) + T.RegionGroupId.mapi create_abs rgl + +(** Helper. + + Create a list of abstractions from a list of regions groups, and insert + them in the context. + + [region_can_end]: gives the region groups from which we generate functions + which can end or not. + + [compute_abs_avalues]: this function must compute, given an initialized, + empty (i.e., with no avalues) abstraction, compute the avalues which + should be inserted in this abstraction before we insert it in the context. + Note that this function may update the context: it is necessary when + computing borrow projections, for instance. +*) +let create_push_abstractions_from_abs_region_groups (call_id : V.FunCallId.id) + (kind : V.abs_kind) (rgl : A.abs_region_group list) + (region_can_end : T.RegionGroupId.id -> bool) + (compute_abs_avalues : + V.abs -> C.eval_ctx -> C.eval_ctx * V.typed_avalue list) + (ctx : C.eval_ctx) : C.eval_ctx = + (* Initialize the abstractions as empty (i.e., with no avalues) abstractions *) + let empty_absl = + create_empty_abstractions_from_abs_region_groups call_id kind rgl + region_can_end + in + + (* Compute and add the avalues to the abstractions, the insert the abstractions + * in the context. *) + let insert_abs (ctx : C.eval_ctx) (abs : V.abs) : C.eval_ctx = + (* Compute the values to insert in the abstraction *) + let ctx, avalues = compute_abs_avalues abs ctx in + (* Add the avalues to the abstraction *) + let abs = { abs with avalues } in + (* Insert the abstraction in the context *) + let ctx = { ctx with env = Abs abs :: ctx.env } in + (* Return *) + ctx + in + List.fold_left insert_abs ctx empty_absl + +(** Evaluate a statement *) +let rec eval_statement (config : C.config) (st : A.statement) : st_cm_fun = + fun cf ctx -> + (* Debugging *) + log#ldebug + (lazy + ("\n**About to evaluate statement**: [\n" + ^ statement_to_string_with_tab ctx st + ^ "\n]\n\n**Context**:\n" ^ eval_ctx_to_string ctx ^ "\n\n")); + + (* Expand the symbolic values if necessary - we need to do that before + * checking the invariants *) + let cc = greedy_expand_symbolic_values config in + (* Sanity check *) + let cc = comp cc (Inv.cf_check_invariants config) in + + (* Evaluate *) + let cf_eval_st cf : m_fun = + fun ctx -> + match st.content with + | A.Assign (p, rvalue) -> + (* Evaluate the rvalue *) + let cf_eval_rvalue = eval_rvalue config rvalue in + (* Assign *) + let cf_assign cf (res : (V.typed_value, eval_error) result) ctx = + log#ldebug + (lazy + ("about to assign to place: " ^ place_to_string ctx p + ^ "\n- Context:\n" ^ eval_ctx_to_string ctx)); + match res with + | Error EPanic -> cf Panic ctx + | Ok rv -> ( + let expr = assign_to_place config rv p (cf Unit) ctx in + (* Update the synthesized AST - here we store meta-information. + * We do it only in specific cases (it is not always useful, and + * also it can lead to issues - for instance, if we borrow an + * inactivated borrow, we later can't translate it to pure values...) *) + match rvalue with + | E.Use _ + | E.Ref (_, (E.Shared | E.Mut | E.TwoPhaseMut)) + | E.UnaryOp _ | E.BinaryOp _ | E.Discriminant _ | E.Aggregate _ -> + let rp = rvalue_get_place rvalue in + let rp = + match rp with + | Some rp -> Some (S.mk_mplace rp ctx) + | None -> None + in + S.synthesize_assignment (S.mk_mplace p ctx) rv rp expr) + in + + (* Compose and apply *) + comp cf_eval_rvalue cf_assign cf ctx + | A.AssignGlobal { dst; global } -> eval_global config dst global cf ctx + | A.FakeRead p -> + let expand_prim_copy = false in + let cf_prepare cf = + access_rplace_reorganize_and_read config expand_prim_copy Read p cf + in + let cf_continue cf v : m_fun = + fun ctx -> + assert (not (bottom_in_value ctx.ended_regions v)); + cf ctx + in + comp cf_prepare cf_continue (cf Unit) ctx + | A.SetDiscriminant (p, variant_id) -> + set_discriminant config p variant_id cf ctx + | A.Drop p -> drop_value config p (cf Unit) ctx + | A.Assert assertion -> eval_assertion config assertion cf ctx + | A.Call call -> eval_function_call config call cf ctx + | A.Panic -> cf Panic ctx + | A.Return -> cf Return ctx + | A.Break i -> cf (Break i) ctx + | A.Continue i -> cf (Continue i) ctx + | A.Nop -> cf Unit ctx + | A.Sequence (st1, st2) -> + (* Evaluate the first statement *) + let cf_st1 = eval_statement config st1 in + (* Evaluate the sequence *) + let cf_st2 cf res = + match res with + (* Evaluation successful: evaluate the second statement *) + | Unit -> eval_statement config st2 cf + (* Control-flow break: transmit. We enumerate the cases on purpose *) + | Panic | Break _ | Continue _ | Return -> cf res + in + (* Compose and apply *) + comp cf_st1 cf_st2 cf ctx + | A.Loop loop_body -> + (* For now, we don't support loops in symbolic mode *) + assert (config.C.mode = C.ConcreteMode); + (* Continuation for after we evaluate the loop body: depending the result + of doing one loop iteration: + - redoes a loop iteration + - exits the loop + - other... + + We need a specific function because of the {!Continue} case: in case we + continue, we might have to reevaluate the current loop body with the + new context (and repeat this an indefinite number of times). + *) + let rec reeval_loop_body res : m_fun = + match res with + | Return | Panic -> cf res + | Break i -> + (* Break out of the loop by calling the continuation *) + let res = if i = 0 then Unit else Break (i - 1) in + cf res + | Continue 0 -> + (* Re-evaluate the loop body *) + eval_statement config loop_body reeval_loop_body + | Continue i -> + (* Continue to an outer loop *) + cf (Continue (i - 1)) + | Unit -> + (* We can't get there. + * Note that if we decide not to fail here but rather do + * the same thing as for [Continue 0], we could make the + * code slightly simpler: calling {!reeval_loop_body} with + * {!Unit} would account for the first iteration of the loop. + * We prefer to write it this way for consistency and sanity, + * though. *) + raise (Failure "Unreachable") + in + (* Apply *) + eval_statement config loop_body reeval_loop_body ctx + | A.Switch (op, tgts) -> eval_switch config op tgts cf ctx + in + (* Compose and apply *) + comp cc cf_eval_st cf ctx + +and eval_global (config : C.config) (dest : V.VarId.id) + (gid : LA.GlobalDeclId.id) : st_cm_fun = + fun cf ctx -> + let global = C.ctx_lookup_global_decl ctx gid in + let place = { E.var_id = dest; projection = [] } in + match config.mode with + | ConcreteMode -> + (* Treat the evaluation of the global as a call to the global body (without arguments) *) + (eval_local_function_call_concrete config global.body_id [] [] [] place) + cf ctx + | SymbolicMode -> + (* Generate a fresh symbolic value. In the translation, this fresh symbolic value will be + * defined as equal to the value of the global (see {!S.synthesize_global_eval}). *) + let sval = + mk_fresh_symbolic_value V.Global (ety_no_regions_to_rty global.ty) + in + let cc = + assign_to_place config (mk_typed_value_from_symbolic_value sval) place + in + let e = cc (cf Unit) ctx in + S.synthesize_global_eval gid sval e + +(** Evaluate a switch *) +and eval_switch (config : C.config) (op : E.operand) (tgts : A.switch_targets) : + st_cm_fun = + fun cf ctx -> + (* We evaluate the operand in two steps: + * first we prepare it, then we check if its value is concrete or + * symbolic. If it is concrete, we can then evaluate the operand + * directly, otherwise we must first expand the value. + * Note that we can't fully evaluate the operand *then* expand the + * value if it is symbolic, because the value may have been move + * (and would thus floating in thin air...)! + * *) + (* Prepare the operand *) + let cf_eval_op cf : m_fun = eval_operand config op cf in + (* Match on the targets *) + let cf_match (cf : st_m_fun) (op_v : V.typed_value) : m_fun = + fun ctx -> + match tgts with + | A.If (st1, st2) -> ( + match op_v.value with + | V.Concrete (V.Bool b) -> + (* Evaluate the if and the branch body *) + let cf_branch cf : m_fun = + (* Branch *) + if b then eval_statement config st1 cf + else eval_statement config st2 cf + in + (* Compose the continuations *) + cf_branch cf ctx + | V.Symbolic sv -> + (* Expand the symbolic boolean, and continue by evaluating + * the branches *) + let cf_true : m_fun = eval_statement config st1 cf in + let cf_false : m_fun = eval_statement config st2 cf in + expand_symbolic_bool config sv + (S.mk_opt_place_from_op op ctx) + cf_true cf_false ctx + | _ -> raise (Failure "Inconsistent state")) + | A.SwitchInt (int_ty, stgts, otherwise) -> ( + match op_v.value with + | V.Concrete (V.Scalar sv) -> + (* Evaluate the branch *) + let cf_eval_branch cf = + (* Sanity check *) + assert (sv.V.int_ty = int_ty); + (* Find the branch *) + match List.find_opt (fun (svl, _) -> List.mem sv svl) stgts with + | None -> eval_statement config otherwise cf + | Some (_, tgt) -> eval_statement config tgt cf + in + (* Compose *) + cf_eval_branch cf ctx + | V.Symbolic sv -> + (* Expand the symbolic value and continue by evaluating the + * proper branches *) + let stgts = + List.map + (fun (cv, tgt_st) -> (cv, eval_statement config tgt_st cf)) + stgts + in + (* Several branches may be grouped together: every branch is described + * by a pair (list of values, branch expression). + * In order to do a symbolic evaluation, we make this "flat" by + * de-grouping the branches. *) + let stgts = + List.concat + (List.map + (fun (vl, st) -> List.map (fun v -> (v, st)) vl) + stgts) + in + (* Translate the otherwise branch *) + let otherwise = eval_statement config otherwise cf in + (* Expand and continue *) + expand_symbolic_int config sv + (S.mk_opt_place_from_op op ctx) + int_ty stgts otherwise ctx + | _ -> raise (Failure "Inconsistent state")) + in + (* Compose the continuations *) + comp cf_eval_op cf_match cf ctx + +(** Evaluate a function call (auxiliary helper for [eval_statement]) *) +and eval_function_call (config : C.config) (call : A.call) : st_cm_fun = + (* There are two cases: + - this is a local function, in which case we execute its body + - this is a non-local function, in which case there is a special treatment + *) + match call.func with + | A.Regular fid -> + eval_local_function_call config fid call.region_args call.type_args + call.args call.dest + | A.Assumed fid -> + eval_non_local_function_call config fid call.region_args call.type_args + call.args call.dest + +(** Evaluate a local (i.e., non-assumed) function call in concrete mode *) +and eval_local_function_call_concrete (config : C.config) (fid : A.FunDeclId.id) + (region_args : T.erased_region list) (type_args : T.ety list) + (args : E.operand list) (dest : E.place) : st_cm_fun = + fun cf ctx -> + assert (region_args = []); + + (* Retrieve the (correctly instantiated) body *) + let def = C.ctx_lookup_fun_decl ctx fid in + (* We can evaluate the function call only if it is not opaque *) + let body = + match def.body with + | None -> + raise + (Failure + ("Can't evaluate a call to an opaque function: " + ^ Print.name_to_string def.name)) + | Some body -> body + in + let tsubst = + Subst.make_type_subst + (List.map (fun v -> v.T.index) def.A.signature.type_params) + type_args + in + let locals, body_st = Subst.fun_body_substitute_in_body tsubst body in + + (* Evaluate the input operands *) + assert (List.length args = body.A.arg_count); + let cc = eval_operands config args in + + (* Push a frame delimiter - we use {!comp_transmit} to transmit the result + * of the operands evaluation from above to the functions afterwards, while + * ignoring it in this function *) + let cc = comp_transmit cc push_frame in + + (* Compute the initial values for the local variables *) + (* 1. Push the return value *) + let ret_var, locals = + match locals with + | ret_ty :: locals -> (ret_ty, locals) + | _ -> raise (Failure "Unreachable") + in + let input_locals, locals = + Collections.List.split_at locals body.A.arg_count + in + + let cc = comp_transmit cc (push_var ret_var (mk_bottom ret_var.var_ty)) in + + (* 2. Push the input values *) + let cf_push_inputs cf args = + let inputs = List.combine input_locals args in + (* Note that this function checks that the variables and their values + * have the same type (this is important) *) + push_vars inputs cf + in + let cc = comp cc cf_push_inputs in + + (* 3. Push the remaining local variables (initialized as {!Bottom}) *) + let cc = comp cc (push_uninitialized_vars locals) in + + (* Execute the function body *) + let cc = comp cc (eval_function_body config body_st) in + + (* Pop the stack frame and move the return value to its destination *) + let cf_finish cf res = + match res with + | Panic -> cf Panic + | Break _ | Continue _ | Unit -> raise (Failure "Unreachable") + | Return -> + (* Pop the stack frame, retrieve the return value, move it to + * its destination and continue *) + pop_frame_assign config dest (cf Unit) + in + let cc = comp cc cf_finish in + + (* Continue *) + cc cf ctx + +(** Evaluate a local (i.e., non-assumed) function call in symbolic mode *) +and eval_local_function_call_symbolic (config : C.config) (fid : A.FunDeclId.id) + (region_args : T.erased_region list) (type_args : T.ety list) + (args : E.operand list) (dest : E.place) : st_cm_fun = + fun cf ctx -> + (* Retrieve the (correctly instantiated) signature *) + let def = C.ctx_lookup_fun_decl ctx fid in + let sg = def.A.signature in + (* Instantiate the signature and introduce fresh abstraction and region ids + * while doing so *) + let inst_sg = instantiate_fun_sig type_args sg in + (* Sanity check *) + assert (List.length args = List.length def.A.signature.inputs); + (* Evaluate the function call *) + eval_function_call_symbolic_from_inst_sig config (A.Regular fid) inst_sg + region_args type_args args dest cf ctx + +(** Evaluate a function call in symbolic mode by using the function signature. + + This allows us to factorize the evaluation of local and non-local function + calls in symbolic mode: only their signatures matter. + *) +and eval_function_call_symbolic_from_inst_sig (config : C.config) + (fid : A.fun_id) (inst_sg : A.inst_fun_sig) + (region_args : T.erased_region list) (type_args : T.ety list) + (args : E.operand list) (dest : E.place) : st_cm_fun = + fun cf ctx -> + assert (region_args = []); + (* Generate a fresh symbolic value for the return value *) + let ret_sv_ty = inst_sg.A.output in + let ret_spc = mk_fresh_symbolic_value V.FunCallRet ret_sv_ty in + let ret_value = mk_typed_value_from_symbolic_value ret_spc in + let ret_av regions = + mk_aproj_loans_value_from_symbolic_value regions ret_spc + in + let args_places = List.map (fun p -> S.mk_opt_place_from_op p ctx) args in + let dest_place = Some (S.mk_mplace dest ctx) in + + (* Evaluate the input operands *) + let cc = eval_operands config args in + + (* Generate the abstractions and insert them in the context *) + let abs_ids = List.map (fun rg -> rg.T.id) inst_sg.regions_hierarchy in + let cf_call cf (args : V.typed_value list) : m_fun = + fun ctx -> + let args_with_rtypes = List.combine args inst_sg.A.inputs in + + (* Check the type of the input arguments *) + assert ( + List.for_all + (fun ((arg, rty) : V.typed_value * T.rty) -> + arg.V.ty = Subst.erase_regions rty) + args_with_rtypes); + (* Check that the input arguments don't contain symbolic values that can't + * be fed to functions (i.e., symbolic values output from function return + * values and which contain borrows of borrows can't be used as function + * inputs *) + assert ( + List.for_all + (fun arg -> + not (value_has_ret_symbolic_value_with_borrow_under_mut ctx arg)) + args); + + (* Initialize the abstractions and push them in the context. + * First, we define the function which, given an initialized, empty + * abstraction, computes the avalues which should be inserted inside. + *) + let compute_abs_avalues (abs : V.abs) (ctx : C.eval_ctx) : + C.eval_ctx * V.typed_avalue list = + (* Project over the input values *) + let ctx, args_projs = + List.fold_left_map + (fun ctx (arg, arg_rty) -> + apply_proj_borrows_on_input_value config ctx abs.regions + abs.ancestors_regions arg arg_rty) + ctx args_with_rtypes + in + (* Group the input and output values *) + (ctx, List.append args_projs [ ret_av abs.regions ]) + in + (* Actually initialize and insert the abstractions *) + let call_id = C.fresh_fun_call_id () in + let region_can_end _ = true in + let ctx = + create_push_abstractions_from_abs_region_groups call_id V.FunCall + inst_sg.A.regions_hierarchy region_can_end compute_abs_avalues ctx + in + + (* Apply the continuation *) + let expr = cf ctx in + + (* Synthesize the symbolic AST *) + S.synthesize_regular_function_call fid call_id abs_ids type_args args + args_places ret_spc dest_place expr + in + let cc = comp cc cf_call in + + (* Move the return value to its destination *) + let cc = comp cc (assign_to_place config ret_value dest) in + + (* End the abstractions which don't contain loans and don't have parent + * abstractions. + * We do the general, nested borrows case here: we end abstractions, then + * retry (because then we might end their children abstractions) + *) + let abs_ids = ref abs_ids in + let rec end_abs_with_no_loans cf : m_fun = + fun ctx -> + (* Find the abstractions which don't contain loans *) + let no_loans_abs, with_loans_abs = + List.partition + (fun abs_id -> + (* Lookup the abstraction *) + let abs = C.ctx_lookup_abs ctx abs_id in + (* Check if it has parents *) + V.AbstractionId.Set.is_empty abs.parents + (* Check if it contains non-ignored loans *) + && Option.is_none + (InterpreterBorrowsCore + .get_first_non_ignored_aloan_in_abstraction abs)) + !abs_ids + in + (* Check if there are abstractions to end *) + if no_loans_abs <> [] then ( + (* Update the reference to the list of asbtraction ids, for the recursive calls *) + abs_ids := with_loans_abs; + (* End the abstractions which can be ended *) + let no_loans_abs = V.AbstractionId.Set.of_list no_loans_abs in + let cc = InterpreterBorrows.end_abstractions config [] no_loans_abs in + (* Recursive call *) + let cc = comp cc end_abs_with_no_loans in + (* Continue *) + cc cf ctx) + else (* No abstractions to end: continue *) + cf ctx + in + (* Try to end the abstractions with no loans if: + * - the option is enabled + * - the function returns unit + * (see the documentation of {!config} for more information) + *) + let cc = + if config.return_unit_end_abs_with_no_loans && ty_is_unit inst_sg.output + then comp cc end_abs_with_no_loans + else cc + in + + (* Continue - note that we do as if the function call has been successful, + * by giving {!Unit} to the continuation, because we place us in the case + * where we haven't panicked. Of course, the translation needs to take the + * panic case into account... *) + cc (cf Unit) ctx + +(** Evaluate a non-local function call in symbolic mode *) +and eval_non_local_function_call_symbolic (config : C.config) + (fid : A.assumed_fun_id) (region_args : T.erased_region list) + (type_args : T.ety list) (args : E.operand list) (dest : E.place) : + st_cm_fun = + fun cf ctx -> + (* Sanity check: make sure the type parameters don't contain regions - + * this is a current limitation of our synthesis *) + assert ( + List.for_all + (fun ty -> not (ty_has_borrows ctx.type_context.type_infos ty)) + type_args); + + (* There are two cases (and this is extremely annoying): + - the function is not box_free + - the function is box_free + See {!eval_box_free} + *) + match fid with + | A.BoxFree -> + (* Degenerate case: box_free - note that this is not really a function + * call: no need to call a "synthesize_..." function *) + eval_box_free config region_args type_args args dest (cf Unit) ctx + | _ -> + (* "Normal" case: not box_free *) + (* In symbolic mode, the behaviour of a function call is completely defined + * by the signature of the function: we thus simply generate correctly + * instantiated signatures, and delegate the work to an auxiliary function *) + let inst_sig = + match fid with + | A.BoxFree -> + (* should have been treated above *) + raise (Failure "Unreachable") + | _ -> instantiate_fun_sig type_args (Assumed.get_assumed_sig fid) + in + + (* Evaluate the function call *) + eval_function_call_symbolic_from_inst_sig config (A.Assumed fid) inst_sig + region_args type_args args dest cf ctx + +(** Evaluate a non-local (i.e, assumed) function call such as [Box::deref] + (auxiliary helper for [eval_statement]) *) +and eval_non_local_function_call (config : C.config) (fid : A.assumed_fun_id) + (region_args : T.erased_region list) (type_args : T.ety list) + (args : E.operand list) (dest : E.place) : st_cm_fun = + fun cf ctx -> + (* Debug *) + log#ldebug + (lazy + (let type_args = + "[" ^ String.concat ", " (List.map (ety_to_string ctx) type_args) ^ "]" + in + let args = + "[" ^ String.concat ", " (List.map (operand_to_string ctx) args) ^ "]" + in + let dest = place_to_string ctx dest in + "eval_non_local_function_call:\n- fid:" ^ A.show_assumed_fun_id fid + ^ "\n- type_args: " ^ type_args ^ "\n- args: " ^ args ^ "\n- dest: " + ^ dest)); + + match config.mode with + | C.ConcreteMode -> + eval_non_local_function_call_concrete config fid region_args type_args + args dest (cf Unit) ctx + | C.SymbolicMode -> + eval_non_local_function_call_symbolic config fid region_args type_args + args dest cf ctx + +(** Evaluate a local (i.e, not assumed) function call (auxiliary helper for + [eval_statement]) *) +and eval_local_function_call (config : C.config) (fid : A.FunDeclId.id) + (region_args : T.erased_region list) (type_args : T.ety list) + (args : E.operand list) (dest : E.place) : st_cm_fun = + match config.mode with + | ConcreteMode -> + eval_local_function_call_concrete config fid region_args type_args args + dest + | SymbolicMode -> + eval_local_function_call_symbolic config fid region_args type_args args + dest + +(** Evaluate a statement seen as a function body (auxiliary helper for + [eval_statement]) *) +and eval_function_body (config : C.config) (body : A.statement) : st_cm_fun = + fun cf ctx -> + let cc = eval_statement config body in + let cf_finish cf res = + (* Note that we *don't* check the result ({!Panic}, {!Return}, etc.): we + * delegate the check to the caller. *) + (* Expand the symbolic values if necessary - we need to do that before + * checking the invariants *) + let cc = greedy_expand_symbolic_values config in + (* Sanity check *) + let cc = comp_check_ctx cc (Inv.check_invariants config) in + (* Continue *) + cc (cf res) + in + (* Compose and continue *) + comp cc cf_finish cf ctx |