return -v;
}
- The original decl is left alone and instead calls are generated to pre/post
- functions within the body:
+ We implement the checking as follows:
- void fun.pre(int v)
- {
- [[ assert: v > 0 ]];
- }
- int fun.post(int v, int __r)
+ For functions with no post-conditions we wrap the original function body as
+ follows:
+
+ {
+ handle_pre_condition_checking ();
+ original_function_body ();
+ }
+
+ This implements the intent that the preconditions are processed after the
+ function parameters are initialised but before any other actions.
+
+ For functions with post-conditions:
+
+ if (preconditions_exist)
+ handle_pre_condition_checking ();
+ try
{
- [[ assert: __r < 0 ]];
- return __r;
+ original_function_body ();
}
- int fun(int v)
+ finally
{
- fun.pre(v);
- return fun.post(v, -v);
+ handle_post_condition_checking ();
}
-
- If fun returns in memory, the return value is not passed through the post
- function; instead, the return object is initialized directly and then passed
- to the post function by invisible reference.
-
- This sides steps a number of issues with having to rewrite the bodies or
- rewrite the parsed conditions as the parameters to the original function
- changes (as happens during redeclaration). The ultimate goal is to get
- something that optimizes well along the lines of
-
- int fun(int v)
+ else [only if the function is not marked noexcept(true) ]
{
- [[ assert: v > 0 ]];
- auto &&__r = -v;
- goto out;
- out:
- [[ assert: __r < 0 ]];
- return __r;
+ ;
}
- With the idea being that multiple return statements could collapse the
- function epilogue after inlining the pre/post functions. clang is able
- to collapse common function epilogues, while gcc needs -O3 -Os combined.
-
- Directly laying the pre contracts down in the function body doesn't have
- many issues. The post contracts may need to be repeated multiple times, once
- for each return, or a goto epilogue would need to be generated.
- For this initial implementation, generating function calls and letting
- later optimizations decide whether to inline and duplicate the actual
- checks or whether to collapse the shared epilogue was chosen.
-
- For cdtors a post contract is implemented using a CLEANUP_STMT.
+ In this, post-conditions [that might apply to the return value etc.] are
+ evaluated on every non-exceptional edge out of the function.
- FIXME the compiler already shores cleanup code on multiple exit paths, so
- this outlining seems unnecessary if we represent the postcondition as a
- cleanup for all functions.
+ FIXME outlining contract checks into separate functions was motivated
+ partly by wanting to call the postcondition function at each return
+ statement, which we no longer do; at this point outlining doesn't seem to
+ have any advantage over emitting the contracts directly in the function
+ body.
More helpful for optimization might be to make the contracts a wrapper
- function (for non-variadic functions), that could be inlined into a
- caller while preserving the call to the actual function? Either that or
- mirror a never-continue post contract with an assume in the caller. */
+ function that could be inlined into the caller, the callee, or both. */
#include "config.h"
#include "system.h"
tree attribute = build_tree_list (build_tree_list (NULL_TREE, identifier),
build_tree_list (NULL_TREE, contract));
-
/* Mark the attribute as dependent if the condition is dependent.
TODO: I'm not sure this is strictly necessary. It's going to be marked as
*last = build_tree_list (NULL_TREE, value_type);
TREE_CHAIN (*last) = void_list_node;
- if (aggregate_value_p (value_type, fndecl))
- /* If FNDECL returns in memory, don't return the value from the
- postcondition. */
- value_type = void_type_node;
+ /* The handler is a void return. */
+ value_type = void_type_node;
}
TREE_TYPE (fn) = build_function_type (value_type, arg_types);
/* Emit statements for postcondition attributes. */
static void
-emit_postconditions_cleanup (tree contracts)
+emit_postconditions (tree attr)
{
- tree stmts = push_stmt_list ();
- emit_contract_conditions (contracts, POSTCONDITION_STMT);
- stmts = pop_stmt_list (stmts);
- push_cleanup (NULL_TREE, stmts, /*eh_only*/false);
+ return emit_contract_conditions (attr, POSTCONDITION_STMT);
}
/* We're compiling the pre/postcondition function CONDFN; remap any FN
if (!handle_contracts_p (decl1))
return;
+ /* For cdtors, we evaluate the contracts check inline. */
if (!outline_contracts_p (decl1))
- {
- emit_preconditions (DECL_CONTRACTS (current_function_decl));
- emit_postconditions_cleanup (DECL_CONTRACTS (current_function_decl));
- return;
- }
+ return;
/* Contracts may have just been added without a chance to parse them, though
we still need the PRE_FN available to generate a call to it. */
if (!DECL_PRE_FN (decl1))
build_contract_function_decls (decl1);
+}
+
+/* If we have a precondition function and it's valid, call it. */
+
+static void
+add_pre_condition_fn_call (tree fndecl)
+{
/* If we're starting a guarded function with valid contracts, we need to
insert a call to the pre function. */
- if (DECL_PRE_FN (decl1)
- && DECL_PRE_FN (decl1) != error_mark_node)
+ gcc_checking_assert (DECL_PRE_FN (fndecl)
+ && DECL_PRE_FN (fndecl) != error_mark_node);
+
+ releasing_vec args = build_arg_list (fndecl);
+ tree call = build_call_a (DECL_PRE_FN (fndecl), args->length (),
+ args->address ());
+ CALL_FROM_THUNK_P (call) = true;
+ finish_expr_stmt (call);
+}
+
+/* Build and add a call to the post-condition checking function, when that
+ is in use. */
+
+static void
+add_post_condition_fn_call (tree fndecl)
+{
+ gcc_checking_assert (DECL_POST_FN (fndecl)
+ && DECL_POST_FN (fndecl) != error_mark_node);
+
+ releasing_vec args = build_arg_list (fndecl);
+ if (get_postcondition_result_parameter (fndecl))
+ vec_safe_push (args, DECL_RESULT (fndecl));
+ tree call = build_call_a (DECL_POST_FN (fndecl), args->length (),
+ args->address ());
+ CALL_FROM_THUNK_P (call) = true;
+ finish_expr_stmt (call);
+}
+
+/* Add a call or a direct evaluation of the pre checks. */
+
+static void
+apply_preconditions (tree fndecl)
+{
+ if (outline_contracts_p (fndecl))
+ add_pre_condition_fn_call (fndecl);
+ else
+ emit_preconditions (DECL_CONTRACTS (fndecl));
+}
+
+/* Add a call or a direct evaluation of the post checks. */
+
+static void
+apply_postconditions (tree fndecl)
+{
+ if (outline_contracts_p (fndecl))
+ add_post_condition_fn_call (fndecl);
+ else
+ emit_postconditions (DECL_CONTRACTS (fndecl));
+}
+
+/* Add contract handling to the function in FNDECL.
+
+ When we have only pre-conditions, this simply prepends a call (or a direct
+ evaluation, for cdtors) to the existing function body.
+
+ When we have post conditions we build a try-finally block.
+ If the function might throw then the handler in the try-finally is an
+ EH_ELSE expression, where the post condition check is applied to the
+ non-exceptional path, and an empty statement is added to the EH path. If
+ the function has a non-throwing eh spec, then the handler is simply the
+ post-condition checker. */
+
+void
+maybe_apply_function_contracts (tree fndecl)
+{
+ if (!handle_contracts_p (fndecl))
+ /* We did nothing and the original function body statement list will be
+ popped by our caller. */
+ return;
+
+ bool do_pre = has_active_preconditions (fndecl);
+ bool do_post = has_active_postconditions (fndecl);
+ /* We should not have reached here with nothing to do... */
+ gcc_checking_assert (do_pre || do_post);
+
+ /* This copies the approach used for function try blocks. */
+ tree fnbody = pop_stmt_list (DECL_SAVED_TREE (fndecl));
+ DECL_SAVED_TREE (fndecl) = push_stmt_list ();
+ tree compound_stmt = begin_compound_stmt (0);
+ current_binding_level->artificial = 1;
+
+ /* Do not add locations for the synthesised code. */
+ location_t loc = UNKNOWN_LOCATION;
+
+ /* For other cases, we call a function to process the check. */
+
+ /* If we have a pre, but not a post, then just emit that and we are done. */
+ if (!do_post)
+ {
+ apply_preconditions (fndecl);
+ add_stmt (fnbody);
+ finish_compound_stmt (compound_stmt);
+ return;
+ }
+
+ if (do_pre)
+ /* Add a precondition call, if we have one. */
+ apply_preconditions (fndecl);
+ tree try_fin = build_stmt (loc, TRY_FINALLY_EXPR, fnbody, NULL_TREE);
+ add_stmt (try_fin);
+ TREE_OPERAND (try_fin, 1) = push_stmt_list ();
+ /* If we have exceptions, and a function that might throw, then add
+ an EH_ELSE clause that allows the exception to propagate upwards
+ without encountering the post-condition checks. */
+ if (flag_exceptions && !type_noexcept_p (TREE_TYPE (fndecl)))
{
- releasing_vec args = build_arg_list (decl1);
- tree call = build_call_a (DECL_PRE_FN (decl1),
- args->length (),
- args->address ());
- CALL_FROM_THUNK_P (call) = true;
- finish_expr_stmt (call);
+ tree eh_else = build_stmt (loc, EH_ELSE_EXPR, NULL_TREE, NULL_TREE);
+ add_stmt (eh_else);
+ TREE_OPERAND (eh_else, 0) = push_stmt_list ();
+ apply_postconditions (fndecl);
+ TREE_OPERAND (eh_else, 0) = pop_stmt_list (TREE_OPERAND (eh_else, 0));
+ TREE_OPERAND (eh_else, 1) = build_empty_stmt (loc);
}
+ else
+ apply_postconditions (fndecl);
+ TREE_OPERAND (try_fin, 1) = pop_stmt_list (TREE_OPERAND (try_fin, 1));
+ finish_compound_stmt (compound_stmt);
+ /* The DECL_SAVED_TREE stmt list will be popped by our caller. */
}
/* Finish up the pre & post function definitions for a guarded FNDECL,
}
}
-/* Rewrite the expression of a returned expression so that it invokes the
- postcondition function as needed. */
-
-tree
-apply_postcondition_to_return (tree expr)
-{
- tree fn = current_function_decl;
- tree post = DECL_POST_FN (fn);
- if (!post)
- return NULL_TREE;
-
- /* If FN returns in memory, POST has a void return type and we call it when
- EXPR is DECL_RESULT (fn). If FN returns a scalar, POST has the same
- return type and we call it when EXPR is the value being returned. */
- if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post)))
- != (expr == DECL_RESULT (fn)))
- return NULL_TREE;
-
- releasing_vec args = build_arg_list (fn);
- if (get_postcondition_result_parameter (fn))
- vec_safe_push (args, expr);
- tree call = build_call_a (post,
- args->length (),
- args->address ());
- CALL_FROM_THUNK_P (call) = true;
-
- return call;
-}
/* A subroutine of duplicate_decls. Diagnose issues in the redeclaration of
guarded functions. */