Patchwork RFC: [GUPC] UPC-related front-end changes

mail settings
Submitter Gary Funck
Date July 8, 2010, 6:17 a.m.
Message ID <>
Download mbox | patch
Permalink /patch/58221/
State New
Headers show


Gary Funck - July 8, 2010, 6:17 a.m.
RFC: UPC-related Front-End Changes

This document describes the UPC-related front-end
changes, and is provided as background for
review of the UPC changes implemented in
the GUPC branch.

The current GUPC branch is based upon a recent
version of the GCC trunk (161517), and has been
bootstrapped on both x86_64 Linux and i686 Linux.
Bootstraps on other platforms are in progress.

The GUPC branch is described here:

The UPC-related source code differnces
are summarized here:

In the discussion below, the changes are
excerpted in order to highlight important
aspects of the changes.

UPC's Shared Qualifier and Layout Qualifier

The UPC language specification describes
the language syntax and semantics:

UPC introduces a new qualifier, "shared"
that indicates that the qualified object
is located in a global shared address space
that is accessible by all UPC threads.
Additional qualifiers ("strict" and "relaxed")
further specify the semantics of accesses to
UPC shared objects.

In UPC, a shared qualified array can further
specify a "layout qualifier" that indicates
how the shared data is blocked and distributed.

There are two language pre-defined identifiers
that indicate the number of threads that
will be created when the program starts (THREADS)
and the current (zero-based) thread number
(MYTHREAD).  Typically, a UPC thread is implemented
as an operating system process.  Access to UPC
shared memory may be implemented locally via
OS provided facilities (for example, mmap),
or across nodes via a high speed network

GUPC provides a runtime (libupc) that targets
an SMP-based system and uses mmap() to implement
global shared memory.  

Optionally, GUPC can use the more general and
more capable Berkeley UPCR runtime:
The UPCR runtime supports a number of network
topologies, and has been ported to most of the
current High Performance Computing (HPC) systems.

The following example illustrates
the use of the UPC "shared" qualifier
combined with layout qualifier.

    #define BLKSIZE 5
    #define N_PER_THREAD (4 * BLKSIZE)
    shared [BLKSIZE] double A[N_PER_THREAD*THREADS];

Above the "[BLKSIZE]" construct is the UPC
layout factor; this specifies that the shared
array, A, distributes its elements across
each thread in blocks of 5 elements.  If the
program is run with two threads. then A is
distributed as shown below:

    Thread 0	Thread 1
    --------	---------
    A[ 0.. 4]	A[ 5.. 9]
    A[10..14]	A[15..19]
    A[20..24]	A[25..29]
    A[30..34]	A[35..39]

Above, the elements shown for thread 0
are defined as having "affinity" to thread 0.
Similarly, those elements shown for thread 1
have affinity to thread 1.  In UPC, a pointer
to a shared object can be cast to a thread
local pointer, when the designated shared
object has affinity to the referencing thread.

A "pointer-to-shared" (PTS) is a pointer
that references a UPC shared object.
A UPC pointer-to-shared is a "fat" pointer
with the following logical fields:
   (virt_addr, thread, offset)

The virtual address (virt_addr) field is combined with
the thread number (thread) and offset within the
block (offset), to derive the location of the
referenced object within the UPC shared address space.

GUPC implements pointer-to-shared objects using
either a "packed" representation or a "struct"
representation.  The user can select the
pointer-to-shared representation with a "configure"
parameter. The packed representation is the default.

The "packed" pointer-to-shared representation
limits the range of the various fields within
the pointer-to-shared in order to gain efficiency.
Packed pointer-to-shared values encode the three
part shared address (described above) as a 64-bit
value (on both 64-bit and 32-bit platforms).

The "struct" representation provides a wider
addressing range at the expense of requiring
twice the number of bits (128) needed to encode
the pointer-to-shared value.

UPC-Related Front-End Changes

GCC's internal tree representation is
extended to record the UPC "shared",
"strict", "relaxed" qualifiers,
and the layout qualifier.

      separately.  */
   tree attrs;
+  /* For UPC, this is the blocking factor (layout qualifier).
+     For example, shared [10] int x;  */
+  tree upc_layout_qualifier;

UPC defines a few additional tree node types:

+++ gcc/upc/upc-tree.def	(.../branches/gupc)	(revision 161914)
+/* UPC statements */
+/* Used to represent a `upc_forall' statement. The operands are
+   UPC_FORALL_BODY, and UPC_FORALL_AFFINITY respectively. */
+DEFTREECODE (UPC_FORALL_STMT, "upc_forall_stmt", tcc_statement, 5)
+/* Used to represent a UPC synchronization statement. The first
+   operand is the synchonization operation, UPC_SYNC_OP:
+   UPC_SYNC_NOTIFY_OP	1	Notify operation
+   UPC_SYNC_WAIT_OP	2	Wait operation
+   UPC_SYNC_BARRIER_OP	3	Barrier operation
+   The second operand, UPC_SYNC_ID is the (optional) expression
+   whose value specifies the barrier identifier which is checked
+   by the various synchronization operations. */
+DEFTREECODE (UPC_SYNC_STMT, "upc_sync_stmt", tcc_statement, 2)

The "C" parser is extended to recognize UPC's syntactic

--- gcc/c-family/c-common.c	(.../trunk)	(revision 161517)
+++ gcc/c-family/c-common.c	(.../branches/gupc)	(revision 161914)
+  /* UPC keywords */
+  { "shared",		RID_SHARED,		D_UPC },
+  { "relaxed",		RID_RELAXED,		D_UPC },
+  { "strict",		RID_STRICT,		D_UPC },
+  { "upc_barrier",	RID_UPC_BARRIER,	D_UPC },
+  { "upc_blocksizeof",	RID_UPC_BLOCKSIZEOF,	D_UPC },
+  { "upc_elemsizeof",	RID_UPC_ELEMSIZEOF,	D_UPC },
+  { "upc_forall",	RID_UPC_FORALL,		D_UPC },
+  { "upc_localsizeof",	RID_UPC_LOCALSIZEOF,	D_UPC },
+  { "upc_notify",	RID_UPC_NOTIFY,		D_UPC },
+  { "upc_wait",		RID_UPC_WAIT,		D_UPC },

--- gcc/c-parser.c	(.../trunk)	(revision 161517)
+++ gcc/c-parser.c	(.../branches/gupc)	(revision 161914)
+/* These UPC parser functions are only ever called when
+   compiling UPC.  */
+static void c_parser_upc_forall_statement (c_parser *);
+static void c_parser_upc_sync_statement (c_parser *, int);
+static void c_parser_upc_shared_qual (c_parser *, struct c_declspecs *);
+        /* UPC qualifiers */
+	case RID_SHARED:
+	  attrs_ok = true;
+          c_parser_upc_shared_qual (parser, specs);
+	  break;
+	case RID_STRICT:
+	  attrs_ok = true;
+	  declspecs_add_qual (specs, c_parser_peek_token (parser)->value);
+	  c_parser_consume_token (parser);
+	  break;
+  /* Process all #pragma's just after the opening brace.  This
+     handles #pragma upc, which can only appear just after
+     the opening brace, when it appears within a function body.  */
+  push_upc_consistency_mode ();
+  permit_pragma_upc ();
+  while (c_parser_next_token_is (parser, CPP_PRAGMA))
+    {
+      location_t loc ATTRIBUTE_UNUSED = c_parser_peek_token (parser)->location;
+      if (c_parser_pragma (parser, pragma_compound))
+        last_label = false, last_stmt = true;
+      parser->error = false;
+    }
+  deny_pragma_upc ();
+          gcc_assert (c_dialect_upc ());
+	  c_parser_upc_forall_statement (parser);
+	  break;
+        case RID_UPC_NOTIFY:
+          gcc_assert (c_dialect_upc ());
+	  c_parser_upc_sync_statement (parser, UPC_SYNC_NOTIFY_OP);
+	  goto expect_semicolon;
+        case RID_UPC_WAIT:
+          gcc_assert (c_dialect_upc ());
+	  c_parser_upc_sync_statement (parser, UPC_SYNC_WAIT_OP);
+	  goto expect_semicolon;
+        case RID_UPC_BARRIER:
+          gcc_assert (c_dialect_upc ());
+	  c_parser_upc_sync_statement (parser, UPC_SYNC_BARRIER_OP);
+	  goto expect_semicolon;
+          gcc_assert (c_dialect_upc ());
+	  return c_parser_sizeof_expression (parser);

--- gcc/c-family/c-pragma.c	(.../trunk)	(revision 161517)
+++ gcc/c-family/c-pragma.c	(.../branches/gupc)	(revision 161914)
+ *  #pragma upc strict
+ *  #pragma upc relaxed
+ *  #pragma upc upc_code
+ *  #pragma upc c_code
+ */
+static void
+handle_pragma_upc (cpp_reader * ARG_UNUSED (dummy))

c-decl.c handles the additional UPC qualifiers
and declspecs.  The layout qualifier is handled here:

+  /* A UPC layout qualifier is encoded as an ARRAY_REF,
+     further, it implies the presence of the 'shared' keyword. */
+  if (TREE_CODE (qual) == ARRAY_REF)
+    {
+      if (specs->upc_layout_qualifier)
+        error ("two or more layout qualifiers specified");
+      else
+        {
+          specs->upc_layout_qualifier = qual;
+          qual = ridpointers[RID_SHARED];
+        }
+    }
+	    /* Add UPC-defined block size, if supplied */
+	    if (layout_qualifier)
+	      type = upc_set_block_factor (POINTER_TYPE, type, layout_qualifier);
 	    size_varies = false;
+	    upc_threads_ref = 0;
+	    layout_qualifier = 0;

In UPC, a qualifier includes both the traditional
"C" qualifier flags and the UPC "layout qualifier".
Thus, the pointer_quals field of a declarator node
is defined as a struct including both qualifier
flags and the UPC type qualifier, as shown below.
 	    /* Process type qualifiers (such as const or volatile)
 	       that were given inside the `*'.  */
-	    type_quals = declarator->u.pointer_quals;
+	    type_quals = declarator->u.pointer.quals;
+	    layout_qualifier = declarator->u.pointer.upc_layout_qual;
+	    sharedp = ((type_quals & TYPE_QUAL_SHARED) != 0);

UPC shared variables are allocated at runtime in the global
memory that is allocated and managed by the UPC runtime.
A separate link section is used as a method of assigning
virtual addresses to UPC shared variables.  The UPC
shared variable section is designated as a "no load"
section on systems that support that facility; in that
case, the linkage section begins at virtual address zero.
The logic below assigns UPC shared variables to
their own linkage section.

+    /* Shared variables are given their own link section on
+       most target platforms, and if compiling in pthreads mode
+       regular local file scope variables are made thread local. */
+    if ((TREE_CODE(decl) == VAR_DECL)
+        && !threadp && (TREE_SHARED (decl) || flag_upc_pthreads))
+      upc_set_decl_section (decl);

Various UPC language related checks and operations
are called in the "C" front-end and middle-end.
To insure that these operations are defined,
when linked with the other language front-ends
and compilers, these functions are stub-ed,
in a fashion similar to Objective C:

+/* In upc-act.c and stub-upc.c */
+extern int count_upc_threads_refs (tree);
+extern int is_multiple_of_upc_threads (tree);
+extern int upc_check_decl_init (tree, tree);
+extern int upc_is_null_pts_p (tree);
+extern int upc_pts_cvt_op_p (tree);
+extern int upc_shared_type_p (tree);
+extern struct c_expr upc_blocksizeof_expr (location_t, struct c_expr);
+extern struct c_expr upc_elemsizeof_expr (location_t, struct c_expr);
+extern struct c_expr upc_localsizeof_expr (location_t, struct c_expr);
+extern tree upc_affinity_test (location_t, tree, tree);
+extern tree upc_build_shared_var_addr (location_t, tree, tree);
+extern tree upc_build_sync_stmt (location_t, tree, tree);
+extern tree upc_get_block_factor (const tree);
+extern tree upc_get_unshared_type (tree);
+extern tree upc_instrument_forall (location_t, int);
+extern tree upc_num_threads (void);
+extern tree upc_pts_diff (tree, tree);
+extern tree upc_pts_increment (location_t, enum tree_code, tree);
+extern tree upc_pts_int_sum (location_t, enum tree_code, tree, tree);
+extern tree upc_set_block_factor (enum tree_code, tree, tree);
+extern void set_upc_threads_refs_to_one (tree *);
+extern void upc_check_decl (tree);
+extern void upc_decl_init (tree, tree);
+extern void upc_set_decl_section (tree);

+/* used by c-parser */
+extern tree upc_build_sync_stmt (location_t, tree, tree);
+extern tree upc_affinity_test (location_t, tree, tree);
+extern struct c_expr upc_blocksizeof_expr (location_t, struct c_expr);
+extern struct c_expr upc_blocksizeof_type (location_t, struct c_type_name *);
+extern struct c_expr upc_elemsizeof_expr (location_t, struct c_expr);
+extern struct c_expr upc_elemsizeof_type (location_t, struct c_type_name *);
+extern struct c_expr upc_localsizeof_expr (location_t, struct c_expr);
+extern struct c_expr upc_localsizeof_type (location_t, struct c_type_name *);

+/* UPC related functions */
+extern void set_lang_layout_decl_p (int (*) (tree, tree));
+extern void set_lang_layout_decl (void (*) (tree, tree));

A few command line option flags must also be
stub'ed out in order to link the other
language front-ends.

+++ gcc/c-family/stub-upc.c	(.../branches/gupc)	(revision 161914)
+int compiling_upc;
+int flag_upc;
+int flag_upc_instrument;
+int flag_upc_instrument_functions;
+int use_upc_dwarf2_extensions;

The UPC-related front-end "diff's" are attached
for review.  All feedback and suggestions
are appreciated.  The goal is to work
these changes (and the others that will
be posted to this list) into a form that
the GUPC branch can be merged into the GCC trunk.

- Gary
This file has shows the differences between GCC trunk version 161517
and the GUPC branch, for the following files:

gcc/c-convert.c                gcc/c-parser.c         gcc/tree.c
gcc/c-decl.c                   gcc/cp/lex.c           gcc/tree.h
gcc/c-family/c-common.c        gcc/c-tree.h           gcc/upc/ChangeLog
gcc/c-family/c-common.h        gcc/c-typeck.c         gcc/upc/upc-act.c
gcc/c-family/c-cppbuiltin.c    gcc/explow.c           gcc/upc/upc-act.h
gcc/c-family/c-lex.c           gcc/function.c         gcc/upc/upc-lang.c
gcc/c-family/c-pragma.c        gcc/gcc.c              gcc/upc/upc-tree.def
gcc/ChangeLog.upc              gcc/langhooks-def.h    gcc/upc/upc-tree.h
gcc/c-objc-common.h            gcc/langhooks.h        gcc/varasm.c
gcc/config/upc-conf.h          gcc/libfuncs.h
gcc/convert.c                  gcc/stor-layout.c

Index: gcc/c-family/c-common.c
--- gcc/c-family/c-common.c	(.../trunk)	(revision 161517)
+++ gcc/c-family/c-common.c	(.../branches/gupc)	(revision 161914)
@@ -29,6 +29,7 @@ along with GCC; see the file COPYING3.  
 #include "tm.h"
 #include "intl.h"
 #include "tree.h"
+#include "c-tree.h"
 #include "flags.h"
 #include "output.h"
 #include "c-pragma.h"
@@ -199,6 +200,31 @@ const char *pch_file;
    user's namespace.  */
 int flag_iso;
+/* FIXME: Convert the UPC switch values below to use
+   the Var() definitions in c.opts, where applicable.  */
+/* Nonzero whenever UPC -fupc-threads-N is asserted.
+   The value N gives the number of UPC threads to be
+   defined at compile-time. */
+int flag_upc_threads;
+/* Nonzero whenever UPC -fupc-pthreads-model-* is asserted. */
+int flag_upc_pthreads;
+/* The -fupc-pthreads-per-process-N switch tells the UPC compiler
+   and runtime to map N UPC threads per process onto
+   N POSIX threads running inside the process. */
+int flag_upc_pthreads_per_process;
+/* The -fupc-inline-lib switch tells the UPC compiler to
+   inline shared access routines. */
+int flag_upc_inline_lib;
+/* The implementation model for UPC threads that
+   are mapped to POSIX threads, specified at compilation
+   time by the -fupc-pthreads-model-* switch. */
+upc_pthreads_model_kind upc_pthreads_model;
 /* Warn about #pragma directives that are not recognized.  */
 int warn_unknown_pragmas; /* Tri state variable.  */
@@ -376,8 +402,9 @@ static int resort_field_decl_cmp (const 
    C --std=c89: D_C99 | D_CXXONLY | D_OBJC | D_CXX_OBJC
    C --std=c99: D_CXXONLY | D_OBJC
    ObjC is like C except that D_OBJC and D_CXX_OBJC are not set
-   C++ --std=c98: D_CONLY | D_CXXOX | D_OBJC
-   C++ --std=c0x: D_CONLY | D_OBJC
+   UPC is like C except that D_UPC is not set
+   C++ --std=c98: D_CONLY | D_CXXOX | D_OBJC | D_UPC
+   C++ --std=c0x: D_CONLY | D_OBJC | D_UPC
    ObjC++ is like C++ except that D_OBJC is not set
    If -fno-asm is used, D_ASM is added to the mask.  If
@@ -546,6 +573,19 @@ const struct c_common_resword c_common_r
   { "inout",		RID_INOUT,		D_OBJC },
   { "oneway",		RID_ONEWAY,		D_OBJC },
   { "out",		RID_OUT,		D_OBJC },
+  /* UPC keywords */
+  { "shared",		RID_SHARED,		D_UPC },
+  { "relaxed",		RID_RELAXED,		D_UPC },
+  { "strict",		RID_STRICT,		D_UPC },
+  { "upc_barrier",	RID_UPC_BARRIER,	D_UPC },
+  { "upc_blocksizeof",	RID_UPC_BLOCKSIZEOF,	D_UPC },
+  { "upc_elemsizeof",	RID_UPC_ELEMSIZEOF,	D_UPC },
+  { "upc_forall",	RID_UPC_FORALL,		D_UPC },
+  { "upc_localsizeof",	RID_UPC_LOCALSIZEOF,	D_UPC },
+  { "upc_notify",	RID_UPC_NOTIFY,		D_UPC },
+  { "upc_wait",		RID_UPC_WAIT,		D_UPC },
 const unsigned int num_c_common_reswords =
@@ -3593,6 +3633,11 @@ pointer_int_sum (location_t loc, enum tr
   /* The result is a pointer of the same type that is being added.  */
   tree result_type = TREE_TYPE (ptrop);
+  /* If the pointer lives in UPC shared memory, then
+     drop the 'shared' qualifier.  */
+  if (upc_shared_type_p (result_type))
+    result_type = upc_get_unshared_type (result_type);
   if (TREE_CODE (TREE_TYPE (result_type)) == VOID_TYPE)
       pedwarn (loc, pedantic ? OPT_pedantic : OPT_Wpointer_arith,
@@ -3989,6 +4034,20 @@ c_apply_type_quals_to_decl (int type_qua
 	error ("invalid use of %<restrict%>");
+  if (type_quals & TYPE_QUAL_SHARED)
+    {
+      TREE_SHARED (decl) = 1;
+	 for "strict" qualified types?  At the moment, this
+	 leads to ICE in gimple_has_volatile_ops(). */
+      if (type_quals & TYPE_QUAL_STRICT)
+	TREE_STRICT(decl) = 1;
+      else if (type_quals & TYPE_QUAL_RELAXED)
+	TREE_RELAXED(decl) = 1;
+      /* The declaration's type should have been previously defined
+	 as a UPC shared type.  */
+      gcc_assert (upc_shared_type_p (type));
+    }
 /* Hash function for the problem of multiple type definitions in
@@ -4246,6 +4305,14 @@ c_sizeof_or_alignof_type (location_t loc
 	value = size_int (TYPE_ALIGN_UNIT (type));
+  if (is_sizeof && (TREE_CODE (type) == ARRAY_TYPE)
+      && upc_shared_type_p (type)
+    {
+      const tree n_threads = convert (sizetype, upc_num_threads ());
+      value = size_binop (MULT_EXPR, value, n_threads);
+    }
   /* VALUE will have an integer type with TYPE_IS_SIZETYPE set.
      TYPE_IS_SIZETYPE means that certain things (like overflow) will
      never happen.  However, this node should really have type

Property changes on: gcc/c-family/c-common.c
Tom Tromey - July 8, 2010, 3:06 p.m.
>>>>> "Gary" == Gary Funck <> writes:


I didn't really read most of the patch, but this bit stood out for me:

Gary> --- gcc/tree.h	(.../trunk)	(revision 161517)
Gary> +++ gcc/tree.h	(.../branches/gupc)	(revision 161914)
Gary> @@ -367,6 +367,12 @@ struct GTY(()) tree_base {
Gary>    unsigned asm_written_flag: 1;
Gary>    unsigned nowarning_flag : 1;
Gary> +  /* UPC flags */
Gary> +  unsigned shared_flag : 1;		/* UPC: shared  qualified */
Gary> +  unsigned strict_flag : 1;		/* UPC: strict  qualified */
Gary> +  unsigned relaxed_flag : 1;		/* UPC: relaxed qualified */
Gary> +  unsigned upc_unused : 5;		/* UPC: unused bits  */

I think it would be better to use some bits from tree_base's "spare"
field and not have "upc_unused" at all.  tree_base is size-sensitive.

Joseph S. Myers - July 27, 2010, 10:39 p.m.
On Wed, 7 Jul 2010, Gary Funck wrote:

> The UPC-related front-end "diff's" are attached
> for review.  All feedback and suggestions

This patch does not seem to be in a state in which it is ready for 
detailed technical review of whether it implements correct semantics.

* Before posting a patch for review, clean up FIXMEs and TODOs.

* There are a large number of copyright and license notices in an 
extremely obsolete form, referencing "GNU CC".  Make sure to use the 
current standard forms of all such notices.

* Review code for correct conformance to coding standards.  Comments 
before all functions that explain the function semantics, all parameters 
and the return value.  Spaces before parentheses where appropriate.  
Diagnostics following GNU standards including no closing "." and starting 
with lowercase.

* Do not try to build up diagnostics with sprintf; this is unfriendly to 
i18n.  Put appropriate format strings directly in the calls to diagnostic 
functions.  Avoid direct use of IDENTIFIER_POINTER in diagnostics as that 
is also unfriendly to i18n; use formats such as %qD instead.

* It is not acceptable for files in c-family/ to include c-tree.h, because 
c-tree.h describes facilities only available in the C and ObjC front ends 
and not available for C++.  Do not revert recent modularity improvements 
such as this.

* It appears you have a large number of #ifdef conditionals with no 
obvious reason for the macros being conditionally defined or not defined.  
The use of conditional compilation like this is deprecated for new code.  
If something may vary from target to target, use target hooks, not macros, 
and document them in tm.texi (if they are in fact documented there in 
something outside this patch, perhaps you need to post that other patch).  
Conditions using "if" are strongly preferred to those using "#if" whenever 

* In general, since this code has evidently been around for a very long 
time given such things as use of "GNU CC", it needs careful review for 
whether it accords with current GCC coding practices for new code and 
takes account of cleanups done in the past decade or so, rather than with 
the practices of a decade ago.

* Make sure the diffs do not make unrelated changes to code not otherwise 
being modified.  For example, the

@@ -5387,7 +5471,7 @@ grokdeclarator (const struct c_declarato

diff hunk appears just to be changing the indentation of a comment, and 
while that looks like a correct change it's got nothing to do with UPC, so 
should go in trunk separately.
Gary Funck - July 31, 2010, 5:35 p.m.
On 07/27/10 22:39:50, Joseph S. Myers wrote:
> On Wed, 7 Jul 2010, Gary Funck wrote:
> > The UPC-related front-end "diff's" are attached
> > for review.  All feedback and suggestions
> This patch does not seem to be in a state in which it is ready for 
> detailed technical review of whether it implements correct semantics.

Joseph, thanks for the detailed review and suggestions.
I will re-work the GUPC front-end related changes into the
appropriate form, and re-submit the patch.

- Gary


--- gcc/tree.h	(.../trunk)	(revision 161517)
+++ gcc/tree.h	(.../branches/gupc)	(revision 161914)
@@ -367,6 +367,12 @@  struct GTY(()) tree_base {
   unsigned asm_written_flag: 1;
   unsigned nowarning_flag : 1;
+  /* UPC flags */
+  unsigned shared_flag : 1;		/* UPC: shared  qualified */
+  unsigned strict_flag : 1;		/* UPC: strict  qualified */
+  unsigned relaxed_flag : 1;		/* UPC: relaxed qualified */
+  unsigned upc_unused : 5;		/* UPC: unused bits  */

--- gcc/c-tree.h	(.../trunk)	(revision 161517)
+++ gcc/c-tree.h	(.../branches/gupc)	(revision 161914)
@@ -221,6 +261,9 @@  struct c_declspecs {
      NULL; attributes (possibly from multiple lists) will be passed