Patchwork RFA: implement C11 _Generic

login
register
mail settings
Submitter Tom Tromey
Date July 27, 2012, 7:40 p.m.
Message ID <87mx2l9emz.fsf@fleche.redhat.com>
Download mbox | patch
Permalink /patch/173769/
State New
Headers show

Comments

Tom Tromey - July 27, 2012, 7:40 p.m.
This patch attempts to implement the C11 _Generic feature.
Based on the last comment in

    http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46073

I am not at all sure I've done it correctly.

There are a couple of other things that aren't clear to me.

First, should c_parser_generic_selection call mark_exp_read on each
expression in the generic association list?  I chose not to, on the
basis that those expressions are parsed but not evaluated or otherwise
used; but I am not sure that this is correct.  (I did choose to call
it for the controlling expression, by analogy with typeof).

Second, it isn't clear to me whether setting
c_inhibit_evaluation_warnings is sufficient here.  I welcome your
advice.

Finally, I'd appreciate advice on the content of the various error
messages.

I wrote some new tests.  I tried to test every constraint in the spec,
but I have never really been all that good at language lawyering.

Comments?

2012-07-27  Tom Tromey  <tromey@redhat.com>

	* c-common.h (enum rid) <RID_GENERIC>: New constant.
	* c-common.c (c_common_reswords): Add _Generic.

2012-07-27  Tom Tromey  <tromey@redhat.com>

	* c-parser.c (struct c_generic_association): New.
	(c_generic_association_d): New typedef.
	(c_parser_generic_selection): New function.
	(c_parser_postfix_expression): Handle RID_GENERIC.

2012-07-27  Tom Tromey  <tromey@redhat.com>

	* gcc.dg/c11-generic-2.c: New file.
	* gcc.dg/c11-generic-1.c: New file.

---
 gcc/c-family/ChangeLog               |    5 +
 gcc/c-family/c-common.c              |    1 +
 gcc/c-family/c-common.h              |    4 +-
 gcc/c/ChangeLog                      |    7 ++
 gcc/c/c-parser.c                     |  193 ++++++++++++++++++++++++++++++++++
 gcc/testsuite/ChangeLog              |    5 +
 gcc/testsuite/gcc.dg/c11-generic-1.c |   28 +++++
 gcc/testsuite/gcc.dg/c11-generic-2.c |   24 ++++
 8 files changed, 265 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/c11-generic-1.c
 create mode 100644 gcc/testsuite/gcc.dg/c11-generic-2.c
Joseph S. Myers - July 27, 2012, 7:49 p.m.
Could you explain the choices you have made for the issues raised on 
comp.std.c last month (regarding the handling of qualifiers on controlling 
expressions) and the rationale for those choices (and make sure there are 
appropriate testcases)?
Tom Tromey - July 27, 2012, 8:31 p.m.
>>>>> "Joseph" == Joseph S Myers <joseph@codesourcery.com> writes:

Joseph> Could you explain the choices you have made for the issues
Joseph> raised on comp.std.c last month (regarding the handling of
Joseph> qualifiers on controlling expressions) and the rationale for
Joseph> those choices (and make sure there are appropriate testcases)?

I found this:

    https://groups.google.com/forum/?fromgroups#!topic/comp.std.c/InNlRotSWTc

I wasn't really aware of 6.3.2.1, but after reading it and re-reading
6.5.1.1, I think I agree with his "model 0" interpretation: no promotion
or conversion.

I don't have a standards-based reason for this, though; just my belief
that _Generic was omitted from 6.3.2.1 by mistake.

That's an amateur opinion though.  I think it would be better for you to
say what you think.


I thought I was implementing "model 0", but the test program he posts
shows differently.

I'm happy to try to fix that up, but I'd like your guidance as to the
proper direction.

Tom
Joseph S. Myers - July 27, 2012, 9:47 p.m.
On Fri, 27 Jul 2012, Tom Tromey wrote:

> I found this:
> 
>     https://groups.google.com/forum/?fromgroups#!topic/comp.std.c/InNlRotSWTc
> 
> I wasn't really aware of 6.3.2.1, but after reading it and re-reading
> 6.5.1.1, I think I agree with his "model 0" interpretation: no promotion
> or conversion.
> 
> I don't have a standards-based reason for this, though; just my belief
> that _Generic was omitted from 6.3.2.1 by mistake.
> 
> That's an amateur opinion though.  I think it would be better for you to
> say what you think.

I think there are three standards issues and two GCC issues:

* Integer promotions.  I think it's clear that those do not occur; a 
"short" value remains as "short" rather than "int".

* Conversion of qualified to unqualified types (which normally would occur 
as part of lvalue-to-rvalue conversion, but might also be an issue with 
casts to qualified types).  The given example of a cbrt type-generic macro 
would only work as users expect (given that an argument might be a 
variable of type "const long double", or a cast to const long double) if 
the expression (whether lvalue or rvalue) is converted from qualified to 
unqualified type.  That suggests qualifiers should be discarded on the 
controlling expression (and if one of the type names specifies a qualified 
type, that case can never match).

* Conversion of arrays and function designators to pointers.  Given the 
removal of qualifiers it would seem natural for this to apply as well (to 
the controlling expression - not of course to the selected expression, 
given that this may explicitly be a function designator).  But unlike the 
case of qualifiers, there's no real pointer in the standard to what's 
intended.

And the GCC issues:

* GCC doesn't have very well-defined semantics for whether an rvalue has a 
qualified type or not.  This only appears as an issue with typeof at 
present, but does require more care about eliminating qualifiers for 
_Generic.

* build_unary_op has code

      /* If the lvalue is const or volatile, merge that into the type
         to which the address will point.  This is only needed
         for function types.  */

that will give pointer-to-qualified type to expressions such as &abort - 
the address of a noreturn function.  Again, at language level this is only 
visible with typeof at present.  But in C11 terms, _Noreturn is not part 
of a function's type.  This means that if the controlling expression is 
something such as &abort, with pointer-to-qualified-function type, the 
qualifiers on the pointer target type need to be removed for the 
comparisons.
Tom Tromey - July 30, 2012, 1:11 p.m.
>>>>> "Joseph" == Joseph S Myers <joseph@codesourcery.com> writes:

Tom> I wasn't really aware of 6.3.2.1, but after reading it and re-reading
Tom> 6.5.1.1, I think I agree with his "model 0" interpretation: no promotion
Tom> or conversion.

Tom> I don't have a standards-based reason for this, though; just my belief
Tom> that _Generic was omitted from 6.3.2.1 by mistake.

I re-re-read the sections and came up with a standards-based reason:

6.3 is about conversions, and the first paragraph starts "several
operators convert ...".  Based on this, and other such phrases in the
text, I think the entire section applies to operators.

However, _Generic is not called an operator in the text.  It is a
primary expression.

Therefore, 6.3.2 does not apply.

This seems plausible to me, if a bit legalistic.

Joseph> * Conversion of qualified to unqualified types (which normally
Joseph> would occur as part of lvalue-to-rvalue conversion, but might
Joseph> also be an issue with casts to qualified types).  The given
Joseph> example of a cbrt type-generic macro would only work as users
Joseph> expect (given that an argument might be a variable of type
Joseph> "const long double", or a cast to const long double) if the
Joseph> expression (whether lvalue or rvalue) is converted from
Joseph> qualified to unqualified type.

It seems to me that keeping the qualifiers is obviously more useful.
Qualifiers can be stripped in various ways, but once stripped, can't be
regained.

The cbrt example can be salvaged by adding a few extra generic
associations.  This can even be easily done via a macro.

#define CLAUSE(TYPE, EXPR) \
   const TYPE: EXPR,
   TYPE: EXPR

#define cbrt(X) _Generic ((X), CLAUSE (double, ...), ...)

Joseph> * Conversion of arrays and function designators to pointers.

If the above interpretation is correct then this conversion is not done
either.  IIUC.

Joseph> * GCC doesn't have very well-defined semantics for whether an
Joseph> rvalue has a qualified type or not.  This only appears as an
Joseph> issue with typeof at present, but does require more care about
Joseph> eliminating qualifiers for _Generic.

Joseph> * build_unary_op has code
[...]

Thanks.  I'll take a look, but these sound a bit scary at first glance.

Tom
Joseph S. Myers - July 30, 2012, 2:35 p.m.
On Mon, 30 Jul 2012, Tom Tromey wrote:

> 6.3 is about conversions, and the first paragraph starts "several
> operators convert ...".  Based on this, and other such phrases in the
> text, I think the entire section applies to operators.

6.3.2.1 paragraphs 2 and 3 are phrased in terms of operators *preventing* 
conversion and certain conversions happening unless there is an operator 
to prevent them.

It seems entirely clear that you can use a function designator inside a 
braced initializer for an array of function pointers, for example, 
although an element of such an initializer is not an operand of an 
operator.

(I cannot find anything in a quick look through the ISO/IEC Directives 
Part 2 to indicate whether subclause titles such as "Other operands" are 
meant to be normative or informative.)

> However, _Generic is not called an operator in the text.  It is a
> primary expression.

A generic-selection is a primary expression.  _Generic is a keyword that 
is part of the syntax for a generic-selection.  C99 and C11 do not have a 
complete syntactic definition for "operator" anywhere (C90 did have such a 
syntax production).

> The cbrt example can be salvaged by adding a few extra generic
> associations.  This can even be easily done via a macro.
> 
> #define CLAUSE(TYPE, EXPR) \
>    const TYPE: EXPR,
>    TYPE: EXPR
> 
> #define cbrt(X) _Generic ((X), CLAUSE (double, ...), ...)

That doesn't handle volatile, or _Atomic.  If dealing with pointers you 
have restrict as well.  And in the presence of TR 18037 you have address 
space qualifiers, meaning such a macro cannot be written in a way agnostic 
to the set of address spaces on the system where the code is used.  
(Using +(X) or (X)+0 may work in some cases - if the promotions those 
cause aren't problems and it isn't a case where those operations are 
invalid.)

And to keep qualifiers here you'd need various other parts of the standard 
to be much clearer about whether qualifiers are present in the types of 
rvalues - an issue that previously hasn't been relevant.
Tom Tromey - July 30, 2012, 2:48 p.m.
>>>>> "Joseph" == Joseph S Myers <joseph@codesourcery.com> writes:

Joseph> On Mon, 30 Jul 2012, Tom Tromey wrote:
>> 6.3 is about conversions, and the first paragraph starts "several
>> operators convert ...".  Based on this, and other such phrases in the
>> text, I think the entire section applies to operators.

Joseph> 6.3.2.1 paragraphs 2 and 3 are phrased in terms of operators
Joseph> *preventing* conversion and certain conversions happening unless
Joseph> there is an operator to prevent them.

Wow, I really don't read it that way at all.  Looking at it yet again,
now, I can't even really make it come out that way.

I think it is now clear that I ought to drop this.

Tom
Joseph S. Myers - July 30, 2012, 3:35 p.m.
On Mon, 30 Jul 2012, Tom Tromey wrote:

> >>>>> "Joseph" == Joseph S Myers <joseph@codesourcery.com> writes:
> 
> Joseph> On Mon, 30 Jul 2012, Tom Tromey wrote:
> >> 6.3 is about conversions, and the first paragraph starts "several
> >> operators convert ...".  Based on this, and other such phrases in the
> >> text, I think the entire section applies to operators.
> 
> Joseph> 6.3.2.1 paragraphs 2 and 3 are phrased in terms of operators
> Joseph> *preventing* conversion and certain conversions happening unless
> Joseph> there is an operator to prevent them.
> 
> Wow, I really don't read it that way at all.  Looking at it yet again,
> now, I can't even really make it come out that way.

"Except when it is the operand of [...] an lvalue [...] is converted 
[...]" seems straightforward enough to me.  Thus, as another example

volatile int *p;
void f(void) { *p; }

dereferences the pointer when f is called (the lvalue *p is converted to 
an rvalue in the expression statement, without *p being an operand of an 
operator; if it were an operand of unary '&', that would stop the 
conversion).

Patch

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index b72506b..cc880de 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -418,6 +418,7 @@  const struct c_common_resword c_common_reswords[] =
   { "_Sat",             RID_SAT,       D_CONLY | D_EXT },
   { "_Static_assert",   RID_STATIC_ASSERT, D_CONLY },
   { "_Noreturn",        RID_NORETURN,  D_CONLY },
+  { "_Generic",         RID_GENERIC,   D_CONLY },
   { "__FUNCTION__",	RID_FUNCTION_NAME, 0 },
   { "__PRETTY_FUNCTION__", RID_PRETTY_FUNCTION_NAME, 0 },
   { "__alignof",	RID_ALIGNOF,	0 },
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 050112e..415b6e0 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1,6 +1,6 @@ 
 /* Definitions for c-common.c.
    Copyright (C) 1987, 1993, 1994, 1995, 1997, 1998,
-   1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2008, 2009, 2010, 2011
+   1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007, 2008, 2009, 2010, 2011, 2012
    Free Software Foundation, Inc.
 
 This file is part of GCC.
@@ -108,7 +108,7 @@  enum rid
   RID_FRACT, RID_ACCUM,
 
   /* C11 */
-  RID_ALIGNAS,
+  RID_ALIGNAS, RID_GENERIC,
 
   /* This means to warn that this is a C++ keyword, and then treat it
      as a normal identifier.  */
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 2237749..360cc58 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -6158,6 +6158,195 @@  c_parser_get_builtin_args (c_parser *parser, const char *bname,
   return true;
 }
 
+/* This represents a single generic-association.  */
+
+struct c_generic_association
+{
+  /* The location of the starting token of the type.  */
+  location_t type_location;
+  /* The association's type, or NULL_TREE for 'default'..  */
+  tree type;
+  /* The association's expression.  */
+  struct c_expr expression;
+};
+
+typedef struct c_generic_association c_generic_association_d;
+
+DEF_VEC_O (c_generic_association_d);
+DEF_VEC_ALLOC_O (c_generic_association_d, heap);
+
+/* Parse a generic-selection.  (C11 6.5.1.1).
+   
+   generic-selection:
+     _Generic ( assignment-expression , generic-assoc-list )
+     
+   generic-assoc-list:
+     generic-association
+     generic-assoc-list , generic-association
+   
+   generic-association:
+     type-name : assignment-expression
+     default : assignment-expression
+*/
+
+static struct c_expr
+c_parser_generic_selection (c_parser *parser)
+{
+  VEC (c_generic_association_d, heap) *associations = NULL;
+  struct c_expr selector, error_expr;
+  tree selector_type;
+  struct c_generic_association matched_assoc;
+  int match_found = 0;
+  location_t generic_loc, selector_loc;
+
+  error_expr.original_code = ERROR_MARK;
+  error_expr.original_type = NULL;
+  error_expr.value = error_mark_node;
+
+  gcc_assert (c_parser_next_token_is_keyword (parser, RID_GENERIC));
+  generic_loc = c_parser_peek_token (parser)->location;
+  c_parser_consume_token (parser);
+  if (!flag_isoc11)
+    {
+      if (flag_isoc99)
+	pedwarn (generic_loc, OPT_Wpedantic,
+		 "ISO C99 does not support %<_Generic%>");
+      else
+	pedwarn (generic_loc, OPT_Wpedantic,
+		 "ISO C90 does not support %<_Generic%>");
+    }
+
+  if (!c_parser_require (parser, CPP_OPEN_PAREN, "expected %<.%>"))
+    return error_expr;
+
+  c_inhibit_evaluation_warnings++;
+  selector_loc = c_parser_peek_token (parser)->location;
+  selector = c_parser_expr_no_commas (parser, NULL);
+  c_inhibit_evaluation_warnings--;
+
+  if (selector.value == error_mark_node)
+    return selector;
+  mark_exp_read (selector.value);
+  selector_type = TREE_TYPE (selector.value);
+
+  if (!c_parser_require (parser, CPP_COMMA, "expected %<.%>"))
+    return error_expr;
+
+  while (1)
+    {
+      struct c_generic_association assoc, *iter;
+      int ix;
+      c_token *token = c_parser_peek_token (parser);
+
+      assoc.type_location = token->location;
+      if (token->type == CPP_KEYWORD && token->keyword == RID_DEFAULT)
+	{
+	  c_parser_consume_token (parser);
+	  assoc.type = NULL_TREE;
+	}
+      else
+	{
+	  struct c_type_name *type_name;
+
+	  type_name = c_parser_type_name (parser);
+	  if (type_name == NULL)
+	    goto error_exit;
+	  assoc.type = groktypename (type_name, NULL, NULL);
+	  if (assoc.type == error_mark_node)
+	    goto error_exit;
+
+	  if (! COMPLETE_TYPE_P (assoc.type))
+	    error_at (assoc.type_location,
+		      "%<_Generic%> association has incomplete type");
+
+	  if (variably_modified_type_p (assoc.type, NULL_TREE))
+	    error_at (assoc.type_location,
+		      "%<_Generic%> association has "
+		      "variable length type");
+	}
+
+      if (!c_parser_require (parser, CPP_COLON, "expected %<.%>"))
+	goto error_exit;
+
+      assoc.expression = c_parser_expr_no_commas (parser, NULL);
+      if (assoc.expression.value == error_mark_node)
+	goto error_exit;
+
+      for (ix = 0;
+	   VEC_iterate (c_generic_association_d, associations, ix, iter);
+	   ++ix)
+	{
+	  if (assoc.type == NULL_TREE)
+	    {
+	      if (iter->type == NULL_TREE)
+		{
+		  error_at (assoc.type_location,
+			    "duplicate %<default%> case in %<_Generic%>");
+		  inform (iter->type_location, "original %<default%> is here");
+		}
+	    }
+	  else if (iter->type != NULL_TREE)
+	    {
+	      if (comptypes (assoc.type, iter->type))
+		{
+		  error_at (assoc.type_location,
+			    "%<_Generic%> specifies two compatible types");
+		  inform (iter->type_location, "compatible type is here");
+		}
+	    }
+	}
+
+      if (assoc.type == NULL_TREE)
+	{
+	  if (!match_found)
+	    {
+	      matched_assoc = assoc;
+	      match_found = 1;
+	    }
+	}
+      else if (comptypes (assoc.type, selector_type))
+	{
+	  if (!match_found || matched_assoc.type == NULL_TREE)
+	    {
+	      matched_assoc = assoc;
+	      match_found = 1;
+	    }
+	  else
+	    {
+	      error_at (assoc.type_location,
+			"%<_Generic> selector matches multiple associations");
+	      inform (matched_assoc.type_location,
+		      "other match is here");
+	    }
+	}
+
+      VEC_safe_push (c_generic_association_d, heap, associations, &assoc);
+
+      if (c_parser_peek_token (parser)->type != CPP_COMMA)
+	break;
+      c_parser_consume_token (parser);
+    }
+
+  VEC_free (c_generic_association_d, heap, associations);
+
+  if (!c_parser_require (parser, CPP_CLOSE_PAREN, "expected %<.%>"))
+    return error_expr;
+
+  if (!match_found)
+    {
+      error_at (selector_loc, "%<_Generic%> selector of type %qT is not "
+		"compatible with any association",
+		selector_type);
+      return error_expr;
+    }
+
+  mark_exp_read (matched_assoc.expression.value);
+  return matched_assoc.expression;
+
+ error_exit:
+  VEC_free (c_generic_association_d, heap, associations);
+  return error_expr;
+}
 
 /* Parse a postfix expression (C90 6.3.1-6.3.2, C99 6.5.1-6.5.2).
 
@@ -6181,6 +6370,7 @@  c_parser_get_builtin_args (c_parser *parser, const char *bname,
      constant
      string-literal
      ( expression )
+     generic-selection
 
    GNU extensions:
 
@@ -6749,6 +6939,9 @@  c_parser_postfix_expression (c_parser *parser)
 	    expr.value = objc_build_encode_expr (type);
 	  }
 	  break;
+	case RID_GENERIC:
+	  expr = c_parser_generic_selection (parser);
+	  break;
 	default:
 	  c_parser_error (parser, "expected expression");
 	  expr.value = error_mark_node;
diff --git a/gcc/testsuite/gcc.dg/c11-generic-1.c b/gcc/testsuite/gcc.dg/c11-generic-1.c
new file mode 100644
index 0000000..156d3a6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c11-generic-1.c
@@ -0,0 +1,28 @@ 
+/* Test C11 _Generic.  Valid uses.  */
+/* { dg-do run } */
+/* { dg-options "-std=c11 -pedantic-errors" } */
+
+extern void exit (int);
+extern void abort (void);
+
+void
+check (int n)
+{
+  if (n)
+    abort ();
+}
+
+int
+main (void)
+{
+  int n = 0;
+
+  check (_Generic (n++, int: 0));
+  /* _Generic should not evaluate its argument.  */
+  check (n);
+
+  check (_Generic (n, double: n++, default: 0));
+  check (n);
+
+  exit (0);
+}
diff --git a/gcc/testsuite/gcc.dg/c11-generic-2.c b/gcc/testsuite/gcc.dg/c11-generic-2.c
new file mode 100644
index 0000000..db06a8e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c11-generic-2.c
@@ -0,0 +1,24 @@ 
+/* Test C11 _Generic.  Error cases.  */
+/* { dg-do compile } */
+/* { dg-options "-std=c11 -pedantic-errors" } */
+
+struct incomplete;
+
+void
+f (int n)
+{
+  /* Multiple 'default's.  */
+  _Generic (n, default: 1, default: 2); /* { dg-error "duplicate .*default.* case" } */
+
+  /* Variably-modified type not ok.  */
+  _Generic (n, int[n]: 0, default: 1);	/* { dg-error "variable length type" } */
+  /* Type must be complete.  */
+  _Generic (n, struct incomplete: 0, default: 1); /* { dg-error "incomplete type" } */
+  _Generic (n, void: 0, default: 1); /* { dg-error "incomplete type" } */
+
+  /* Two compatible types in association list.  */
+  _Generic (&n, int: 5, signed int: 7, default: 23); /* { dg-error "two compatible types" } */
+
+  /* No matching association.  */
+  _Generic (n, void *: 5);	/* { dg-error "not compatible with any association" } */
+}