public inbox for fortran@gcc.gnu.org
 help / color / mirror / Atom feed
From: Julian Brown <julian@codesourcery.com>
To: <gcc-patches@gcc.gnu.org>
Cc: <fortran@gcc.gnu.org>, Tobias Burnus <tobias@codesourcery.com>,
	"Jakub Jelinek" <jakub@redhat.com>,
	Thomas Schwinge <thomas@codesourcery.com>
Subject: [PATCH v6 09/11] OpenMP: lvalue parsing for map clauses (C)
Date: Fri, 23 Dec 2022 04:13:02 -0800	[thread overview]
Message-ID: <cb79a146432898f400ad52e364b0c7d27098f0ca.1671796516.git.julian@codesourcery.com> (raw)
In-Reply-To: <cover.1671796515.git.julian@codesourcery.com>

This patch adds support for parsing general lvalues for OpenMP "map", "to"
and "from" clauses to the C front-end, similar to the previously-posted
patch for C++.

This version of the patch incorporates the patch to change uses of
TREE_LIST to the new OMP_ARRAY_SECTION tree code to represent OpenMP
array sections, and rejects array sections in certain expressions where
they make no sense (see new tests).

2022-12-22  Julian Brown  <julian@codesourcery.com>

gcc/c/
	* c-pretty-print.cc (c_pretty_printer::postfix_expression,
	c_pretty_printer::expression): Add OMP_ARRAY_SECTION support.
	* c-parser.cc (c_parser_braced_init, c_parser_conditional_expression):
	Don't allow OpenMP array section.
	(c_parser_postfix_expression): Don't allow array section in statement
	expression.
	(c_parser_postfix_expression_after_primary): Add support
	for OpenMP array section parsing.
	(c_parser_expr_list): Don't allow OpenMP array section here.
	(c_parser_omp_variable_list): Change ALLOW_DEREF parameter to
	MAP_LVALUE.  Support parsing of general lvalues in "map", "to" and
	"from" clauses.
	(c_parser_omp_var_list_parens): Change ALLOW_DEREF parameter to
	MAP_LVALUE.  Update call to c_parser_omp_variable_list.
	(c_parser_oacc_data_clause, c_parser_omp_clause_to,
	c_parser_omp_clause_from): Update calls to
	c_parser_omp_var_list_parens.
	* c-tree.h (c_omp_array_section_p): Add extern declaration.
	(build_omp_array_section): Add prototype.
	* c-typeck.c (c_omp_array_section_p): Add flag.
	(mark_exp_read): Support OMP_ARRAY_SECTION.
	(build_omp_array_section): Add function.
	(build_external_ref): Tweak error path for OpenMP array sections.
	(handle_omp_array_sections_1): Use OMP_ARRAY_SECTION tree code instead
	of TREE_LIST.  Handle more kinds of expressions.
	(c_finish_omp_clauses): Use OMP_ARRAY_SECTION instead of TREE_LIST.
	Check for supported expression types.

gcc/testsuite/
	* gcc.dg/gomp/bad-array-section-c-1.c: New test.
	* gcc.dg/gomp/bad-array-section-c-2.c: New test.
	* gcc.dg/gomp/bad-array-section-c-3.c: New test.
	* gcc.dg/gomp/bad-array-section-c-4.c: New test.
	* gcc.dg/gomp/bad-array-section-c-5.c: New test.
	* gcc.dg/gomp/bad-array-section-c-6.c: New test.
	* gcc.dg/gomp/bad-array-section-c-7.c: New test.
	* gcc.dg/gomp/bad-array-section-c-8.c: New test.

libgomp/
	* testsuite/libgomp.c-c++-common/ind-base-4.c: New test.
	* testsuite/libgomp.c-c++-common/unary-ptr-1.c: New test.
---
 gcc/c-family/c-pretty-print.cc                |  12 ++
 gcc/c/c-parser.cc                             | 187 +++++++++++++++---
 gcc/c/c-tree.h                                |   2 +
 gcc/c/c-typeck.cc                             | 109 ++++++++--
 .../gcc.dg/gomp/bad-array-section-c-1.c       |  16 ++
 .../gcc.dg/gomp/bad-array-section-c-2.c       |  13 ++
 .../gcc.dg/gomp/bad-array-section-c-3.c       |  24 +++
 .../gcc.dg/gomp/bad-array-section-c-4.c       |  26 +++
 .../gcc.dg/gomp/bad-array-section-c-5.c       |  15 ++
 .../gcc.dg/gomp/bad-array-section-c-6.c       |  16 ++
 .../gcc.dg/gomp/bad-array-section-c-7.c       |  26 +++
 .../gcc.dg/gomp/bad-array-section-c-8.c       |  21 ++
 .../libgomp.c-c++-common/ind-base-4.c         |  50 +++++
 .../libgomp.c-c++-common/unary-ptr-1.c        |  16 ++
 14 files changed, 486 insertions(+), 47 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/gomp/bad-array-section-c-1.c
 create mode 100644 gcc/testsuite/gcc.dg/gomp/bad-array-section-c-2.c
 create mode 100644 gcc/testsuite/gcc.dg/gomp/bad-array-section-c-3.c
 create mode 100644 gcc/testsuite/gcc.dg/gomp/bad-array-section-c-4.c
 create mode 100644 gcc/testsuite/gcc.dg/gomp/bad-array-section-c-5.c
 create mode 100644 gcc/testsuite/gcc.dg/gomp/bad-array-section-c-6.c
 create mode 100644 gcc/testsuite/gcc.dg/gomp/bad-array-section-c-7.c
 create mode 100644 gcc/testsuite/gcc.dg/gomp/bad-array-section-c-8.c
 create mode 100644 libgomp/testsuite/libgomp.c-c++-common/ind-base-4.c
 create mode 100644 libgomp/testsuite/libgomp.c-c++-common/unary-ptr-1.c

diff --git a/gcc/c-family/c-pretty-print.cc b/gcc/c-family/c-pretty-print.cc
index c99b2ceffe65..d9954bd2b951 100644
--- a/gcc/c-family/c-pretty-print.cc
+++ b/gcc/c-family/c-pretty-print.cc
@@ -1615,6 +1615,17 @@ c_pretty_printer::postfix_expression (tree e)
       pp_c_right_bracket (this);
       break;
 
+    case OMP_ARRAY_SECTION:
+      postfix_expression (TREE_OPERAND (e, 0));
+      pp_c_left_bracket (this);
+      if (TREE_OPERAND (e, 1))
+	expression (TREE_OPERAND (e, 1));
+      pp_colon (this);
+      if (TREE_OPERAND (e, 2))
+	expression (TREE_OPERAND (e, 2));
+      pp_c_right_bracket (this);
+      break;
+
     case CALL_EXPR:
       {
 	call_expr_arg_iterator iter;
@@ -2664,6 +2675,7 @@ c_pretty_printer::expression (tree e)
     case POSTINCREMENT_EXPR:
     case POSTDECREMENT_EXPR:
     case ARRAY_REF:
+    case OMP_ARRAY_SECTION:
     case CALL_EXPR:
     case COMPONENT_REF:
     case BIT_FIELD_REF:
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 15e2fd7962af..31e5c49cbe83 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -5730,6 +5730,8 @@ c_parser_braced_init (c_parser *parser, tree type, bool nested_p,
   location_t brace_loc = c_parser_peek_token (parser)->location;
   gcc_obstack_init (&braced_init_obstack);
   gcc_assert (c_parser_next_token_is (parser, CPP_OPEN_BRACE));
+  bool save_c_omp_array_section_p = c_omp_array_section_p;
+  c_omp_array_section_p = false;
   matching_braces braces;
   braces.consume_open (parser);
   if (nested_p)
@@ -5768,6 +5770,7 @@ c_parser_braced_init (c_parser *parser, tree type, bool nested_p,
 	    break;
 	}
     }
+  c_omp_array_section_p = save_c_omp_array_section_p;
   c_token *next_tok = c_parser_peek_token (parser);
   if (next_tok->type != CPP_CLOSE_BRACE)
     {
@@ -8141,6 +8144,7 @@ c_parser_conditional_expression (c_parser *parser, struct c_expr *after,
 {
   struct c_expr cond, exp1, exp2, ret;
   location_t start, cond_loc, colon_loc;
+  bool save_c_omp_array_section_p = c_omp_array_section_p;
 
   gcc_assert (!after || c_dialect_objc ());
 
@@ -8148,6 +8152,7 @@ c_parser_conditional_expression (c_parser *parser, struct c_expr *after,
 
   if (c_parser_next_token_is_not (parser, CPP_QUERY))
     return cond;
+  c_omp_array_section_p = false;
   if (cond.value != error_mark_node)
     start = cond.get_start ();
   else
@@ -8200,6 +8205,7 @@ c_parser_conditional_expression (c_parser *parser, struct c_expr *after,
       ret.set_error ();
       ret.original_code = ERROR_MARK;
       ret.original_type = NULL;
+      c_omp_array_section_p = save_c_omp_array_section_p;
       return ret;
     }
   {
@@ -8246,6 +8252,7 @@ c_parser_conditional_expression (c_parser *parser, struct c_expr *after,
     }
   set_c_expr_source_range (&ret, start, exp2.get_finish ());
   ret.m_decimal = 0;
+  c_omp_array_section_p = save_c_omp_array_section_p;
   return ret;
 }
 
@@ -9687,6 +9694,7 @@ c_parser_postfix_expression (c_parser *parser)
 	  /* A statement expression.  */
 	  tree stmt;
 	  location_t brace_loc;
+	  bool save_c_omp_array_section_p = c_omp_array_section_p;
 	  c_parser_consume_token (parser);
 	  brace_loc = c_parser_peek_token (parser)->location;
 	  c_parser_consume_token (parser);
@@ -9703,6 +9711,7 @@ c_parser_postfix_expression (c_parser *parser)
 	      expr.set_error ();
 	      break;
 	    }
+	  c_omp_array_section_p = false;
 	  stmt = c_begin_stmt_expr ();
 	  c_parser_compound_statement_nostart (parser);
 	  location_t close_loc = c_parser_peek_token (parser)->location;
@@ -9713,6 +9722,7 @@ c_parser_postfix_expression (c_parser *parser)
 	  expr.value = c_finish_stmt_expr (brace_loc, stmt);
 	  set_c_expr_source_range (&expr, loc, close_loc);
 	  mark_exp_read (expr.value);
+	  c_omp_array_section_p = save_c_omp_array_section_p;
 	}
       else
 	{
@@ -11173,7 +11183,7 @@ c_parser_postfix_expression_after_primary (c_parser *parser,
 					   struct c_expr expr)
 {
   struct c_expr orig_expr;
-  tree ident, idx;
+  tree ident, idx, len;
   location_t sizeof_arg_loc[3], comp_loc;
   tree sizeof_arg[3];
   unsigned int literal_zero_mask;
@@ -11192,16 +11202,44 @@ c_parser_postfix_expression_after_primary (c_parser *parser,
 	case CPP_OPEN_SQUARE:
 	  /* Array reference.  */
 	  c_parser_consume_token (parser);
-	  idx = c_parser_expression (parser).value;
-	  c_parser_skip_until_found (parser, CPP_CLOSE_SQUARE,
-				     "expected %<]%>");
-	  start = expr.get_start ();
-	  finish = parser->tokens_buf[0].location;
-	  expr.value = build_array_ref (op_loc, expr.value, idx);
-	  set_c_expr_source_range (&expr, start, finish);
-	  expr.original_code = ERROR_MARK;
-	  expr.original_type = NULL;
-	  expr.m_decimal = 0;
+	  idx = len = NULL_TREE;
+	  if (!c_omp_array_section_p
+	      || c_parser_next_token_is_not (parser, CPP_COLON))
+	    idx = c_parser_expression (parser).value;
+
+	  if (c_omp_array_section_p
+	      && c_parser_next_token_is (parser, CPP_COLON))
+	    {
+	      c_parser_consume_token (parser);
+	      if (c_parser_next_token_is_not (parser, CPP_CLOSE_SQUARE))
+		len = c_parser_expression (parser).value;
+
+	      c_parser_skip_until_found (parser, CPP_CLOSE_SQUARE,
+					 "expected %<]%>");
+
+	     /* NOTE: We are reusing using the type of the whole array as the
+		type of the array section here, which isn't necessarily
+		entirely correct.  Might need revisiting.  */
+	      start = expr.get_start ();
+	      finish = parser->tokens_buf[0].location;
+	      expr.value = build_omp_array_section (op_loc, expr.value, idx,
+						    len);
+	      set_c_expr_source_range (&expr, start, finish);
+	      expr.original_code = ERROR_MARK;
+	      expr.original_type = NULL;
+	    }
+	  else
+	    {
+	      c_parser_skip_until_found (parser, CPP_CLOSE_SQUARE,
+					 "expected %<]%>");
+	      start = expr.get_start ();
+	      finish = parser->tokens_buf[0].location;
+	      expr.value = build_array_ref (op_loc, expr.value, idx);
+	      set_c_expr_source_range (&expr, start, finish);
+	      expr.original_code = ERROR_MARK;
+	      expr.original_type = NULL;
+	      expr.m_decimal = 0;
+	    }
 	  break;
 	case CPP_OPEN_PAREN:
 	  /* Function call.  */
@@ -11484,6 +11522,8 @@ c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
   vec<tree, va_gc> *orig_types;
   struct c_expr expr;
   unsigned int idx = 0;
+  bool save_c_omp_array_section_p = c_omp_array_section_p;
+  c_omp_array_section_p = false;
 
   ret = make_tree_vector ();
   if (p_orig_types == NULL)
@@ -11537,6 +11577,7 @@ c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
     }
   if (orig_types)
     *p_orig_types = orig_types;
+  c_omp_array_section_p = save_c_omp_array_section_p;
   return ret;
 }
 \f
@@ -13729,7 +13770,7 @@ static tree
 c_parser_omp_variable_list (c_parser *parser,
 			    location_t clause_loc,
 			    enum omp_clause_code kind, tree list,
-			    bool allow_deref = false)
+			    bool map_lvalue = false)
 {
   auto_vec<omp_dim> dims;
   bool array_section_p;
@@ -13739,6 +13780,8 @@ c_parser_omp_variable_list (c_parser *parser,
 
   while (1)
     {
+      tree t = NULL_TREE;
+
       if (kind == OMP_CLAUSE_DEPEND || kind == OMP_CLAUSE_AFFINITY)
 	{
 	  if (c_parser_next_token_is_not (parser, CPP_NAME)
@@ -13819,8 +13862,97 @@ c_parser_omp_variable_list (c_parser *parser,
 	  parser->tokens = tokens.address ();
 	  parser->tokens_avail = tokens.length ();
 	}
+      else if (map_lvalue
+	       && (kind == OMP_CLAUSE_MAP
+		   || kind == OMP_CLAUSE_TO
+		   || kind == OMP_CLAUSE_FROM))
+	{
+	  location_t loc = c_parser_peek_token (parser)->location;
+	  bool save_c_omp_array_section_p = c_omp_array_section_p;
+	  c_omp_array_section_p = true;
+	  c_expr expr = c_parser_expr_no_commas (parser, NULL);
+	  if (expr.value != error_mark_node)
+	    mark_exp_read (expr.value);
+	  c_omp_array_section_p = save_c_omp_array_section_p;
+	  tree decl = expr.value;
 
-      tree t = NULL_TREE;
+	 /* This code rewrites a parsed expression containing various tree
+	    codes used to represent array accesses into a more uniform nest of
+	    OMP_ARRAY_SECTION nodes before it is processed by
+	    c-typeck.cc:handle_omp_array_sections_1.  It might be more
+	    efficient to move this logic to that function instead, analysing
+	    the parsed expression directly rather than this preprocessed
+	    form.  (See also equivalent code in cp/parser.cc,
+	    cp/semantics.cc).  */
+	  dims.truncate (0);
+	  if (TREE_CODE (decl) == OMP_ARRAY_SECTION)
+	    {
+	      while (TREE_CODE (decl) == OMP_ARRAY_SECTION)
+		{
+		  tree low_bound = TREE_OPERAND (decl, 1);
+		  tree length = TREE_OPERAND (decl, 2);
+		  dims.safe_push (omp_dim (low_bound, length, loc, false));
+		  decl = TREE_OPERAND (decl, 0);
+		}
+
+	      while (TREE_CODE (decl) == ARRAY_REF
+		     || TREE_CODE (decl) == INDIRECT_REF
+		     || TREE_CODE (decl) == COMPOUND_EXPR)
+		{
+		  if (TREE_CODE (decl) == COMPOUND_EXPR)
+		    {
+		      decl = TREE_OPERAND (decl, 1);
+		      STRIP_NOPS (decl);
+		    }
+		  else if (TREE_CODE (decl) == INDIRECT_REF)
+		    {
+		      dims.safe_push (omp_dim (integer_zero_node,
+					       integer_one_node, loc, true));
+		      decl = TREE_OPERAND (decl, 0);
+		    }
+		  else  /* ARRAY_REF. */
+		    {
+		      tree index = TREE_OPERAND (decl, 1);
+		      dims.safe_push (omp_dim (index, integer_one_node, loc,
+					       true));
+		      decl = TREE_OPERAND (decl, 0);
+		    }
+		}
+
+	      for (int i = dims.length () - 1; i >= 0; i--)
+		decl = build_omp_array_section (loc,  decl, dims[i].low_bound,
+						dims[i].length);
+	    }
+	  else if (TREE_CODE (decl) == INDIRECT_REF)
+	    {
+	      /* Turn *foo into the representation previously used for
+		 foo[0].  */
+	      decl = TREE_OPERAND (decl, 0);
+	      STRIP_NOPS (decl);
+
+	      decl = build_omp_array_section (loc, decl, integer_zero_node,
+					      integer_one_node);
+	    }
+	  else if (TREE_CODE (decl) == ARRAY_REF)
+	    {
+	      tree idx = TREE_OPERAND (decl, 1);
+
+	      decl = TREE_OPERAND (decl, 0);
+	      STRIP_NOPS (decl);
+
+	      decl = build_omp_array_section (loc, decl, idx, integer_one_node);
+	    }
+	  else if (TREE_CODE (decl) == NON_LVALUE_EXPR
+		   || CONVERT_EXPR_P (decl))
+	    decl = TREE_OPERAND (decl, 0);
+
+	  tree u = build_omp_clause (clause_loc, kind);
+	  OMP_CLAUSE_DECL (u) = decl;
+	  OMP_CLAUSE_CHAIN (u) = list;
+	  list = u;
+
+	  goto next_item;
+	}
 
       if (c_parser_next_token_is (parser, CPP_NAME)
 	  && c_parser_peek_token (parser)->id_kind == C_ID_ID)
@@ -13871,8 +14003,7 @@ c_parser_omp_variable_list (c_parser *parser,
 	    case OMP_CLAUSE_TO:
 	    start_component_ref:
 	      while (c_parser_next_token_is (parser, CPP_DOT)
-		     || (allow_deref
-			 && c_parser_next_token_is (parser, CPP_DEREF)))
+		     || c_parser_next_token_is (parser, CPP_DEREF))
 		{
 		  location_t op_loc = c_parser_peek_token (parser)->location;
 		  location_t arrow_loc = UNKNOWN_LOCATION;
@@ -13973,9 +14104,7 @@ c_parser_omp_variable_list (c_parser *parser,
 		       || kind == OMP_CLAUSE_TO)
 		      && !array_section_p
 		      && (c_parser_next_token_is (parser, CPP_DOT)
-			  || (allow_deref
-			      && c_parser_next_token_is (parser,
-							 CPP_DEREF))))
+			  || c_parser_next_token_is (parser, CPP_DEREF)))
 		    {
 		      for (unsigned i = 0; i < dims.length (); i++)
 			{
@@ -13987,7 +14116,9 @@ c_parser_omp_variable_list (c_parser *parser,
 		    }
 		  else
 		    for (unsigned i = 0; i < dims.length (); i++)
-		      t = tree_cons (dims[i].low_bound, dims[i].length, t);
+		      t = build_omp_array_section (clause_loc, t,
+						   dims[i].low_bound,
+						   dims[i].length);
 		}
 
 	      if ((kind == OMP_CLAUSE_DEPEND || kind == OMP_CLAUSE_AFFINITY)
@@ -14035,6 +14166,8 @@ c_parser_omp_variable_list (c_parser *parser,
 	  parser->tokens = &parser->tokens_buf[0];
 	  parser->tokens_avail = tokens_avail;
 	}
+
+    next_item:
       if (c_parser_next_token_is_not (parser, CPP_COMMA))
 	break;
 
@@ -14051,7 +14184,7 @@ c_parser_omp_variable_list (c_parser *parser,
 
 static tree
 c_parser_omp_var_list_parens (c_parser *parser, enum omp_clause_code kind,
-			      tree list, bool allow_deref = false)
+			      tree list, bool map_lvalue = false)
 {
   /* The clauses location.  */
   location_t loc = c_parser_peek_token (parser)->location;
@@ -14059,7 +14192,7 @@ c_parser_omp_var_list_parens (c_parser *parser, enum omp_clause_code kind,
   matching_parens parens;
   if (parens.require_open (parser))
     {
-      list = c_parser_omp_variable_list (parser, loc, kind, list, allow_deref);
+      list = c_parser_omp_variable_list (parser, loc, kind, list, map_lvalue);
       parens.skip_until_found_close (parser);
     }
   return list;
@@ -14128,7 +14261,7 @@ c_parser_oacc_data_clause (c_parser *parser, pragma_omp_clause c_kind,
       gcc_unreachable ();
     }
   tree nl, c;
-  nl = c_parser_omp_var_list_parens (parser, OMP_CLAUSE_MAP, list, true);
+  nl = c_parser_omp_var_list_parens (parser, OMP_CLAUSE_MAP, list);
 
   for (c = nl; c != list; c = OMP_CLAUSE_CHAIN (c))
     OMP_CLAUSE_SET_MAP_KIND (c, kind);
@@ -15711,13 +15844,15 @@ c_parser_omp_clause_reduction (c_parser *parser, enum omp_clause_code kind,
 	  for (c = nl; c != list; c = OMP_CLAUSE_CHAIN (c))
 	    {
 	      tree d = OMP_CLAUSE_DECL (c), type;
-	      if (TREE_CODE (d) != TREE_LIST)
+	      if (TREE_CODE (d) != OMP_ARRAY_SECTION)
 		type = TREE_TYPE (d);
 	      else
 		{
 		  int cnt = 0;
 		  tree t;
-		  for (t = d; TREE_CODE (t) == TREE_LIST; t = TREE_CHAIN (t))
+		  for (t = d;
+		      TREE_CODE (t) == OMP_ARRAY_SECTION;
+		      t = TREE_OPERAND (t, 0))
 		    cnt++;
 		  type = TREE_TYPE (t);
 		  while (cnt > 0)
@@ -17428,7 +17563,7 @@ c_parser_omp_clause_device_type (c_parser *parser, tree list)
 static tree
 c_parser_omp_clause_to (c_parser *parser, tree list)
 {
-  return c_parser_omp_var_list_parens (parser, OMP_CLAUSE_TO, list, true);
+  return c_parser_omp_var_list_parens (parser, OMP_CLAUSE_TO, list);
 }
 
 /* OpenMP 4.0:
@@ -17437,7 +17572,7 @@ c_parser_omp_clause_to (c_parser *parser, tree list)
 static tree
 c_parser_omp_clause_from (c_parser *parser, tree list)
 {
-  return c_parser_omp_var_list_parens (parser, OMP_CLAUSE_FROM, list, true);
+  return c_parser_omp_var_list_parens (parser, OMP_CLAUSE_FROM, list);
 }
 
 /* OpenMP 4.0:
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index c287124c990b..075518e59e81 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -722,6 +722,7 @@ extern int in_alignof;
 extern int in_sizeof;
 extern int in_typeof;
 extern bool c_in_omp_for;
+extern bool c_omp_array_section_p;
 
 extern tree c_last_sizeof_arg;
 extern location_t c_last_sizeof_loc;
@@ -751,6 +752,7 @@ extern tree composite_type (tree, tree);
 extern tree build_component_ref (location_t, tree, tree, location_t,
 				 location_t);
 extern tree build_array_ref (location_t, tree, tree);
+extern tree build_omp_array_section (location_t, tree, tree, tree);
 extern tree build_external_ref (location_t, tree, bool, tree *);
 extern void pop_maybe_used (bool);
 extern struct c_expr c_expr_sizeof_expr (location_t, struct c_expr);
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index a4f3b855f6e7..b2d4679e2a08 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -75,6 +75,9 @@ int in_typeof;
 /* True when parsing OpenMP loop expressions.  */
 bool c_in_omp_for;
 
+/* True when parsing OpenMP map clause.  */
+bool c_omp_array_section_p;
+
 /* The argument of last parsed sizeof expression, only to be tested
    if expr.original_code == SIZEOF_EXPR.  */
 tree c_last_sizeof_arg;
@@ -2037,6 +2040,13 @@ mark_exp_read (tree exp)
     case C_MAYBE_CONST_EXPR:
       mark_exp_read (TREE_OPERAND (exp, 1));
       break;
+    case OMP_ARRAY_SECTION:
+      mark_exp_read (TREE_OPERAND (exp, 0));
+      if (TREE_OPERAND (exp, 1))
+	mark_exp_read (TREE_OPERAND (exp, 1));
+      if (TREE_OPERAND (exp, 2))
+	mark_exp_read (TREE_OPERAND (exp, 2));
+      break;
     default:
       break;
     }
@@ -2914,6 +2924,53 @@ build_array_ref (location_t loc, tree array, tree index)
       return ret;
     }
 }
+
+/* Build an OpenMP array section reference, creating an exact type for the
+   resulting expression based on the element type and bounds if possible.  If
+   we have variable bounds, create an incomplete array type for the result
+   instead.  */
+
+tree
+build_omp_array_section (location_t loc, tree array, tree index, tree length)
+{
+  tree idxtype;
+
+  if (index != NULL_TREE
+      && length != NULL_TREE
+      && TREE_CODE (index) == INTEGER_CST
+      && TREE_CODE (length) == INTEGER_CST)
+    {
+      tree low = fold_convert (sizetype, index);
+      tree high = fold_convert (sizetype, length);
+      high = size_binop (PLUS_EXPR, low, high);
+      high = size_binop (MINUS_EXPR, high, size_one_node);
+      idxtype = build_range_type (sizetype, low, high);
+    }
+  else if ((index == NULL_TREE || integer_zerop (index))
+	   && length != NULL_TREE
+	   && TREE_CODE (length) == INTEGER_CST)
+    idxtype = build_index_type (length);
+  else
+    idxtype = NULL_TREE;
+
+  tree type = TREE_TYPE (array);
+  gcc_assert (type);
+
+  tree sectype, eltype = TREE_TYPE (type);
+
+  /* It's not an array or pointer type.  Just reuse the type of the original
+     expression as the type of the array section (an error will be raised
+     anyway, later).  */
+  if (eltype == NULL_TREE
+      || error_operand_p (eltype)
+      || error_operand_p (idxtype))
+    sectype = TREE_TYPE (array);
+  else
+    sectype = build_array_type (eltype, idxtype);
+
+  return build3_loc (loc, OMP_ARRAY_SECTION, sectype, array, index, length);
+}
+
 \f
 /* Build an external reference to identifier ID.  FUN indicates
    whether this will be used for a function call.  LOC is the source
@@ -2953,7 +3010,11 @@ build_external_ref (location_t loc, tree id, bool fun, tree *type)
       return error_mark_node;
     }
 
-  if (TREE_TYPE (ref) == error_mark_node)
+  /* For an OpenMP map clause, we can get better diagnostics for decls with
+     unmappable types if we return the decl with an error_mark_node type,
+     rather than returning error_mark_node for the decl itself.  */
+  if (TREE_TYPE (ref) == error_mark_node
+      && !c_omp_array_section_p)
     return error_mark_node;
 
   if (TREE_UNAVAILABLE (ref))
@@ -13635,7 +13696,7 @@ handle_omp_array_sections_1 (tree c, tree t, vec<tree> &types,
 {
   tree ret, low_bound, length, type;
   bool openacc = (ort & C_ORT_ACC) != 0;
-  if (TREE_CODE (t) != TREE_LIST)
+  if (TREE_CODE (t) != OMP_ARRAY_SECTION)
     {
       if (error_operand_p (t))
 	return error_mark_node;
@@ -13660,7 +13721,9 @@ handle_omp_array_sections_1 (tree c, tree t, vec<tree> &types,
 	t = ai.unconverted_ref_origin ();
       if (t == error_mark_node)
 	return error_mark_node;
-      if (!VAR_P (t) && TREE_CODE (t) != PARM_DECL)
+      if (!VAR_P (t)
+	  && (ort == C_ORT_ACC || !EXPR_P (t))
+	  && TREE_CODE (t) != PARM_DECL)
 	{
 	  if (DECL_P (t))
 	    error_at (OMP_CLAUSE_LOCATION (c),
@@ -13708,14 +13771,14 @@ handle_omp_array_sections_1 (tree c, tree t, vec<tree> &types,
       return ret;
     }
 
-  ret = handle_omp_array_sections_1 (c, TREE_CHAIN (t), types,
+  ret = handle_omp_array_sections_1 (c, TREE_OPERAND (t, 0), types,
 				     maybe_zero_len, first_non_one, ort);
   if (ret == error_mark_node || ret == NULL_TREE)
     return ret;
 
   type = TREE_TYPE (ret);
-  low_bound = TREE_PURPOSE (t);
-  length = TREE_VALUE (t);
+  low_bound = TREE_OPERAND (t, 1);
+  length = TREE_OPERAND (t, 2);
 
   if (low_bound == error_mark_node || length == error_mark_node)
     return error_mark_node;
@@ -13908,7 +13971,7 @@ handle_omp_array_sections_1 (tree c, tree t, vec<tree> &types,
 	  tree lb = save_expr (low_bound);
 	  if (lb != low_bound)
 	    {
-	      TREE_PURPOSE (t) = lb;
+	      TREE_OPERAND (t, 1) = lb;
 	      low_bound = lb;
 	    }
 	}
@@ -13939,14 +14002,15 @@ handle_omp_array_sections_1 (tree c, tree t, vec<tree> &types,
 	 array-section-subscript, the array section could be non-contiguous.  */
       if (OMP_CLAUSE_CODE (c) != OMP_CLAUSE_DEPEND
 	  && OMP_CLAUSE_CODE (c) != OMP_CLAUSE_AFFINITY
-	  && TREE_CODE (TREE_CHAIN (t)) == TREE_LIST)
+	  && TREE_CODE (TREE_OPERAND (t, 0)) == OMP_ARRAY_SECTION)
 	{
 	  /* If any prior dimension has a non-one length, then deem this
 	     array section as non-contiguous.  */
-	  for (tree d = TREE_CHAIN (t); TREE_CODE (d) == TREE_LIST;
-	       d = TREE_CHAIN (d))
+	  for (tree d = TREE_OPERAND (t, 0);
+	       TREE_CODE (d) == OMP_ARRAY_SECTION;
+	       d = TREE_OPERAND (d, 0))
 	    {
-	      tree d_length = TREE_VALUE (d);
+	      tree d_length = TREE_OPERAND (d, 2);
 	      if (d_length == NULL_TREE || !integer_onep (d_length))
 		{
 		  error_at (OMP_CLAUSE_LOCATION (c),
@@ -13969,7 +14033,7 @@ handle_omp_array_sections_1 (tree c, tree t, vec<tree> &types,
   tree lb = save_expr (low_bound);
   if (lb != low_bound)
     {
-      TREE_PURPOSE (t) = lb;
+      TREE_OPERAND (t, 1) = lb;
       low_bound = lb;
     }
   ret = build_array_ref (OMP_CLAUSE_LOCATION (c), ret, low_bound);
@@ -14032,10 +14096,10 @@ handle_omp_array_sections (tree c, enum c_omp_region_type ort)
 	maybe_zero_len = true;
 
       for (i = num, t = OMP_CLAUSE_DECL (c); i > 0;
-	   t = TREE_CHAIN (t))
+	   t = TREE_OPERAND (t, 0))
 	{
-	  tree low_bound = TREE_PURPOSE (t);
-	  tree length = TREE_VALUE (t);
+	  tree low_bound = TREE_OPERAND (t, 1);
+	  tree length = TREE_OPERAND (t, 2);
 
 	  i--;
 	  if (low_bound
@@ -14441,8 +14505,8 @@ c_oacc_check_attachments (tree c)
     {
       tree t = OMP_CLAUSE_DECL (c);
 
-      while (TREE_CODE (t) == TREE_LIST)
-	t = TREE_CHAIN (t);
+      while (TREE_CODE (t) == OMP_ARRAY_SECTION)
+	t = TREE_OPERAND (t, 0);
 
       if (TREE_CODE (TREE_TYPE (t)) != POINTER_TYPE)
 	{
@@ -14550,7 +14614,7 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	case OMP_CLAUSE_TASK_REDUCTION:
 	  need_implicitly_determined = true;
 	  t = OMP_CLAUSE_DECL (c);
-	  if (TREE_CODE (t) == TREE_LIST)
+	  if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	    {
 	      if (handle_omp_array_sections (c, ort))
 		{
@@ -15171,7 +15235,7 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	    }
 	  else
 	    last_iterators = NULL_TREE;
-	  if (TREE_CODE (t) == TREE_LIST)
+	  if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	    {
 	      if (handle_omp_array_sections (c, ort))
 		remove = true;
@@ -15281,7 +15345,7 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	    auto_vec<omp_addr_token *, 10> addr_tokens;
 
 	    t = OMP_CLAUSE_DECL (c);
-	    if (TREE_CODE (t) == TREE_LIST)
+	    if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	      {
 		grp_start_p = pc;
 		grp_sentinel = OMP_CLAUSE_CHAIN (c);
@@ -15449,6 +15513,9 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 
 	    if (!VAR_P (t) && TREE_CODE (t) != PARM_DECL)
 	      {
+		if (ort != C_ORT_ACC && EXPR_P (t))
+		  break;
+
 		error_at (OMP_CLAUSE_LOCATION (c),
 			  "%qE is not a variable in %qs clause", t,
 			  omp_clause_code_name[OMP_CLAUSE_CODE (c)]);
@@ -15675,7 +15742,7 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 
 	case OMP_CLAUSE_HAS_DEVICE_ADDR:
 	  t = OMP_CLAUSE_DECL (c);
-	  if (TREE_CODE (t) == TREE_LIST)
+	  if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	    {
 	      if (handle_omp_array_sections (c, ort))
 		remove = true;
diff --git a/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-1.c b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-1.c
new file mode 100644
index 000000000000..a2226ebf6429
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-1.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+
+int foo (int *ptr);
+
+int main()
+{
+  int arr[20];
+  /* Reject array section as function argument.  */
+#pragma omp target map(foo(arr[3:5]))
+/* { dg-error {expected '\]' before ':' token} "" { target *-*-* } .-1 } */
+/* { dg-warning {passing argument 1 of 'foo' makes pointer from integer without a cast} "" { target *-*-* } .-2 } */
+/* { dg-message {sorry, unimplemented: unsupported map expression} "" { target *-*-* } .-3 } */
+  { }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-2.c b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-2.c
new file mode 100644
index 000000000000..449487ad55d6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-2.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+
+int main()
+{
+  int arr[20];
+  /* Reject array section in statement expression.  */
+#pragma omp target map( ({ int x = 5; arr[0:x]; }) )
+/* { dg-error {expected '\]' before ':' token} "" { target *-*-* } .-1 } */
+/* { dg-message {sorry, unimplemented: unsupported map expression} "" { target *-*-* } .-2 } */
+  { }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-3.c b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-3.c
new file mode 100644
index 000000000000..8be15ced8c06
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-3.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+
+struct S {
+  int *ptr;
+};
+
+int main()
+{
+  int arr[20];
+
+  /* Reject array section in compound initialiser.  */
+#pragma omp target map( (struct S) { .ptr = (int *) arr[5:5] } )
+/* { dg-error {expected '\]' before ':' token} "" { target *-*-* } .-1 } */
+/* { dg-warning {cast to pointer from integer of different size} "" { target *-*-* } .-2 } */
+/* { dg-message {sorry, unimplemented: unsupported map expression} "" { target *-*-* } .-3 } */
+  { }
+
+  /* ...and this is unsupported too (probably not useful anyway).  */
+#pragma omp target map( (struct S) { .ptr = &arr[5] } )
+/* { dg-message {sorry, unimplemented: unsupported map expression} "" { target *-*-* } .-1 } */
+  { }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-4.c b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-4.c
new file mode 100644
index 000000000000..b78cdfc8a13e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-4.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+
+int x;
+
+int main()
+{
+  int arr[20];
+  int *ptr;
+  /* "arr[1:10]" looks like it might be an expression of array type, hence
+     able to be indexed (again).  This isn't allowed, though.  */
+#pragma omp target map(arr[1:10][2])
+/* { dg-error {'arr\[1\]' does not have pointer or array type} "" { target *-*-* } .-1 } */
+  { }
+#pragma omp target map(arr[1:x][2])
+/* { dg-error {'arr\[1\]' does not have pointer or array type} "" { target *-*-* } .-1 } */
+  { }
+  /* ...and nor is this.  */
+#pragma omp target map(ptr[1:10][2])
+/* { dg-error {'\*\(ptr \+ [0-9]+\)' does not have pointer or array type} "" { target *-*-* } .-1 } */
+  { }
+#pragma omp target map(ptr[1:x][2])
+/* { dg-error {'\*\(ptr \+ [0-9]+\)' does not have pointer or array type} "" { target *-*-* } .-1 } */
+  { }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-5.c b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-5.c
new file mode 100644
index 000000000000..ae343464a19a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-5.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+
+int partly = 0;
+
+int main()
+{
+  int arr[20];
+#pragma omp target map(partly ? arr[5:5] : arr)
+/* { dg-error {expected '\]' before ':' token} "" { target *-*-* } .-1 } */
+/* { dg-warning {pointer/integer type mismatch in conditional expression} "" { target *-*-* } .-2 } */
+/* { dg-message {sorry, unimplemented: unsupported map expression} "" { target *-*-* } .-3 } */
+  { }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-6.c b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-6.c
new file mode 100644
index 000000000000..bfca4f0fca3f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-6.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+
+int x;
+
+int main()
+{
+  int arr[20];
+#pragma omp target map(arr[5:5] * 2)
+/* { dg-error {invalid operands to binary \*} "" { target *-*-* } .-1 } */
+  { }
+#pragma omp target map(arr[x:5] * 2)
+/* { dg-error {invalid operands to binary \*} "" { target *-*-* } .-1 } */
+  { }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-7.c b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-7.c
new file mode 100644
index 000000000000..1fd9e2b383a4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-7.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+
+int x;
+
+struct T {
+  int arr[20];
+};
+
+struct S {
+  struct T *tvec;
+};
+
+int main()
+{
+  struct S *s;
+  /* You can't use an array section like this.  Make sure sensible errors are
+     reported.  */
+#pragma omp target map(s->tvec[3:5].arr[0:20])
+/* { dg-error {'\(struct T \*\)&s->tvec\[3:5\]' is a pointer; did you mean to use '->'\?} "" { target *-*-* } .-1 } */
+  { }
+#pragma omp target map(s->tvec[5:x].arr[0:20])
+/* { dg-error {'\(struct T \*\)&s->tvec\[5:x\]' is a pointer; did you mean to use '->'\?} "" { target *-*-* } .-1 } */
+  { }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-8.c b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-8.c
new file mode 100644
index 000000000000..f90eca1fa9fa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gomp/bad-array-section-c-8.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+
+int x;
+
+int main()
+{
+  int arr1[40];
+  int arr2[40];
+#pragma omp target map(arr1[arr2[4:5]:arr2[6:7]])
+/* { dg-error {low bound 'arr2\[4:5\]' of array section does not have integral type} "" { target *-*-* } .-1 } */
+  { }
+#pragma omp target map(arr1[arr2[:1]:arr2[6:1]])
+/* { dg-error {low bound 'arr2\[:1\]' of array section does not have integral type} "" { target *-*-* } .-1 } */
+  { }
+#pragma omp target map(arr1[x:arr2[6:1]])
+/* { dg-error {length 'arr2\[6:1\]' of array section does not have integral type} "" { target *-*-* } .-1 } */
+  { }
+
+  return 0;
+}
+
diff --git a/libgomp/testsuite/libgomp.c-c++-common/ind-base-4.c b/libgomp/testsuite/libgomp.c-c++-common/ind-base-4.c
new file mode 100644
index 000000000000..91549ac4d245
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c-c++-common/ind-base-4.c
@@ -0,0 +1,50 @@
+// { dg-do run }
+// { dg-options "-fopenmp" }
+
+#include <assert.h>
+#include <stdlib.h>
+
+typedef struct
+{
+  int x[10];
+} S;
+
+typedef struct
+{
+  S ***s;
+} T;
+
+typedef struct
+{
+  T **t;
+} U;
+
+void
+foo (void)
+{
+  U *u = (U *) malloc (sizeof (U));
+  T *real_t = (T *) malloc (sizeof (T));
+  S *real_s = (S *) malloc (sizeof (S));
+  T **t_pp = &real_t;
+  S **s_pp = &real_s;
+  S ***s_ppp = &s_pp;
+  u->t = t_pp;
+  (*u->t)->s = s_ppp;
+  for (int i = 0; i < 10; i++)
+    (**((*u->t)->s))->x[i] = 0;
+#pragma omp target map(u->t, *u->t, (*u->t)->s, *(*u->t)->s, **(*u->t)->s, \
+		       (**(*u->t)->s)->x[0:10])
+  for (int i = 0; i < 10; i++)
+    (**((*u->t)->s))->x[i] = i * 3;
+  for (int i = 0; i < 10; i++)
+    assert ((**((*u->t)->s))->x[i] == i * 3);
+  free (real_s);
+  free (real_t);
+  free (u);
+}
+
+int main (int argc, char *argv[])
+{
+  foo ();
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c-c++-common/unary-ptr-1.c b/libgomp/testsuite/libgomp.c-c++-common/unary-ptr-1.c
new file mode 100644
index 000000000000..3623b2695763
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c-c++-common/unary-ptr-1.c
@@ -0,0 +1,16 @@
+#include <assert.h>
+
+int main (int argc, char *argv[])
+{
+  int y = 0;
+  int *x = &y;
+
+#pragma omp target map(*x)
+  {
+    (*x)++;
+  }
+
+  assert (y == 1);
+
+  return 0;
+}
-- 
2.29.2


  parent reply	other threads:[~2022-12-23 12:14 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-12-23 12:12 [PATCH v6 00/11] OpenMP: C/C++ lvalue parsing, C/C++/Fortran "declare mapper" support Julian Brown
2022-12-23 12:12 ` [PATCH v6 01/11] OpenMP/OpenACC: Reindent TO/FROM/_CACHE_ stanza in {c_}finish_omp_clause Julian Brown
2022-12-23 12:12 ` [PATCH v6 02/11] OpenMP/OpenACC: Rework clause expansion and nested struct handling Julian Brown
2022-12-23 12:12 ` [PATCH v6 03/11] OpenMP/OpenACC: Refine condition for when map clause expansion happens Julian Brown
2022-12-23 12:12 ` [PATCH v6 04/11] OpenMP: implicitly map base pointer for array-section pointer components Julian Brown
2022-12-23 12:12 ` [PATCH v6 05/11] OpenMP: Pointers and member mappings Julian Brown
2022-12-23 12:12 ` [PATCH v6 06/11] OpenMP/OpenACC: Unordered/non-constant component offset runtime diagnostic Julian Brown
2022-12-23 12:13 ` [PATCH v6 07/11] OpenMP: lvalue parsing for map/to/from clauses (C++) Julian Brown
2022-12-23 12:13 ` [PATCH v6 08/11] OpenMP: C++ "declare mapper" support Julian Brown
2022-12-23 12:13 ` Julian Brown [this message]
2022-12-23 12:13 ` [PATCH v6 10/11] OpenMP: Support OpenMP 5.0 "declare mapper" directives for C Julian Brown
2022-12-23 12:13 ` [PATCH v6 11/11] OpenMP: Fortran "!$omp declare mapper" support Julian Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cb79a146432898f400ad52e364b0c7d27098f0ca.1671796516.git.julian@codesourcery.com \
    --to=julian@codesourcery.com \
    --cc=fortran@gcc.gnu.org \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jakub@redhat.com \
    --cc=thomas@codesourcery.com \
    --cc=tobias@codesourcery.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).