On 6/26/21 10:23 AM, Andrew Sutton wrote:
Hi Jason,

I ended up taking over this work from Jeff (CC'd on his existing email
address). I scraped all the contracts changes into one big patch
against master. See attached. The ChangeLog.contracts files list the
sum of changes for the patch, not the full history of the work.

I think this version addresses most of your concerns.

Thanks, looking good.  I'll fuss with it a bit and commit it soon.

There are a few big changes.

The first is that we treat contracts like any other attribute, which
gets interesting at times. For example, in duplicate_decl, we have to
do a little dance to make sure the target merge_attributes doesn't
copy attributes between the new and old decls in seemingly arbitrary
order. Friends are also a bit funny because the attributes aren't
attached by cplus_decl_attributes at the point declarations are
merged, so we have to defer comparisons.

Contracts are always parsed where they appear, except on member
functions. For postconditions with result variables (e.g., [[post r:
...]]), we temporarily declare r as if 'auto r' and then update it
later when we've computed the function's return type. (I feel like
this was kind of overlooked in N4820... it's generally impossible to
assign a type to 'r' given the position of contract attributes in the
declarator: 'auto f(int n) [[post r: q]] -> int'. It's worse in GCC
since the return type is computed in grokdeclarator, well after
contract attributes have been parsed).

On a related note, the handling of postconditions involving deduced
return type was completely rewritten. Everything happens in
apply_deduced_return_type, which seems right. I think
check_return_expr is where the postcondition is actually invoked.

We no longer instantiate contract attributes until absolutely
necessary in regenerate_decl_from_tempalte. That seems to work well...
at least does after I discovered we were quietly rewriting contract
lists every time we removed contracts from an old declaration or from
a template specialization. This also gets rid of the need to have
unshare_templates, which had a FIXME note attached.

Lastly, we only ever generate pre/post checks for actual functions,
not function templates. I also simplified a lot of the logic around
associating pre/post check functions, so that it's only set exactly
once when start analyzing function definitions.

I believe Jeff addressed some of the ABI concerns and COMDAT-related questions.

On the issue of copy_fn_decl vs.v copy_fndecl_with_name... I didn't
change that. The latter sends the function to middle end for codegen,
which we really don't want at the point we make the copy.

I think we're probably still breaking NRVO. I didn't get a chance to
look at that.

On Fri, May 28, 2021 at 9:18 AM Jeff Chapman <jchap...@lock3software.com> wrote:

Hello again :)  Wanted to shoot a quick status update. Some github issues have
been created for points of feedback, and we've been working on addressing them.
A few changes have been pushed to the contracts-jac-alt branch, while there's
also an active more in depth rewrite branch. Some specific comments inline
below.

On 5/17/21, Jason Merrill <ja...@redhat.com> wrote:
On 5/14/21 4:54 PM, Jason Merrill wrote:
On 4/30/21 1:44 PM, Jeff Chapman wrote:
Hello! Looping back around to this. re:
https://gcc.gnu.org/pipermail/gcc-patches/2021-March/567334.html

On 3/25/21, Jason Merrill <ja...@redhat.com> wrote:
On 3/1/21 8:12 AM, Jeff Chapman wrote:
On 1/18/21, Jason Merrill <ja...@redhat.com> wrote:
On 1/4/21 9:58 AM, Jeff Chapman wrote:
Ping. re:
https://gcc.gnu.org/pipermail/gcc-patches/2020-December/561135.html
<https://gcc.gnu.org/pipermail/gcc-patches/2020-December/561135.html>

https://github.com/lock3/gcc/tree/contracts-jac-alt
<https://github.com/lock3/gcc/tree/contracts-jac-alt>

Why is some of the code in c-family?  From the modules merge there is
now a cp_handle_option function that you could add the option
handling
to, and I don't see a reason for cxx-contracts.c to be in c-family/
rather than cp/.

I've been pushing changes that address the points raised and wanted to
ping to see if there's more feedback and give a status summary. The
notable change is making sure the implementation plays nicely with
modules and a mangling change that did away with a good chunk of code.

The contracts code outside of cp/ has been moved into it, and the
contract attributes have been altered to work with language
independent
handling code. More of the contracts code can probably still be
moved to
cxx-contracts which I'll loop back to after other refactoring. The
naming, spelling, and comments (or lack thereof) have been addressed.

Sounds good.  I plan to get back to this when GCC 11 branches, which
should be mid-April.

Please let me know if you see any more issues when you pick it back up.
Particularly in modules interop, since I don't think you've had a chance
to look at that yet.

Completed another merge with master earlier this week which didn't bring
to light any new issues or regressions, but I'll keep on that :)

+  /* If we have contracts, check that they're valid in this
context. */
+  // FIXME: These aren't entirely correct.

How not?  Can local extern function decls have contract attributes?

+             /* FIXME when we instatiate a template class with
guarded
+              * members, particularly guarded template members,
the resulting
+              * pre/post functions end up being inaccessible
because their
+              * template info's context is the original
uninstantiated class.

This sounds like a significant functionality gap.  I'm guessing you
want
to reconsider the unshare_template approach.

One approach would be to only do the pre/post/guarded/unguarded
transformation for a fully-instantiated function; a temploid function
would leave the contracts as attributes.


There's an in progress rewrite which moves pre/post function generation until
much later which should resolve this. Like you suggested, these are not created
until we're completely out of template processing.

+      /* FIXME do we need magic to perfectly forward this so we
don't clobber
+        RVO/NRVO etc?  */

Yes.  CALL_FROM_THUNK_P will probably get you some of the magic you
want.

These points are still being investigated and addressed; including
them
for reference.

Any update?


CALL_FROM_THUNK_P is being set now when an actual call is built. Is there a good
way to test that the optimization isn't being broken in general?

More soon.

Please let me know what other issues need work.

If there's anything I can do to make the process smoother please don't
hesitate to ask.

Larger-scope comments:

Please add an overview of the implementation strategy to the top of
cxx-contracts.c.  Particularly to discuss the why and how of
pre/post/guarded/unguarded functions.

An initial overview has been added, though it'll need updated with some of the
pending rewrites.


I'm confused by the approach to late parsing of contracts; it seems like
you wait until the end of parsing the function to go back and parse the
contracts.  Why not parse them sooner, along with nsdmis, noexcept, and
function bodies?

Parsing has been moved forward on the rewrite branch.


Smaller-scope comments:

+       /* If we didn't build a check, insert a NOP so we don't leak
+          contracts into GENERIC.  */
+       *stmt_p = build1 (NOP_EXPR, void_type_node, integer_zero_node);

You can use void_node for the NOP.

+      error_at (EXPR_LOCATION (new_contract),
+               "mismatched contract condition in %s",
+               ctx == cmc_declaration ? "declaration" : "override");

This sort of build-up of diagnostic messages by substring replacement
doesn't work very well for translations.  In general, don't use %s for
inserting English text, only code strings that will be the same in all
languages.

+  /* Remove the associated contracts and unchecked result, if any.  */
+  if (flag_contracts && TREE_CODE (newdecl) == FUNCTION_DECL)
+    {
+      remove_contract_attributes (newdecl);
+      set_contract_functions (newdecl, NULL_TREE, NULL_TREE);
+    }

Why bother removing attributes on a decl that's about to be freed?

Why did we set the contract functions above only to clear them now?

Because the pre/post FUNCTION_DECLs are in a parallel map rather than being a
part of the original FUNCTION_DECL itself, this was to prevent the potentially
reused newdecl from already being associated with the wrong functions if it
also had contracts. Moot now though :)


        if (DECL_EXTERNAL (decl) && ! DECL_TEMPLATE_SPECIALIZATION
(decl)
           /* Aliases are definitions. */
-         && !alias)
+         && !alias
+         && (DECL_VIRTUAL_P (decl) || !flag_contracts))
         permerror (declarator->id_loc,
                    "declaration of %q#D outside of class is not
definition",
                    decl);
+      else if (DECL_EXTERNAL (decl) && !
DECL_TEMPLATE_SPECIALIZATION (decl)
+         /* Aliases are definitions. */
+         && !alias
+         && flag_contract_strict_declarations)
+       warning_at (declarator->id_loc,
OPT_fcontract_strict_declarations_,
+                   "declaration of %q#D outside of class is not
definition",
+                   decl);

Let's share the common predicates.

+  /* FIXME: move fallthrough up here so it applies to decls/etc?  */

That seems unnecessary, we already warn and ignore fallthrough on a decl.

+int cp_contract_operand = 0;
+tree cp_contract_return_value = NULL_TREE;

These need to be saved/restored by push_to/pop_from_top_level somehow,
so that they don't stay set when parsing the contract triggers template
instantiation.  The best way is probably to add them to struct
saved_scope.

+      token = cp_lexer_peek_token (parser->lexer);
+      if (token->type == CPP_NAME)
+       {
+         attr_name = token->u.value;
+         attr_name = canonicalize_attr_name (attr_name);
+       }
+
+      /* Handle contract-attribute-specs specially.  */
+      if (attr_name && contract_attribute_p (attr_name))
+       {
+         attributes
+           = cp_parser_contract_attribute_spec (parser, attr_name);
+         goto finish_attrs;
+       }

Let's move the name checking inside cp_parser_contract_attribute_spec,
and make that function return null for a non-contract attribute.

+  /* FIXME we can do this a lot more efficiently? Once per function
for all of
+   * its pre contracts, and then once per post contract? Is there an
+   * appreciable difference? Or a way to simply rename the post ret
val parm? *

You might want to share the context handling with late parsing of
noexcept.

+  /* Don't do access checking if it is a templated function.  */
+  if (processing_template_decl)
+    push_deferring_access_checks (dk_no_check);

Don't we already defer access checking for templated functions?

+/* Return a copy of the FUNCTION_DECL IDECL with its own unshared +
PARM_DECL and DECL_ATTRIBUTEs.  */
+
+static tree
+copy_fn_decl (tree idecl)

Can you use, or at least share code with, the existing
copy_fndecl_with_name?

+  /* FIXME will later optimizations delete unused args to prevent
extra arg
+   * passing? do we care? */

It might be possible to leverage the optimizer's partial-inlining code
for this, I don't know.  Probably not worth messing with now.

More soon.

More:

+       if (semantic == CCS_ASSUME && !cp_tree_defined_p (c))
+         break;

Another approach to this would be to evaluate an assume with a separate
non_constant_p and within the lifetime of a
uid_sensitive_constexpr_evaluation_sentinel.  But the way you have it is
fine.

+  /* Ignored contracts are never checked or assumed.  */
+  if (semantic == CCS_IGNORE)
+    return build1 (NOP_EXPR, void_type_node, integer_zero_node);

Another place you can use void_node.

+static void
+build_contract_handler_fn (tree contract,
+                          contract_continuation cmode)

This function needs a comment, and a better name.  Maybe just
build_contract_handler_fn_call.

+/* Rewrite the post function decl of FNDECL, replacing the original
undeduced
+   return type with RETURN_TYPE.  */
+
+static void
+apply_post_deduced_return_type (tree fndecl, tree return_type)

Can this share more code with apply_deduced_return_type?

+  if (needs_post && DECL_POST_FN (current_function_decl) !=
error_mark_node)
+    {
+      vec<tree, va_gc> *args = build_arg_list (current_function_decl);
+      if (DECL_UNCHECKED_RESULT (current_function_decl))
+       vec_safe_push (args, expr); // FIXME do we need forward_parm or
similar?
+
+      if (undeduced_auto_decl (DECL_POST_FN (current_function_decl)))
+       apply_post_deduced_return_type (current_function_decl,
+                                       TREE_TYPE (expr));
+
+      push_deferring_access_checks (dk_no_check);
+      tree call = finish_call_expr (DECL_POST_FN (current_function_decl),
&args,
+                                   /*disallow_virtual=*/true,
+                                   /*koenig_p=*/false,
+                                   /*complain=*/tf_warning_or_error);
+      gcc_assert (call != error_mark_node);
+      pop_deferring_access_checks ();
+
+      /* Replace returned expression with call to post function.  */
+      expr = call;
+    }

This looks like you pass the returned expression as an argument to the
post function instead of using it to initialize the return value.  That
will break copy elision.

What if instead (for a class return type at least) you initialize the
return value, then pass a reference to the return value to the post
function?  Then you could also share the call to the post function
between all returns.

+       if (tree check = build_contract_check (stmt))
+         {
+           /* Mark the current function as possibly throwing exceptions
+              (through invocation of the contract violation handler).
*/
+           current_function_returns_abnormally = 1;
+           TREE_NOTHROW (current_function_decl) = 0;

These shouldn't be set here, they should be set as part of building the
call to the handler.  You should get that by changing build_call_expr to
build_call_n...

+  tree call = build_call_expr (violation_fn, 8, continue_mode,
line_number,
+                              file_name, function_name, comment,
+                              level_str, role_str,
+                              continuation);

...here.

+  /* FIXME: It looks  like we have two bits of information for
+     continuing.  Is this right?  */

I think this is no longer true since you dropped "always continue".

+  char buf[1024 + 4]{};
+  char *res = buf;
+  size_t len = 1024;

This seems like a good place to use obstacks.

+      int s = expstart.column - 1;
+      int l = expend.column - expstart.column + 1;

So, l = expend.column - s?


Changes to get_source to use obstacks and cleanup some of the math are also
sitting on a branch and will be cherry-picked once the rewrite is stable.

+++ b/libstdc++-v3/src/c++17/contract.cc

Hmm.  Contracts library support was in clause 17, which would mean this
should go in libstdc++-v3/libsupc++ instead.  But it depends on
string_view, which is not in clause 17.  This seems like a problem in
the standardese.

+      /* If this is the pre/post function for a guarded function, append
+        pre/post in the vendor specific portion of the mangling.
+
+        TODO: this likely needs standardizing.
+        TODO: do we need special handling in other tools like the
demangler? */
+      if (DECL_IS_PRE_FN_P (decl))
+       write_string (".pre");
+      else if (DECL_IS_POST_FN_P (decl))
+       write_string (".post");

Do you have in mind that these functions are part of the ABI, or
internal implementation details of the functions?  If the former, we
need to give them a proper mangling.  If the latter, we can make them
hidden symbols in the same COMDAT section with their guarded function
like with constructor variants, and their mangling isn't important.  I'm
still wondering why you split them out from the guarded function in the
first place.


Ditto. These are currently internal implementation details so they've been made
hidden. Future extensions to contracts might result in this being revisited.

+      remap_overrider_contracts (overrider, basefn);
+      remap_overrider_contracts (DECL_PRE_FN (overrider), DECL_PRE_FN
(basefn));
+      remap_overrider_contracts (DECL_POST_FN (overrider), DECL_POST_FN
(basefn));

Why three times?  Why copy the contracts from the guarded function to
the pre/post functions?

This is fixed in the rewrite branch.


+/* Assertion role info.  */

This needs elaboration about what a role is.

A lot of the code in decl.c could move to cxx-contracts.c.

Jason



Please let us know if there are other issues, or if it sounds like we're not
headed in the correct direction.

Thank you,
Jeff Chapman

Reply via email to