From: Richard Sandiford <richard.sandiford@arm.com>
To: Andrew Carlotti <andrew.carlotti@arm.com>
Cc: gcc-patches@gcc.gnu.org, Richard Biener <richard.guenther@gmail.com>
Subject: Re: [PATCH v3 4/5] Add support for target_version attribute
Date: Thu, 14 Dec 2023 14:58:23 +0000 [thread overview]
Message-ID: <mptmsucu8z4.fsf@arm.com> (raw)
In-Reply-To: <c34561ac-f28c-64c5-be42-02e4bc0adbb2@e124511.cambridge.arm.com> (Andrew Carlotti's message of "Wed, 6 Dec 2023 12:46:29 +0000")
Andrew Carlotti <andrew.carlotti@arm.com> writes:
> This patch adds support for the "target_version" attribute to the middle
> end and the C++ frontend, which will be used to implement function
> multiversioning in the aarch64 backend.
>
> On targets that don't use the "target" attribute for multiversioning,
> there is no conflict between the "target" and "target_clones"
> attributes. This patch therefore makes the mutual exclusion in
> C-family, D and Ada conditonal upon the value of the
> expanded_clones_attribute target hook.
>
> The "target_version" attribute is only added to C++ in this patch,
> because this is currently the only frontend which supports
> multiversioning using the "target" attribute. Support for the
> "target_version" attribute will be extended to C at a later date.
>
> Targets that currently use the "target" attribute for function
> multiversioning (i.e. i386 and rs6000) are not affected by this patch.
>
> Ok for master?
>
> gcc/ChangeLog:
>
> * attribs.cc (decl_attributes): Pass attribute name to target.
> (is_function_default_version): Update comment to specify
> incompatibility with target_version attributes.
> * cgraphclones.cc (cgraph_node::create_version_clone_with_body):
> Call valid_version_attribute_p for target_version attributes.
> * defaults.h (TARGET_HAS_FMV_TARGET_ATTRIBUTE): New macro.
> * target.def (valid_version_attribute_p): New hook.
> * doc/tm.texi.in: Add new hook.
> * doc/tm.texi: Regenerate.
> * multiple_target.cc (create_dispatcher_calls): Remove redundant
> is_function_default_version check.
> (expand_target_clones): Use target macro to pick attribute name.
> * targhooks.cc (default_target_option_valid_version_attribute_p):
> New.
> * targhooks.h (default_target_option_valid_version_attribute_p):
> New.
> * tree.h (DECL_FUNCTION_VERSIONED): Update comment to include
> target_version attributes.
>
> gcc/c-family/ChangeLog:
>
> * c-attribs.cc (attr_target_exclusions): Make
> target/target_clones exclusion target-dependent.
> (attr_target_clones_exclusions): Ditto, and add target_version.
> (attr_target_version_exclusions): New.
> (c_common_attribute_table): Add target_version.
> (handle_target_version_attribute): New.
>
> gcc/ada/ChangeLog:
>
> * gcc-interface/utils.cc (attr_target_exclusions): Make
> target/target_clones exclusion target-dependent.
> (attr_target_clones_exclusions): Ditto.
>
> gcc/d/ChangeLog:
>
> * d-attribs.cc (attr_target_exclusions): Make
> target/target_clones exclusion target-dependent.
> (attr_target_clones_exclusions): Ditto.
>
> gcc/cp/ChangeLog:
>
> * decl2.cc (check_classfn): Update comment to include
> target_version attributes.
The front-end changes look mechanical, so: OK with the nit below fixed
unless anyone objects in 24 hours, or asks for more time.
> diff --git a/gcc/ada/gcc-interface/utils.cc b/gcc/ada/gcc-interface/utils.cc
> index f2c504ddf8d3df11abe81aec695c9eea0b39da6c..5d946c33b212c5ea50e7a73524e8c1d062280956 100644
> --- a/gcc/ada/gcc-interface/utils.cc
> +++ b/gcc/ada/gcc-interface/utils.cc
> @@ -145,14 +145,16 @@ static const struct attribute_spec::exclusions attr_noinline_exclusions[] =
>
> static const struct attribute_spec::exclusions attr_target_exclusions[] =
> {
> - { "target_clones", true, true, true },
> + { "target_clones", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
> + TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE },
> { NULL, false, false, false },
> };
>
> static const struct attribute_spec::exclusions attr_target_clones_exclusions[] =
> {
> { "always_inline", true, true, true },
> - { "target", true, true, true },
> + { "target", TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE,
> + TARGET_HAS_FMV_TARGET_ATTRIBUTE },
> { NULL, false, false, false },
> };
>
> diff --git a/gcc/attribs.cc b/gcc/attribs.cc
> index c7209c26acc9faf699774b0ef669ec6748b9073d..19cccf2d7ca4fdd6a46a01884393c6779333dbc5 100644
> --- a/gcc/attribs.cc
> +++ b/gcc/attribs.cc
> @@ -657,7 +657,8 @@ decl_attributes (tree *node, tree attributes, int flags,
> options to the attribute((target(...))) list. */
> if (TREE_CODE (*node) == FUNCTION_DECL
> && current_target_pragma
> - && targetm.target_option.valid_attribute_p (*node, NULL_TREE,
> + && targetm.target_option.valid_attribute_p (*node,
> + get_identifier ("target"),
> current_target_pragma, 0))
> {
> tree cur_attr = lookup_attribute ("target", attributes);
> @@ -1241,8 +1242,9 @@ make_dispatcher_decl (const tree decl)
> return func_decl;
> }
>
> -/* Returns true if decl is multi-versioned and DECL is the default function,
> - that is it is not tagged with target specific optimization. */
> +/* Returns true if DECL is multi-versioned using the target attribute, and this
> + is the default version. This function can only be used for targets that do
> + not support the "target_version" attribute. */
>
> bool
> is_function_default_version (const tree decl)
> diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
> index b3b41ef123a0f171f57acb1b7f7fdde716428c00..eabc11c00f09125b7d6e2c47f3b083a03909825c 100644
> --- a/gcc/c-family/c-attribs.cc
> +++ b/gcc/c-family/c-attribs.cc
> @@ -149,6 +149,7 @@ static tree handle_alloc_align_attribute (tree *, tree, tree, int, bool *);
> static tree handle_assume_aligned_attribute (tree *, tree, tree, int, bool *);
> static tree handle_assume_attribute (tree *, tree, tree, int, bool *);
> static tree handle_target_attribute (tree *, tree, tree, int, bool *);
> +static tree handle_target_version_attribute (tree *, tree, tree, int, bool *);
> static tree handle_target_clones_attribute (tree *, tree, tree, int, bool *);
> static tree handle_optimize_attribute (tree *, tree, tree, int, bool *);
> static tree ignore_attribute (tree *, tree, tree, int, bool *);
> @@ -230,14 +231,23 @@ static const struct attribute_spec::exclusions attr_noinline_exclusions[] =
>
> static const struct attribute_spec::exclusions attr_target_exclusions[] =
> {
> - ATTR_EXCL ("target_clones", true, true, true),
> + ATTR_EXCL ("target_clones", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
> + TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE),
> ATTR_EXCL (NULL, false, false, false),
> };
>
> static const struct attribute_spec::exclusions attr_target_clones_exclusions[] =
> {
> ATTR_EXCL ("always_inline", true, true, true),
> - ATTR_EXCL ("target", true, true, true),
> + ATTR_EXCL ("target", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
> + TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE),
> + ATTR_EXCL ("target_version", true, true, true),
> + ATTR_EXCL (NULL, false, false, false),
> +};
> +
> +static const struct attribute_spec::exclusions attr_target_version_exclusions[] =
> +{
> + ATTR_EXCL ("target_clones", true, true, true),
> ATTR_EXCL (NULL, false, false, false),
> };
>
> @@ -505,6 +515,9 @@ const struct attribute_spec c_common_attribute_table[] =
> { "target", 1, -1, true, false, false, false,
> handle_target_attribute,
> attr_target_exclusions },
> + { "target_version", 1, 1, true, false, false, false,
> + handle_target_version_attribute,
> + attr_target_version_exclusions },
> { "target_clones", 1, -1, true, false, false, false,
> handle_target_clones_attribute,
> attr_target_clones_exclusions },
> @@ -5670,6 +5683,25 @@ handle_target_attribute (tree *node, tree name, tree args, int flags,
> return NULL_TREE;
> }
>
> +/* Handle a "target_version" attribute. */
> +
> +static tree
> +handle_target_version_attribute (tree *node, tree name, tree args, int flags,
> + bool *no_add_attrs)
> +{
> + /* Ensure we have a function type. */
Sorry for not noticing before, but: s/function type/function declaration/.
Thanks,
Richard
> + if (TREE_CODE (*node) != FUNCTION_DECL)
> + {
> + warning (OPT_Wattributes, "%qE attribute ignored", name);
> + *no_add_attrs = true;
> + }
> + else if (!targetm.target_option.valid_version_attribute_p (*node, name, args,
> + flags))
> + *no_add_attrs = true;
> +
> + return NULL_TREE;
> +}
> +
> /* Handle a "target_clones" attribute. */
>
> static tree
> diff --git a/gcc/cgraphclones.cc b/gcc/cgraphclones.cc
> index 29d28ef895a73a223695cbb86aafbc845bbe7688..3ba5a4a2dd9fd4b7206d95f01c990818d0440334 100644
> --- a/gcc/cgraphclones.cc
> +++ b/gcc/cgraphclones.cc
> @@ -78,6 +78,7 @@ along with GCC; see the file COPYING3. If not see
> #include "tree-eh.h"
> #include "tree-cfg.h"
> #include "tree-inline.h"
> +#include "attribs.h"
> #include "dumpfile.h"
> #include "gimple-pretty-print.h"
> #include "alloc-pool.h"
> @@ -1048,7 +1049,17 @@ cgraph_node::create_version_clone_with_body
> location_t saved_loc = input_location;
> tree v = TREE_VALUE (target_attributes);
> input_location = DECL_SOURCE_LOCATION (new_decl);
> - bool r = targetm.target_option.valid_attribute_p (new_decl, NULL, v, 1);
> + bool r;
> + tree name_id = get_attribute_name (target_attributes);
> + const char *name_str = IDENTIFIER_POINTER (name_id);
> + if (strcmp (name_str, "target") == 0)
> + r = targetm.target_option.valid_attribute_p (new_decl, name_id, v, 1);
> + else if (strcmp (name_str, "target_version") == 0)
> + r = targetm.target_option.valid_version_attribute_p (new_decl, name_id,
> + v, 1);
> + else
> + gcc_unreachable();
> +
> input_location = saved_loc;
> if (!r)
> return NULL;
> diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
> index 9e666e5eecee07ae7c742c3a2b27e85899945c4e..e607aa14d284d545d122e04b0eae1247fd301882 100644
> --- a/gcc/cp/decl2.cc
> +++ b/gcc/cp/decl2.cc
> @@ -832,8 +832,8 @@ check_classfn (tree ctype, tree function, tree template_parms)
> tree c2 = get_constraints (fndecl);
>
> /* While finding a match, same types and params are not enough
> - if the function is versioned. Also check version ("target")
> - attributes. */
> + if the function is versioned. Also check for different target
> + specific attributes. */
> if (same_type_p (TREE_TYPE (TREE_TYPE (function)),
> TREE_TYPE (TREE_TYPE (fndecl)))
> && compparms (p1, p2)
> diff --git a/gcc/d/d-attribs.cc b/gcc/d/d-attribs.cc
> index c0dc0e24ded871c136e54e5527e901d16cfa5ceb..13911c75948075a838914caca88802ac325f3107 100644
> --- a/gcc/d/d-attribs.cc
> +++ b/gcc/d/d-attribs.cc
> @@ -128,14 +128,16 @@ static const struct attribute_spec::exclusions attr_noinline_exclusions[] =
>
> static const struct attribute_spec::exclusions attr_target_exclusions[] =
> {
> - ATTR_EXCL ("target_clones", true, true, true),
> + ATTR_EXCL ("target_clones", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
> + TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE),
> ATTR_EXCL (NULL, false, false, false),
> };
>
> static const struct attribute_spec::exclusions attr_target_clones_exclusions[] =
> {
> ATTR_EXCL ("always_inline", true, true, true),
> - ATTR_EXCL ("target", true, true, true),
> + ATTR_EXCL ("target", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
> + TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE),
> ATTR_EXCL (NULL, false, false, false),
> };
>
> diff --git a/gcc/defaults.h b/gcc/defaults.h
> index dc6f09cacae8a85873c6d6685eea91fb749cc367..6f0959694102ca28fd993aaff7ec3f5d09334c39 100644
> --- a/gcc/defaults.h
> +++ b/gcc/defaults.h
> @@ -877,6 +877,16 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
> #endif
> #endif
>
> +/* Indicate whether the target uses "target" attributes for function
> + multiversioning. This is used to choose between the "target" and
> + "target_version" attributes when expanding a "target_clones" attribute, and
> + determine whether the "target" and "target_clones" attributes are mutually
> + exclusive. */
> +#ifndef TARGET_HAS_FMV_TARGET_ATTRIBUTE
> +#define TARGET_HAS_FMV_TARGET_ATTRIBUTE 1
> +#endif
> +
> +
> /* Select a format to encode pointers in exception handling data. We
> prefer those that result in fewer dynamic relocations. Assume no
> special support here and encode direct references. */
> diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
> index c0f949b538c04b3f409217e0508967dfca131604..1dc1b0523decff48f895703303a72fd93202bf7c 100644
> --- a/gcc/doc/tm.texi
> +++ b/gcc/doc/tm.texi
> @@ -10650,6 +10650,18 @@ the function declaration to hold a pointer to a target-specific
> @code{struct cl_target_option} structure.
> @end deftypefn
>
> +@deftypefn {Target Hook} bool TARGET_OPTION_VALID_VERSION_ATTRIBUTE_P (tree @var{fndecl}, tree @var{name}, tree @var{args}, int @var{flags})
> +This hook is called to parse @code{attribute(target_version("..."))},
> +which allows setting target-specific options on individual function versions.
> +These function-specific options may differ
> +from the options specified on the command line. The hook should return
> +@code{true} if the options are valid.
> +
> +The hook should set the @code{DECL_FUNCTION_SPECIFIC_TARGET} field in
> +the function declaration to hold a pointer to a target-specific
> +@code{struct cl_target_option} structure.
> +@end deftypefn
> +
> @deftypefn {Target Hook} void TARGET_OPTION_SAVE (struct cl_target_option *@var{ptr}, struct gcc_options *@var{opts}, struct gcc_options *@var{opts_set})
> This hook is called to save any additional target-specific information
> in the @code{struct cl_target_option} structure for function-specific
> diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
> index ef04c89d7b6fd27332f09ba844c11d4bbcf3bbe0..c452108ad54a7828762f17a864f700d1682a3964 100644
> --- a/gcc/doc/tm.texi.in
> +++ b/gcc/doc/tm.texi.in
> @@ -7031,6 +7031,8 @@ on this implementation detail.
>
> @hook TARGET_OPTION_VALID_ATTRIBUTE_P
>
> +@hook TARGET_OPTION_VALID_VERSION_ATTRIBUTE_P
> +
> @hook TARGET_OPTION_SAVE
>
> @hook TARGET_OPTION_RESTORE
> diff --git a/gcc/multiple_target.cc b/gcc/multiple_target.cc
> index a2ed048d7dd28ec470953fcd8a0dc86817e4b7dc..a832d1e8ff83d99acd7579c276b083141102f3e1 100644
> --- a/gcc/multiple_target.cc
> +++ b/gcc/multiple_target.cc
> @@ -66,10 +66,6 @@ create_dispatcher_calls (struct cgraph_node *node)
> {
> ipa_ref *ref;
>
> - if (!DECL_FUNCTION_VERSIONED (node->decl)
> - || !is_function_default_version (node->decl))
> - return;
> -
> if (!targetm.has_ifunc_p ())
> {
> error_at (DECL_SOURCE_LOCATION (node->decl),
> @@ -377,6 +373,8 @@ expand_target_clones (struct cgraph_node *node, bool definition)
> return false;
> }
>
> + const char *new_attr_name = (TARGET_HAS_FMV_TARGET_ATTRIBUTE
> + ? "target" : "target_version");
> cgraph_function_version_info *decl1_v = NULL;
> cgraph_function_version_info *decl2_v = NULL;
> cgraph_function_version_info *before = NULL;
> @@ -392,7 +390,7 @@ expand_target_clones (struct cgraph_node *node, bool definition)
> char *attr = attrs[i];
>
> /* Create new target clone. */
> - tree attributes = make_attribute ("target", attr,
> + tree attributes = make_attribute (new_attr_name, attr,
> DECL_ATTRIBUTES (node->decl));
>
> char *suffix = XNEWVEC (char, strlen (attr) + 1);
> @@ -430,7 +428,7 @@ expand_target_clones (struct cgraph_node *node, bool definition)
> XDELETEVEC (attr_str);
>
> /* Setting new attribute to initial function. */
> - tree attributes = make_attribute ("target", "default",
> + tree attributes = make_attribute (new_attr_name, "default",
> DECL_ATTRIBUTES (node->decl));
> DECL_ATTRIBUTES (node->decl) = attributes;
> node->local = false;
> diff --git a/gcc/target.def b/gcc/target.def
> index eae7959cdccbfe669d234afe45c39545eae64234..ccdd3c56f5a9d74a14899ba60ac5c41de6bc192d 100644
> --- a/gcc/target.def
> +++ b/gcc/target.def
> @@ -6540,6 +6540,23 @@ the function declaration to hold a pointer to a target-specific\n\
> bool, (tree fndecl, tree name, tree args, int flags),
> default_target_option_valid_attribute_p)
>
> +/* Function to validate the attribute((target_version(...))) strings. If
> + the option is validated, the hook should also fill in
> + DECL_FUNCTION_SPECIFIC_TARGET in the function decl node. */
> +DEFHOOK
> +(valid_version_attribute_p,
> + "This hook is called to parse @code{attribute(target_version(\"...\"))},\n\
> +which allows setting target-specific options on individual function versions.\n\
> +These function-specific options may differ\n\
> +from the options specified on the command line. The hook should return\n\
> +@code{true} if the options are valid.\n\
> +\n\
> +The hook should set the @code{DECL_FUNCTION_SPECIFIC_TARGET} field in\n\
> +the function declaration to hold a pointer to a target-specific\n\
> +@code{struct cl_target_option} structure.",
> + bool, (tree fndecl, tree name, tree args, int flags),
> + default_target_option_valid_version_attribute_p)
> +
> /* Function to save any extra target state in the target options structure. */
> DEFHOOK
> (save,
> diff --git a/gcc/targhooks.h b/gcc/targhooks.h
> index 26695abe41e46d4d73402734c15892cfef6cf829..9e76326166c39a381ef3c9fbb9300d1523d9bb47 100644
> --- a/gcc/targhooks.h
> +++ b/gcc/targhooks.h
> @@ -192,6 +192,7 @@ extern bool default_hard_regno_scratch_ok (unsigned int);
> extern bool default_mode_dependent_address_p (const_rtx, addr_space_t);
> extern bool default_new_address_profitable_p (rtx, rtx_insn *, rtx);
> extern bool default_target_option_valid_attribute_p (tree, tree, tree, int);
> +extern bool default_target_option_valid_version_attribute_p (tree, tree, tree, int);
> extern bool default_target_option_pragma_parse (tree, tree);
> extern bool default_target_can_inline_p (tree, tree);
> extern bool default_update_ipa_fn_target_info (unsigned int &, const gimple *);
> diff --git a/gcc/targhooks.cc b/gcc/targhooks.cc
> index a2dc7331c24d100405434ec670678fcacadaee9b..aabba67787fbe91ae5f5dc50d7c2a29c410944a7 100644
> --- a/gcc/targhooks.cc
> +++ b/gcc/targhooks.cc
> @@ -1789,7 +1789,19 @@ default_target_option_valid_attribute_p (tree ARG_UNUSED (fndecl),
> int ARG_UNUSED (flags))
> {
> warning (OPT_Wattributes,
> - "target attribute is not supported on this machine");
> + "%<target%> attribute is not supported on this machine");
> +
> + return false;
> +}
> +
> +bool
> +default_target_option_valid_version_attribute_p (tree ARG_UNUSED (fndecl),
> + tree ARG_UNUSED (name),
> + tree ARG_UNUSED (args),
> + int ARG_UNUSED (flags))
> +{
> + warning (OPT_Wattributes,
> + "%<target_version%> attribute is not supported on this machine");
>
> return false;
> }
> diff --git a/gcc/tree.h b/gcc/tree.h
> index 086b55f0375435d53a1604b6659da4f19fce3d17..d7841af19b20b0dc0ae28b433d5150e9c4763eff 100644
> --- a/gcc/tree.h
> +++ b/gcc/tree.h
> @@ -3500,8 +3500,8 @@ extern vec<tree, va_gc> **decl_debug_args_insert (tree);
> (FUNCTION_DECL_CHECK (NODE)->function_decl.function_specific_optimization)
>
> /* In FUNCTION_DECL, this is set if this function has other versions generated
> - using "target" attributes. The default version is the one which does not
> - have any "target" attribute set. */
> + to support different architecture feature sets, e.g. using "target" or
> + "target_version" attributes. */
> #define DECL_FUNCTION_VERSIONED(NODE)\
> (FUNCTION_DECL_CHECK (NODE)->function_decl.versioned_function)
>
next prev parent reply other threads:[~2023-12-14 14:58 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-12-06 12:43 [PATCH v3 0/5] target_version and aarch64 function multiversioning Andrew Carlotti
2023-12-06 12:44 ` [1/5] aarch64: Add cpu feature detection to libgcc Andrew Carlotti
2023-12-06 12:45 ` [PATCH v3 2/5] c-family: Simplify attribute exclusion handling Andrew Carlotti
2023-12-06 12:45 ` [PATCH v3 3/5] ada: Improve " Andrew Carlotti
2023-12-06 12:46 ` [PATCH v3 4/5] Add support for target_version attribute Andrew Carlotti
2023-12-14 14:58 ` Richard Sandiford [this message]
2023-12-16 0:45 ` [committed v4 " Andrew Carlotti
2023-12-06 12:47 ` [PATCH v3 5/5] aarch64: Add function multiversioning support Andrew Carlotti
2023-12-14 14:02 ` Richard Sandiford
2023-12-16 0:47 ` [committed v4 " Andrew Carlotti
2023-12-16 4:03 ` Ramana Radhakrishnan
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=mptmsucu8z4.fsf@arm.com \
--to=richard.sandiford@arm.com \
--cc=andrew.carlotti@arm.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=richard.guenther@gmail.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).