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@codesourcery.com>, <jakub@redhat.com>
Subject: [PATCH 2/5] OpenMP: Allow complete replacement of clause during map/to/from expansion
Date: Wed, 6 Sep 2023 02:34:31 -0700	[thread overview]
Message-ID: <463243d6ef75f09ad961f0f8044f82d1af2f32da.1693991759.git.julian@codesourcery.com> (raw)
In-Reply-To: <cover.1693991758.git.julian@codesourcery.com>

At present, map/to/from clauses on OpenMP "target" directives may be
expanded into several mapping nodes if they describe array sections with
pointer or reference bases, or similar.  This patch allows the original
clause to be replaced during that expansion, mostly by passing the list
pointer to the node to various functions rather than the node itself.

This is needed by the following patch. There shouldn't be any functional
changes introduced by this patch itself.

2023-09-05  Julian Brown  <julian@codesourcery.com>

gcc/c-family/
	* c-common.h (expand_array_base, expand_component_selector,
	expand_map_clause): Adjust member declarations.
	* c-omp.cc (omp_expand_access_chain): Pass and return pointer to
	clause.
	(c_omp_address_inspector::expand_array_base): Likewise.
	(c_omp_address_inspector::expand_component_selector): Likewise.
	(c_omp_address_inspector::expand_map_clause): Likewise.

gcc/c/
	* c-typeck.cc (handle_omp_array_sections): Pass pointer to clause to
	process instead of clause.
	(c_finish_omp_clauses): Update calls to handle_omp_array_sections.
	Handle cases where initial clause might be replaced.

gcc/cp/
	* semantics.cc (handle_omp_array_sections): Pass pointer to clause
	instead of clause.  Add PNEXT return parameter for next clause in list
	to process.
	(finish_omp_clauses): Update calls to handle_omp_array_sections.
	Handle cases where initial clause might be replaced.
---
 gcc/c-family/c-common.h | 12 +++----
 gcc/c-family/c-omp.cc   | 75 +++++++++++++++++++++--------------------
 gcc/c/c-typeck.cc       | 32 +++++++++++-------
 gcc/cp/semantics.cc     | 37 +++++++++++++-------
 4 files changed, 88 insertions(+), 68 deletions(-)

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index b4e49c7d0cbc..e2b85d394d32 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1383,12 +1383,12 @@ public:
 
   bool maybe_zero_length_array_section (tree);
 
-  tree expand_array_base (tree, vec<omp_addr_token *> &, tree, unsigned *,
-			  c_omp_region_type, bool);
-  tree expand_component_selector (tree, vec<omp_addr_token *> &, tree,
-				  unsigned *);
-  tree expand_map_clause (tree, tree, vec<omp_addr_token *> &,
-			  c_omp_region_type);
+  tree * expand_array_base (tree *, vec<omp_addr_token *> &, tree, unsigned *,
+			    c_omp_region_type, bool);
+  tree * expand_component_selector (tree *, vec<omp_addr_token *> &, tree,
+				    unsigned *);
+  tree * expand_map_clause (tree *, tree, vec<omp_addr_token *> &,
+			    c_omp_region_type);
 };
 
 enum c_omp_directive_kind {
diff --git a/gcc/c-family/c-omp.cc b/gcc/c-family/c-omp.cc
index b73f9682f460..3c6b79107edc 100644
--- a/gcc/c-family/c-omp.cc
+++ b/gcc/c-family/c-omp.cc
@@ -3331,11 +3331,12 @@ c_omp_address_inspector::maybe_zero_length_array_section (tree clause)
    expression types here, because e.g. you can't have an array of
    references.  See also gimplify.cc:omp_expand_access_chain.  */
 
-static tree
-omp_expand_access_chain (tree c, tree expr, vec<omp_addr_token *> &addr_tokens,
-			 unsigned *idx)
+static tree *
+omp_expand_access_chain (tree *pc, tree expr,
+			 vec<omp_addr_token *> &addr_tokens, unsigned *idx)
 {
   using namespace omp_addr_tokenizer;
+  tree c = *pc;
   location_t loc = OMP_CLAUSE_LOCATION (c);
   unsigned i = *idx;
   tree c2 = NULL_TREE;
@@ -3373,35 +3374,36 @@ omp_expand_access_chain (tree c, tree expr, vec<omp_addr_token *> &addr_tokens,
       break;
 
     default:
-      return error_mark_node;
+      return NULL;
     }
 
   if (c2)
     {
       OMP_CLAUSE_CHAIN (c2) = OMP_CLAUSE_CHAIN (c);
       OMP_CLAUSE_CHAIN (c) = c2;
-      c = c2;
+      pc = &OMP_CLAUSE_CHAIN (c);
     }
 
   *idx = ++i;
 
   if (i < addr_tokens.length ()
       && addr_tokens[i]->type == ACCESS_METHOD)
-    return omp_expand_access_chain (c, expr, addr_tokens, idx);
+    return omp_expand_access_chain (pc, expr, addr_tokens, idx);
 
-  return c;
+  return pc;
 }
 
 /* Translate "array_base_decl access_method" to OMP mapping clauses.  */
 
-tree
-c_omp_address_inspector::expand_array_base (tree c,
+tree *
+c_omp_address_inspector::expand_array_base (tree *pc,
 					    vec<omp_addr_token *> &addr_tokens,
 					    tree expr, unsigned *idx,
 					    c_omp_region_type ort,
 					    bool decl_p)
 {
   using namespace omp_addr_tokenizer;
+  tree c = *pc;
   location_t loc = OMP_CLAUSE_LOCATION (c);
   int i = *idx;
   tree decl = addr_tokens[i + 1]->expr;
@@ -3426,7 +3428,7 @@ c_omp_address_inspector::expand_array_base (tree c,
 	  || OMP_CLAUSE_MAP_KIND (c) == GOMP_MAP_DETACH))
     {
       *idx = ++i;
-      return c;
+      return pc;
     }
 
   switch (addr_tokens[i + 1]->u.access_kind)
@@ -3672,7 +3674,7 @@ c_omp_address_inspector::expand_array_base (tree c,
 
     default:
       *idx = i + consume_tokens;
-      return error_mark_node;
+      return NULL;
     }
 
   if (c3)
@@ -3685,7 +3687,7 @@ c_omp_address_inspector::expand_array_base (tree c,
 	  OMP_CLAUSE_MAP_IMPLICIT (c2) = 1;
 	  OMP_CLAUSE_MAP_IMPLICIT (c3) = 1;
 	}
-      c = c3;
+      pc = &OMP_CLAUSE_CHAIN (c2);
     }
   else if (c2)
     {
@@ -3693,27 +3695,28 @@ c_omp_address_inspector::expand_array_base (tree c,
       OMP_CLAUSE_CHAIN (c) = c2;
       if (implicit_p)
 	OMP_CLAUSE_MAP_IMPLICIT (c2) = 1;
-      c = c2;
+      pc = &OMP_CLAUSE_CHAIN (c);
     }
 
   i += consume_tokens;
   *idx = i;
 
   if (chain_p && map_p)
-    return omp_expand_access_chain (c, expr, addr_tokens, idx);
+    return omp_expand_access_chain (pc, expr, addr_tokens, idx);
 
-  return c;
+  return pc;
 }
 
 /* Translate "component_selector access_method" to OMP mapping clauses.  */
 
-tree
-c_omp_address_inspector::expand_component_selector (tree c,
+tree *
+c_omp_address_inspector::expand_component_selector (tree *pc,
 						    vec<omp_addr_token *>
 						      &addr_tokens,
 						    tree expr, unsigned *idx)
 {
   using namespace omp_addr_tokenizer;
+  tree c = *pc;
   location_t loc = OMP_CLAUSE_LOCATION (c);
   unsigned i = *idx;
   tree c2 = NULL_TREE, c3 = NULL_TREE;
@@ -3820,7 +3823,7 @@ c_omp_address_inspector::expand_component_selector (tree c,
 
     default:
       *idx = i + 2;
-      return error_mark_node;
+      return NULL;
     }
 
   if (c3)
@@ -3828,29 +3831,29 @@ c_omp_address_inspector::expand_component_selector (tree c,
       OMP_CLAUSE_CHAIN (c3) = OMP_CLAUSE_CHAIN (c);
       OMP_CLAUSE_CHAIN (c2) = c3;
       OMP_CLAUSE_CHAIN (c) = c2;
-      c = c3;
+      pc = &OMP_CLAUSE_CHAIN (c2);
     }
   else if (c2)
     {
       OMP_CLAUSE_CHAIN (c2) = OMP_CLAUSE_CHAIN (c);
       OMP_CLAUSE_CHAIN (c) = c2;
-      c = c2;
+      pc = &OMP_CLAUSE_CHAIN (c);
     }
 
   i += 2;
   *idx = i;
 
   if (chain_p && map_p)
-    return omp_expand_access_chain (c, expr, addr_tokens, idx);
+    return omp_expand_access_chain (pc, expr, addr_tokens, idx);
 
-  return c;
+  return pc;
 }
 
 /* Expand a map clause into a group of mapping clauses, creating nodes to
    attach/detach pointers and so forth as necessary.  */
 
-tree
-c_omp_address_inspector::expand_map_clause (tree c, tree expr,
+tree *
+c_omp_address_inspector::expand_map_clause (tree *pc, tree expr,
 					    vec<omp_addr_token *> &addr_tokens,
 					    c_omp_region_type ort)
 {
@@ -3866,18 +3869,18 @@ c_omp_address_inspector::expand_map_clause (tree c, tree expr,
 	  && addr_tokens[i]->u.structure_base_kind == BASE_DECL
 	  && addr_tokens[i + 1]->type == ACCESS_METHOD)
 	{
-	  c = expand_array_base (c, addr_tokens, expr, &i, ort, true);
-	  if (c == error_mark_node)
-	    return error_mark_node;
+	  pc = expand_array_base (pc, addr_tokens, expr, &i, ort, true);
+	  if (pc == NULL)
+	    return NULL;
 	}
       else if (remaining >= 2
 	       && addr_tokens[i]->type == ARRAY_BASE
 	       && addr_tokens[i]->u.structure_base_kind == BASE_ARBITRARY_EXPR
 	       && addr_tokens[i + 1]->type == ACCESS_METHOD)
 	{
-	  c = expand_array_base (c, addr_tokens, expr, &i, ort, false);
-	  if (c == error_mark_node)
-	    return error_mark_node;
+	  pc = expand_array_base (pc, addr_tokens, expr, &i, ort, false);
+	  if (pc == NULL)
+	    return NULL;
 	}
       else if (remaining >= 2
 	       && addr_tokens[i]->type == STRUCTURE_BASE
@@ -3904,18 +3907,18 @@ c_omp_address_inspector::expand_map_clause (tree c, tree expr,
 		i++;
 	      break;
 	    default:
-	      return error_mark_node;
+	      return NULL;
 	    }
 	}
       else if (remaining >= 2
 	       && addr_tokens[i]->type == COMPONENT_SELECTOR
 	       && addr_tokens[i + 1]->type == ACCESS_METHOD)
 	{
-	  c = expand_component_selector (c, addr_tokens, expr, &i);
+	  pc = expand_component_selector (pc, addr_tokens, expr, &i);
 	  /* We used 'expr', so these must have been the last tokens.  */
 	  gcc_assert (i == length);
-	  if (c == error_mark_node)
-	    return error_mark_node;
+	  if (pc == NULL)
+	    return NULL;
 	}
       else if (remaining >= 3
 	       && addr_tokens[i]->type == COMPONENT_SELECTOR
@@ -3933,9 +3936,9 @@ c_omp_address_inspector::expand_map_clause (tree c, tree expr,
     }
 
   if (i == length)
-    return c;
+    return pc;
 
-  return error_mark_node;
+  return NULL;
 }
 
 /* Given a mapper function MAPPER_FN, recursively scan through the map clauses
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index 2696e681be4f..fb480994520e 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -14043,8 +14043,9 @@ handle_omp_array_sections_1 (tree c, tree t, vec<tree> &types,
 /* Handle array sections for clause C.  */
 
 static bool
-handle_omp_array_sections (tree c, enum c_omp_region_type ort)
+handle_omp_array_sections (tree *pc, enum c_omp_region_type ort)
 {
+  tree c = *pc;
   bool maybe_zero_len = false;
   unsigned int first_non_one = 0;
   auto_vec<tree, 10> types;
@@ -14266,8 +14267,8 @@ handle_omp_array_sections (tree c, enum c_omp_region_type ort)
 
       c_omp_address_inspector ai (OMP_CLAUSE_LOCATION (c), t);
 
-      tree nc = ai.expand_map_clause (c, first, addr_tokens, ort);
-      if (nc != error_mark_node)
+      tree *npc = ai.expand_map_clause (pc, first, addr_tokens, ort);
+      if (npc != NULL)
 	{
 	  if (ai.maybe_zero_length_array_section (c))
 	    OMP_CLAUSE_MAP_MAYBE_ZERO_LENGTH_ARRAY_SECTION (c) = 1;
@@ -14616,12 +14617,13 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	  t = OMP_CLAUSE_DECL (c);
 	  if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	    {
-	      if (handle_omp_array_sections (c, ort))
+	      if (handle_omp_array_sections (pc, ort))
 		{
 		  remove = true;
 		  break;
 		}
 
+	      c = *pc;
 	      t = OMP_CLAUSE_DECL (c);
 	      if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_REDUCTION
 		  && OMP_CLAUSE_REDUCTION_INSCAN (c))
@@ -15237,10 +15239,12 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	    last_iterators = NULL_TREE;
 	  if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	    {
-	      if (handle_omp_array_sections (c, ort))
+	      if (handle_omp_array_sections (pc, ort))
 		remove = true;
-	      else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEPEND
-		       && OMP_CLAUSE_DEPEND_KIND (c) == OMP_CLAUSE_DEPEND_DEPOBJ)
+	      else if ((c = *pc)
+		       && OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEPEND
+		       && (OMP_CLAUSE_DEPEND_KIND (c)
+			   == OMP_CLAUSE_DEPEND_DEPOBJ))
 		{
 		  error_at (OMP_CLAUSE_LOCATION (c),
 			    "%<depend%> clause with %<depobj%> dependence "
@@ -15356,10 +15360,11 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 		grp_start_p = pc;
 		grp_sentinel = OMP_CLAUSE_CHAIN (c);
 
-		if (handle_omp_array_sections (c, ort))
+		if (handle_omp_array_sections (pc, ort))
 		  remove = true;
 		else
 		  {
+		    c = *pc;
 		    t = OMP_CLAUSE_DECL (c);
 		    if (!omp_mappable_type (TREE_TYPE (t)))
 		      {
@@ -15649,10 +15654,10 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	      {
 		grp_start_p = pc;
 		grp_sentinel = OMP_CLAUSE_CHAIN (c);
-		tree nc = ai.expand_map_clause (c, OMP_CLAUSE_DECL (c),
-						addr_tokens, ort);
-		if (nc != error_mark_node)
-		  c = nc;
+		tree *npc = ai.expand_map_clause (pc, OMP_CLAUSE_DECL (c),
+						  addr_tokens, ort);
+		if (npc != NULL)
+		  c = *npc;
 	      }
 	  }
 	  break;
@@ -15752,10 +15757,11 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	  t = OMP_CLAUSE_DECL (c);
 	  if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	    {
-	      if (handle_omp_array_sections (c, ort))
+	      if (handle_omp_array_sections (pc, ort))
 		remove = true;
 	      else
 		{
+		  c = *pc;
 		  t = OMP_CLAUSE_DECL (c);
 		  while (TREE_CODE (t) == ARRAY_REF)
 		    t = TREE_OPERAND (t, 0);
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index d6f2da65410f..aa1fa15548cf 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -5630,8 +5630,9 @@ handle_omp_array_sections_1 (tree c, tree t, vec<tree> &types,
 /* Handle array sections for clause C.  */
 
 static bool
-handle_omp_array_sections (tree &c, enum c_omp_region_type ort)
+handle_omp_array_sections (tree *pc, tree **pnext, enum c_omp_region_type ort)
 {
+  tree c = *pc;
   bool maybe_zero_len = false;
   unsigned int first_non_one = 0;
   auto_vec<tree, 10> types;
@@ -5884,23 +5885,27 @@ handle_omp_array_sections (tree &c, enum c_omp_region_type ort)
 
 	  cp_omp_address_inspector ai (OMP_CLAUSE_LOCATION (c), t);
 
-	  tree nc = ai.expand_map_clause (c, first, addr_tokens, ort);
-	  if (nc != error_mark_node)
+	  tree* npc = ai.expand_map_clause (pc, first, addr_tokens, ort);
+	  if (npc != NULL)
 	    {
 	      using namespace omp_addr_tokenizer;
 
+	      c = *pc;
+
 	      if (ai.maybe_zero_length_array_section (c))
 		OMP_CLAUSE_MAP_MAYBE_ZERO_LENGTH_ARRAY_SECTION (c) = 1;
 
 	      /* !!! If we're accessing a base decl via chained access
 		 methods (e.g. multiple indirections), duplicate clause
 		 detection won't work properly.  Skip it in that case.  */
-	      if ((addr_tokens[0]->type == STRUCTURE_BASE
+	      if (pnext
+		  && (addr_tokens[0]->type == STRUCTURE_BASE
 		   || addr_tokens[0]->type == ARRAY_BASE)
 		  && addr_tokens[0]->u.structure_base_kind == BASE_DECL
 		  && addr_tokens[1]->type == ACCESS_METHOD
 		  && omp_access_chain_p (addr_tokens, 1))
-		c = nc;
+		/* NPC points to the last node in the new sequence.  */
+		*pnext = npc;
 
 	      return false;
 	    }
@@ -7028,7 +7033,7 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	  t = OMP_CLAUSE_DECL (c);
 	  if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	    {
-	      if (handle_omp_array_sections (c, ort))
+	      if (handle_omp_array_sections (pc, NULL, ort))
 		{
 		  remove = true;
 		  break;
@@ -8068,7 +8073,7 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 
 	  if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	    {
-	      if (handle_omp_array_sections (c, ort))
+	      if (handle_omp_array_sections (pc, NULL, ort))
 		remove = true;
 	      else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEPEND
 		       && (OMP_CLAUSE_DEPEND_KIND (c)
@@ -8237,10 +8242,13 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 		grp_start_p = pc;
 		grp_sentinel = OMP_CLAUSE_CHAIN (c);
 
-		if (handle_omp_array_sections (c, ort))
+		tree *pnext = NULL;
+		if (handle_omp_array_sections (pc, &pnext, ort))
 		  remove = true;
 		else
 		  {
+		    /* We might have replaced the clause, so refresh C.  */
+		    c = *pc;
 		    t = OMP_CLAUSE_DECL (c);
 		    if (TREE_CODE (t) != OMP_ARRAY_SECTION
 			&& !type_dependent_expression_p (t)
@@ -8338,6 +8346,8 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 		     clauses, reset the OMP_CLAUSE_SIZE (representing a bias)
 		     to zero here.  */
 		  OMP_CLAUSE_SIZE (c) = size_zero_node;
+		if (pnext)
+		  c = *pnext;
 		break;
 	      }
 	    else if (type_dependent_expression_p (t))
@@ -8583,10 +8593,10 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	      {
 		grp_start_p = pc;
 		grp_sentinel = OMP_CLAUSE_CHAIN (c);
-		tree nc = ai.expand_map_clause (c, OMP_CLAUSE_DECL (c),
-						addr_tokens, ort);
-		if (nc != error_mark_node)
-		  c = nc;
+		tree *npc = ai.expand_map_clause (pc, OMP_CLAUSE_DECL (c),
+						  addr_tokens, ort);
+		if (npc != NULL)
+		  c = *npc;
 	      }
 	  }
 	  break;
@@ -8824,10 +8834,11 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	  t = OMP_CLAUSE_DECL (c);
 	  if (TREE_CODE (t) == OMP_ARRAY_SECTION)
 	    {
-	      if (handle_omp_array_sections (c, ort))
+	      if (handle_omp_array_sections (pc, NULL, ort))
 		remove = true;
 	      else
 		{
+		  c = *pc;
 		  t = OMP_CLAUSE_DECL (c);
 		  while (TREE_CODE (t) == OMP_ARRAY_SECTION)
 		    t = TREE_OPERAND (t, 0);
-- 
2.41.0


  parent reply	other threads:[~2023-09-06  9:35 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-09-06  9:34 [PATCH 0/5] OpenMP: Array-shaping operator and strided/rectangular 'target update' support Julian Brown
2023-09-06  9:34 ` [PATCH 1/5] OpenMP, NVPTX: memcpy[23]D bias correction Julian Brown
2023-09-26 22:57   ` Thomas Schwinge
2023-10-02 14:53     ` Julian Brown
2023-12-19 20:45       ` Tobias Burnus
2023-09-06  9:34 ` Julian Brown [this message]
2023-09-06  9:34 ` [PATCH 3/5] OpenMP: Support strided and shaped-array updates for C++ Julian Brown
2023-09-06  9:34 ` [PATCH 4/5] OpenMP: Array shaping operator and strided "target update" for C Julian Brown
2023-09-06  9:34 ` [PATCH 5/5] OpenMP: Noncontiguous "target update" for Fortran Julian Brown
  -- strict thread matches above, loose matches on Subject: below --
2023-07-03 21:33 [PATCH 0/5] [og13] OpenMP: strides, rectangular updates and array-shaping operator for "target update" Julian Brown
2023-07-03 21:33 ` [PATCH 2/5] OpenMP: Allow complete replacement of clause during map/to/from expansion 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=463243d6ef75f09ad961f0f8044f82d1af2f32da.1693991759.git.julian@codesourcery.com \
    --to=julian@codesourcery.com \
    --cc=fortran@gcc.gnu.org \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jakub@redhat.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).