Hello Joseph and all,


here is a preliminary patch the implements the proposed
tag compatibility rules for C23 in GCC (N2863). It works
by tweaking the diagnostics in the FE and by recording
structs/union types to be able to set TYPE_CANONICAL to
a canonical definition (as per previous discussions).

 
Overall, this seems to work very well when testing
on my own projects. There are still some issues
left that I want to point out:

- at the moment, all struct/union types are collected
in a vector. This needs to be replaced by a hash table.

- the feature has a flag (-ftag-compat) which is now turned
on by default in all language modes to facilitate testing
and to identify backwards compatibility problems. Turned on,
it survives bootstrapping and regression testing with
only a few cases that test for diagnostics that go
away changed to turn it off.

- The new rules are not applied to structs with variable
sized members (which are a GNU extension).

- In contrast to the published proposal, structs without
tags are now treated as incompatible as requested by WG14.

- There is still one assertion in ipa-free-lang-data I had
to conditionally turn off and did not have time to 
investigate.  Otherwise, there are only C FE changes.
LTO may still need some more testing.

- It fixes some bugs in (formerly) unused FE code
and removes some other dead code. This could be
moved into its own patch.

- If adopted into C, I assume we need some
compatibility warnings. From testing, I could
not identify any backwards compatibility problems.

- There are certainly some issues I may have
overlooked.


Martin


gcc/
* c-family/c.opt: Add -ftag-compat flag.
* c/c-decl.cc (pop_scope): Remove dead code. 
(diagnose_mismatched_decls): Support for 
new tag compatibility rules.
(start_struct): Dito.
(finish_struct): Dito.
(start_enum): Dito.
(finish_enum): Dito.
(build_enumerator): Pass enumtype to build_decl.
(c_simulate_enum_decl): Pass enumtype to 
build_enumerator.
* c/c-parser.cc (c_parser_enum_specifier): Dito.
* c/c-tree.h (build_enumerator): Add enumtype 
argument.
* c/c-typeck.cc (comptypes_internal): Support
for new tag compatibility rules.
(same_translation_unit_p): Removed.
(tagged_types_tu_compatible_p): Bug fixes and
support for new tag compatibility rules.
(convert_for_assignment): Support for new tag
compatibility rules
(digest_init): Dito.
* ipa-free-lang-data.cc (fld_incomplete_type_of):
Conditionally turn of assertion related to
TYPE_CANONICAL if -ftag-compat is on.
doc/
* invoke.texi: Document -ftag-compat flag.
testsuite/
* gcc.dg/asan/pr81460.c: Add -fno-tag-compat.
* gcc.dg/c99-tag-1.c: Add -fno-tag-compat.
* gcc.dg/c99-tag-2.c: Add -fno-tag-compat. 
* gcc.dg/decl-3.c: Add -fno-tag-compat.
* gcc.dg/enum-redef-1.c: Add -fno-tag-compat.
* gcc.dg/parm-incomplete-1.c: Add -fno-tag-compat.
* gcc.dg/pr17188-1.c: Add -fno-tag-compat.
* gcc.dg/pr18809-1.c: Add -pedantic-errors and
-fno-tag-compat.
* gcc.dg/pr27953.c: Add -fno-tag-compat.
* gcc.dg/pr39084.c: Add -fno-tag-compat.
* gcc.dg/pr68533.c: Add -fno-tag-compat.
* gcc.dg/pr79983.c: Add -fno-tag-compat.
* gcc.dg/pr89211.c: Add -fno-tag-compat.
* gcc.dg/tag-compat.c: New test.
* gcc.dg/tag-compat10.c: New test.
* gcc.dg/tag-compat11.c: New test.
* gcc.dg/tag-compat12.c: New test.
* gcc.dg/tag-compat2.c: New test.
* gcc.dg/tag-compat3.c: New test.
* gcc.dg/tag-compat4.c: New test.
* gcc.dg/tag-compat5.c: New test.
* gcc.dg/tag-compat6.c: New test.
* gcc.dg/tag-compat7.c: New test.
* gcc.dg/tag-compat8.c: New test.
* gcc.dg/tag-compat9.c: New test.
* gcc.dg/vla-11.c: Add -fno-tag-compat.
* gcc.dg/vla-stexp-2.c: Add -fno-tag-compat.



diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 41a20bc625e..cd3164018f2 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -2108,6 +2108,9 @@ Enum(strong_eval_order) String(some) Value(1)
 EnumValue
 Enum(strong_eval_order) String(all) Value(2)
 
+ftag-compat
+C Var(flag_tag_compat) Init(1)
+
 ftemplate-backtrace-limit=
 C++ ObjC++ Joined RejectNegative UInteger
Var(template_backtrace_limit) Init(10)
 Set the maximum number of template instantiation notes for a single
warning or error.
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 5266a61b859..df208a310f9 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -599,6 +599,10 @@ public:
   auto_vec<tree> typedefs_seen;
 };
 
+
+/* All tagged typed so that TYPE_CANONICAL can be set correctly.  */
+static auto_vec<tree> all_structs;
+
 /* Information for the struct or union currently being parsed, or
    NULL if not parsing a struct or union.  */
 static class c_struct_parse_info *struct_parse_info;
@@ -1354,8 +1358,8 @@ pop_scope (void)
              BLOCK_VARS (block) = extp;
            }
          /* If this is the file scope set DECL_CONTEXT of each decl
to
-            the TRANSLATION_UNIT_DECL.  This makes
same_translation_unit_p
-            work.  */
+            the TRANSLATION_UNIT_DECL.  */
+
          if (scope == file_scope)
            {
              DECL_CONTEXT (p) = context;
@@ -1985,9 +1989,22 @@ diagnose_mismatched_decls (tree newdecl, tree
olddecl,
      given scope.  */
   if (TREE_CODE (olddecl) == CONST_DECL)
     {
-      auto_diagnostic_group d;
-      error ("redeclaration of enumerator %q+D", newdecl);
-      locate_old_decl (olddecl);
+      if (flag_tag_compat && comptypes (TREE_TYPE (olddecl), TREE_TYPE
(newdecl)))
+       {
+         if (!COMPLETE_TYPE_P (TREE_TYPE (olddecl))
+             || !simple_cst_equal (DECL_INITIAL (olddecl),
DECL_INITIAL (newdecl)))
+           {
+             auto_diagnostic_group d;
+             error ("conflicting redeclaration of enumerator %q+D",
newdecl);
+             locate_old_decl (olddecl);
+           }
+       }
+      else
+       {
+         auto_diagnostic_group d;
+         error ("redeclaration of enumerator %q+D", newdecl);
+         locate_old_decl (olddecl);
+       }
       return false;
     }
 
@@ -2238,7 +2255,7 @@ diagnose_mismatched_decls (tree newdecl, tree
olddecl,
                 isn't overriding an extern inline reject the new
decl.
                 In c99, no overriding is allowed in the same
translation
                 unit.  */
-             if ((!DECL_EXTERN_INLINE (olddecl)
+             if (!DECL_EXTERN_INLINE (olddecl)
                   || DECL_EXTERN_INLINE (newdecl)
                   || (!flag_gnu89_inline
                       && (!DECL_DECLARED_INLINE_P (olddecl)
@@ -2248,7 +2265,6 @@ diagnose_mismatched_decls (tree newdecl, tree
olddecl,
                           || !lookup_attribute ("gnu_inline",
                                                 DECL_ATTRIBUTES
(newdecl))))
                  )
-                 && same_translation_unit_p (newdecl, olddecl))
                {
                  auto_diagnostic_group d;
                  error ("redefinition of %q+D", newdecl);
@@ -3267,18 +3283,11 @@ pushdecl (tree x)
         type to the composite of all the types of that declaration.
         After the consistency checks, it will be reset to the
         composite of the visible types only.  */
-      if (b && (TREE_PUBLIC (x) || same_translation_unit_p (x, b-
>decl))
-         && b->u.type)
+      if (b && b->u.type)
        TREE_TYPE (b->decl) = b->u.type;
 
-      /* The point of the same_translation_unit_p check here is,
-        we want to detect a duplicate decl for a construct like
-        foo() { extern bar(); } ... static bar();  but not if
-        they are in different translation units.  In any case,
-        the static does not go in the externals scope.  */
-      if (b
-         && (TREE_PUBLIC (x) || same_translation_unit_p (x, b->decl))
-         && duplicate_decls (x, b->decl))
+      /* The static does not go in the externals scope.  */
+      if (b && duplicate_decls (x, b->decl))
        {
          tree thistype;
          if (vistype)
@@ -8088,7 +8097,8 @@ get_parm_info (bool ellipsis, tree expr)
             (it's impossible to call such a function with type-
             correct arguments).  An anonymous union parm type is
             meaningful as a GNU extension, so don't warn for that. 
*/
-         if (TREE_CODE (decl) != UNION_TYPE || b->id != NULL_TREE)
+         if (!flag_tag_compat
+             && (TREE_CODE (decl) != UNION_TYPE || b->id !=
NULL_TREE))
            {
              if (b->id)
                /* The %s will be one of 'struct', 'union', or 'enum'. 
*/
@@ -8288,6 +8298,12 @@ start_struct (location_t loc, enum tree_code
code, tree name,
 
   if (name != NULL_TREE)
     ref = lookup_tag (code, name, true, &refloc);
+
+  /* If we already have a completed definition, then
+     do not use it. We will check for consistency later */
+  if (flag_tag_compat && ref && TYPE_SIZE (ref))
+    ref = NULL_TREE;
+
   if (ref && TREE_CODE (ref) == code)
     {
       if (TYPE_STUB_DECL (ref))
@@ -8977,6 +8993,54 @@ finish_struct (location_t loc, tree t, tree
fieldlist, tree attributes,
       warning_at (loc, 0, "union cannot be made transparent");
     }
 
+  /* Check for consistency with previous definition */
+  if (flag_tag_compat)
+    {
+      struct c_binding *b = NULL;
+      tree name = TYPE_NAME (t);
+
+      if (name)
+       b = I_TAG_BINDING (name);
+
+      if (b)
+       b = b->shadowed;
+
+      if (b && B_IN_CURRENT_SCOPE (b))
+       {
+         tree vistype = b->decl;
+         bool different_types = false;
+
+         if ((1 != comptypes_check_different_types(t, vistype,
&different_types))
+             || different_types)
+           warning_at(loc, 0, "redefinition of struct or union %qT",
vistype);
+       }
+    }
+
+  if (flag_tag_compat)
+    {
+      gcc_assert (t == TYPE_MAIN_VARIANT (t));
+      /* We treat structs with variable size as
+         incompatible with other structs.  */
+      if (C_TYPE_VARIABLE_SIZE (t))
+       TYPE_CANONICAL (t) = t;
+      else
+       {
+         unsigned int i;
+         tree t2 = NULL_TREE;
+          FOR_EACH_VEC_ELT (all_structs, i, t2)
+         if (comptypes (t, t2))
+           break;
+
+         if (t2 != NULL_TREE)
+           TYPE_CANONICAL (t) = t2;
+         else
+           {
+             TYPE_CANONICAL (t) = t;
+             all_structs.safe_push (t);
+           }
+        }
+    }
+
   tree incomplete_vars = C_TYPE_INCOMPLETE_VARS (TYPE_MAIN_VARIANT
(t));
   for (x = TYPE_MAIN_VARIANT (t); x; x = TYPE_NEXT_VARIANT (x))
     {
@@ -8987,6 +9051,7 @@ finish_struct (location_t loc, tree t, tree
fieldlist, tree attributes,
       C_TYPE_FIELDS_VOLATILE (x) = C_TYPE_FIELDS_VOLATILE (t);
       C_TYPE_VARIABLE_SIZE (x) = C_TYPE_VARIABLE_SIZE (t);
       C_TYPE_INCOMPLETE_VARS (x) = NULL_TREE;
+      TYPE_CANONICAL (x) = TYPE_CANONICAL (t);
     }
 
   /* Update type location to the one of the definition, instead of
e.g.
@@ -8994,6 +9059,7 @@ finish_struct (location_t loc, tree t, tree
fieldlist, tree attributes,
   if (TYPE_STUB_DECL (t))
     DECL_SOURCE_LOCATION (TYPE_STUB_DECL (t)) = loc;
 
+
   /* Finish debugging output for this type.  */
   rest_of_type_compilation (t, toplevel);
 
@@ -9100,9 +9166,15 @@ start_enum (location_t loc, struct
c_enum_contents *the_enum, tree name)
   if (name != NULL_TREE)
     enumtype = lookup_tag (ENUMERAL_TYPE, name, true, &enumloc);
 
+  if (flag_tag_compat && enumtype != NULL_TREE
+      && TREE_CODE (enumtype) == ENUMERAL_TYPE
+      && TYPE_VALUES (enumtype) != NULL_TREE)
+    enumtype = NULL_TREE;
+
   if (enumtype == NULL_TREE || TREE_CODE (enumtype) != ENUMERAL_TYPE)
     {
       enumtype = make_node (ENUMERAL_TYPE);
+      TYPE_SIZE (enumtype) = NULL_TREE;
       pushtag (loc, name, enumtype);
     }
   /* Update type location to the one of the definition, instead of
e.g.
@@ -9311,6 +9383,31 @@ finish_enum (tree enumtype, tree values, tree
attributes)
 
   C_TYPE_BEING_DEFINED (enumtype) = 0;
 
+  /* Check for consistency with previous definition */
+
+  if (flag_tag_compat)
+    {
+      struct c_binding *b = NULL;
+      tree name = TYPE_NAME (enumtype);
+
+      if (name)
+       b = I_TAG_BINDING (name);
+
+      if (b)
+       b = b->shadowed;
+
+      if (b && B_IN_CURRENT_SCOPE (b))
+       {
+         tree vistype = b->decl;
+         bool different_types = false;
+
+         if ((1 != comptypes_check_different_types(enumtype, vistype,
&different_types))
+             || different_types)
+           {
+              warning(0, "conflicting redefinition of enum %qT",
vistype);
+           }
+       }
+    }
   return enumtype;
 }
 
@@ -9322,7 +9419,7 @@ finish_enum (tree enumtype, tree values, tree
attributes)
    Assignment of sequential values by default is handled here.  */
 
 tree
-build_enumerator (location_t decl_loc, location_t loc,
+build_enumerator (location_t decl_loc, location_t loc, tree enumtype,
                  struct c_enum_contents *the_enum, tree name, tree
value)
 {
   tree decl, type;
@@ -9409,7 +9506,7 @@ build_enumerator (location_t decl_loc, location_t
loc,
                                  >= TYPE_PRECISION
(integer_type_node)
                                  && TYPE_UNSIGNED (type)));
 
-  decl = build_decl (decl_loc, CONST_DECL, name, type);
+  decl = build_decl (decl_loc, CONST_DECL, name, enumtype);
   DECL_INITIAL (decl) = convert (type, value);
   pushdecl (decl);
 
@@ -9434,7 +9531,7 @@ c_simulate_enum_decl (location_t loc, const char
*name,
   unsigned int i;
   FOR_EACH_VEC_ELT (values, i, value)
     {
-      tree decl = build_enumerator (loc, loc, &the_enum,
+      tree decl = build_enumerator (loc, loc, enumtype, &the_enum,
                                    get_identifier (value->first),
                                    build_int_cst (integer_type_node,
                                                   value->second));
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 8df8f60ef21..40cb5bbbff4 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -3231,7 +3231,7 @@ c_parser_enum_specifier (c_parser *parser)
            }
          else
            enum_value = NULL_TREE;
-         enum_decl = build_enumerator (decl_loc, value_loc,
+         enum_decl = build_enumerator (decl_loc, value_loc, type,
                                        &the_enum, enum_id,
enum_value);
          if (enum_attrs)
            decl_attributes (&TREE_PURPOSE (enum_decl), enum_attrs,
0);
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index 2bcb9662620..e1cd86cfda7 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -578,7 +578,7 @@ extern int quals_from_declspecs (const struct
c_declspecs *);
 extern struct c_declarator *build_array_declarator (location_t, tree,
                                                    struct c_declspecs
*,
                                                    bool, bool);
-extern tree build_enumerator (location_t, location_t, struct
c_enum_contents *,
+extern tree build_enumerator (location_t, location_t, tree, struct
c_enum_contents *,
                              tree, tree);
 extern tree check_for_loop_decls (location_t, bool);
 extern void mark_forward_parm_decls (void);
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index 4f3611f1b89..5c5fede0bdd 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -1256,11 +1256,16 @@ comptypes_internal (const_tree type1,
const_tree type2, bool *enum_and_int_p,
     case ENUMERAL_TYPE:
     case RECORD_TYPE:
     case UNION_TYPE:
-      if (val != 1 && !same_translation_unit_p (t1, t2))
+      if (flag_tag_compat)
        {
          tree a1 = TYPE_ATTRIBUTES (t1);
          tree a2 = TYPE_ATTRIBUTES (t2);
 
+         if (ENUMERAL_TYPE != TREE_CODE (t1)
+             && (TYPE_REVERSE_STORAGE_ORDER (t1)
+                 != TYPE_REVERSE_STORAGE_ORDER (t2)))
+           return 0;
+
          if (! attribute_list_contained (a1, a2)
              && ! attribute_list_contained (a2, a1))
            break;
@@ -1344,41 +1349,6 @@ comp_target_types (location_t location, tree
ttl, tree ttr)
 
 /* Subroutines of `comptypes'.  */
 
-/* Determine whether two trees derive from the same translation unit.
-   If the CONTEXT chain ends in a null, that tree's context is still
-   being parsed, so if two trees have context chains ending in null,
-   they're in the same translation unit.  */
-
-bool
-same_translation_unit_p (const_tree t1, const_tree t2)
-{
-  while (t1 && TREE_CODE (t1) != TRANSLATION_UNIT_DECL)
-    switch (TREE_CODE_CLASS (TREE_CODE (t1)))
-      {
-      case tcc_declaration:
-       t1 = DECL_CONTEXT (t1); break;
-      case tcc_type:
-       t1 = TYPE_CONTEXT (t1); break;
-      case tcc_exceptional:
-       t1 = BLOCK_SUPERCONTEXT (t1); break;  /* assume block */
-      default: gcc_unreachable ();
-      }
-
-  while (t2 && TREE_CODE (t2) != TRANSLATION_UNIT_DECL)
-    switch (TREE_CODE_CLASS (TREE_CODE (t2)))
-      {
-      case tcc_declaration:
-       t2 = DECL_CONTEXT (t2); break;
-      case tcc_type:
-       t2 = TYPE_CONTEXT (t2); break;
-      case tcc_exceptional:
-       t2 = BLOCK_SUPERCONTEXT (t2); break;  /* assume block */
-      default: gcc_unreachable ();
-      }
-
-  return t1 == t2;
-}
-
 /* Allocate the seen two types, assuming that they are compatible. */
 
 static struct tagged_tu_seen_cache *
@@ -1457,6 +1427,12 @@ tagged_types_tu_compatible_p (const_tree t1,
const_tree t2,
   if (flag_isoc99 && TYPE_NAME (t1) != TYPE_NAME (t2))
     return 0;
 
+  if (flag_tag_compat
+      && (NULL_TREE == TYPE_NAME (t1)
+         || NULL_TREE == TYPE_NAME (t2)
+         || TYPE_NAME (t1) != TYPE_NAME (t2)))
+     return 0;
+
   /* C90 didn't say what happened if one or both of the types were
      incomplete; we choose to follow C99 rules here, which is that
they
      are compatible.  */
@@ -1606,7 +1582,7 @@ tagged_types_tu_compatible_p (const_tree t1,
const_tree t2,
                return 0;
              }
          }
-       tu->val = needs_warning ? 2 : 10;
+       tu->val = needs_warning ? 2 : 1;
        return tu->val;
       }
 
@@ -1614,6 +1590,12 @@ tagged_types_tu_compatible_p (const_tree t1,
const_tree t2,
       {
        struct tagged_tu_seen_cache *tu = alloc_tagged_tu_seen_cache
(t1, t2);
 
+       if (list_length (TYPE_FIELDS (t1)) != list_length (TYPE_FIELDS
(t2)))
+         {
+           tu->val = 0;
+           return 0;
+         }
+
        for (s1 = TYPE_FIELDS (t1), s2 = TYPE_FIELDS (t2);
             s1 && s2;
             s1 = DECL_CHAIN (s1), s2 = DECL_CHAIN (s2))
@@ -7046,10 +7028,12 @@ convert_for_assignment (location_t location,
location_t expr_loc, tree type,
 
   /* Aggregates in different TUs might need conversion.  */
   if ((codel == RECORD_TYPE || codel == UNION_TYPE)
-      && codel == coder
-      && comptypes (type, rhstype))
-    return convert_and_check (expr_loc != UNKNOWN_LOCATION
+      && codel == coder)
+    {
+      if (comptypes (TYPE_MAIN_VARIANT (type), TYPE_MAIN_VARIANT
(rhstype)))
+       return convert_and_check (expr_loc != UNKNOWN_LOCATION
                              ? expr_loc : location, type, rhs);
+    }
 
   /* Conversion to a transparent union or record from its member
types.
      This applies only to function arguments.  */
@@ -8159,6 +8143,15 @@ digest_init (location_t init_loc, tree type,
tree init, tree origtype,
           conversion.  */
        inside_init = convert (type, inside_init);
 
+      if (code == RECORD_TYPE || code == UNION_TYPE)
+       {
+         if (!comptypes (TYPE_MAIN_VARIANT (type), TYPE_MAIN_VARIANT
(TREE_TYPE (inside_init))))
+           {
+             error_init (init_loc, "invalid initializer %qT %qT",
type, TREE_TYPE (inside_init));
+             return error_mark_node;
+           }
+       }
+
       if (require_constant
          && TREE_CODE (inside_init) == COMPOUND_LITERAL_EXPR)
        {
@@ -10190,7 +10183,7 @@ initialize_elementwise_p (tree type, tree
value)
     return !VECTOR_TYPE_P (value_type);
 
   if (AGGREGATE_TYPE_P (type))
-    return type != TYPE_MAIN_VARIANT (value_type);
+      return !comptypes (type, TYPE_MAIN_VARIANT (value_type));
 
   return false;
 }
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d8095e3128f..fdbe625b389 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -2832,6 +2832,11 @@ the target (the default).  This option is not
supported for C++.
 @strong{Warning:} the @option{-fsso-struct} switch causes GCC to
generate
 code that is not binary compatible with code generated without it if
the
 specified endianness is not the native endianness of the target.
+
+@item -ftag-compat
+@opindex ftag-compat
+This option makes tagged types that are structurally equivalent
compatible
+and allows identical redeclarations of tagged types in the same scope.
 @end table
 
 @node C++ Dialect Options
diff --git a/gcc/ipa-free-lang-data.cc b/gcc/ipa-free-lang-data.cc
index f99f7be1c58..dc533fe43f6 100644
--- a/gcc/ipa-free-lang-data.cc
+++ b/gcc/ipa-free-lang-data.cc
@@ -254,8 +254,9 @@ fld_incomplete_type_of (tree t, class
free_lang_data_d *fld)
          else
            first = build_reference_type_for_mode (t2, TYPE_MODE (t),
                                                  
TYPE_REF_CAN_ALIAS_ALL (t));
-         gcc_assert (TYPE_CANONICAL (t2) != t2
-                     && TYPE_CANONICAL (t2) == TYPE_CANONICAL
(TREE_TYPE (t)));
+         gcc_assert (flag_tag_compat
+                     || (TYPE_CANONICAL (t2) != t2
+                         && TYPE_CANONICAL (t2) == TYPE_CANONICAL
(TREE_TYPE (t))));
          if (!fld->pset.add (first))
            add_tree_to_fld_list (first, fld);
          return fld_type_variant (first, t, fld);
diff --git a/gcc/testsuite/gcc.dg/asan/pr81460.c
b/gcc/testsuite/gcc.dg/asan/pr81460.c
index 00c1bb7c9f2..98ea40edb56 100644
--- a/gcc/testsuite/gcc.dg/asan/pr81460.c
+++ b/gcc/testsuite/gcc.dg/asan/pr81460.c
@@ -1,5 +1,6 @@
 /* PR sanitizer/80460 */
 /* { dg-do compile } */
+/* { dg-options "-fno-tag-compat" } */
 
 int
 f (int a, struct { int b[a]; } c) /* { dg-warning "anonymous struct
declared inside parameter list will not be visible outside of this
definition or declaration" } */
diff --git a/gcc/testsuite/gcc.dg/c99-tag-1.c
b/gcc/testsuite/gcc.dg/c99-tag-1.c
index d7011d2cbec..1b52234ee3f 100644
--- a/gcc/testsuite/gcc.dg/c99-tag-1.c
+++ b/gcc/testsuite/gcc.dg/c99-tag-1.c
@@ -1,7 +1,7 @@
 /* Test for handling of tags (6.7.2.3).  */
 /* Origin: Joseph Myers <js...@cam.ac.uk> */
 /* { dg-do compile } */
-/* { dg-options "-std=iso9899:1999 -pedantic-errors" } */
+/* { dg-options "-std=iso9899:1999 -pedantic-errors -fno-tag-compat" }
*/
 
 void
 foo (void)
diff --git a/gcc/testsuite/gcc.dg/c99-tag-2.c
b/gcc/testsuite/gcc.dg/c99-tag-2.c
index 22cf90e27d3..b12c7bcd964 100644
--- a/gcc/testsuite/gcc.dg/c99-tag-2.c
+++ b/gcc/testsuite/gcc.dg/c99-tag-2.c
@@ -2,7 +2,7 @@
    not match one declared in an outer scope.  */
 /* Origin: Joseph Myers <j...@polyomino.org.uk> */
 /* { dg-do compile } */
-/* { dg-options "-std=iso9899:1999 -pedantic-errors" } */
+/* { dg-options "-std=iso9899:1999 -pedantic-errors -fno-tag-compat" }
*/
 
 struct s;
 struct t { struct s *p; } x;
diff --git a/gcc/testsuite/gcc.dg/decl-3.c b/gcc/testsuite/gcc.dg/decl-
3.c
index cba0b906db3..66204270337 100644
--- a/gcc/testsuite/gcc.dg/decl-3.c
+++ b/gcc/testsuite/gcc.dg/decl-3.c
@@ -1,5 +1,6 @@
 /* PR c/9928 */
 /* { dg-do compile } */
+/* { dg-options "-fno-tag-compat" } */
 
 enum { CODES }; /* { dg-message "note: previous definition" } */
 enum { CODES }; /* { dg-error "conflicting types|redeclaration of
enumerator" } */
diff --git a/gcc/testsuite/gcc.dg/enum-redef-1.c
b/gcc/testsuite/gcc.dg/enum-redef-1.c
index b3fa6cbf8f1..837992f7441 100644
--- a/gcc/testsuite/gcc.dg/enum-redef-1.c
+++ b/gcc/testsuite/gcc.dg/enum-redef-1.c
@@ -1,3 +1,5 @@
+/* { dg-options "-fno-tag-compat" } */
+
 enum a { A };
 enum a { B }; /* { dg-bogus "nested redefinition" } */
 /* { dg-error "redeclaration of 'enum a'" "" { target *-*-* } .-1 } */
diff --git a/gcc/testsuite/gcc.dg/parm-incomplete-1.c
b/gcc/testsuite/gcc.dg/parm-incomplete-1.c
index 02d97b933f4..fa001d16662 100644
--- a/gcc/testsuite/gcc.dg/parm-incomplete-1.c
+++ b/gcc/testsuite/gcc.dg/parm-incomplete-1.c
@@ -6,7 +6,7 @@
    C99 6.7.5.3); the precise rules are unclear.  */
 /* Origin: Joseph Myers <j...@polyomino.org.uk> */
 /* { dg-do compile } */
-/* { dg-options "" } */
+/* { dg-options "-fno-tag-compat" } */
 
 struct s;
 void f (struct s);
diff --git a/gcc/testsuite/gcc.dg/pr17188-1.c
b/gcc/testsuite/gcc.dg/pr17188-1.c
index 522a14f7d75..bb31ba30b5d 100644
--- a/gcc/testsuite/gcc.dg/pr17188-1.c
+++ b/gcc/testsuite/gcc.dg/pr17188-1.c
@@ -3,7 +3,7 @@
    diagnosed.  Bug 17188.  */
 /* Origin: Joseph Myers <j...@polyomino.org.uk> */
 /* { dg-do compile } */
-/* { dg-options "" } */
+/* { dg-options "-fno-tag-compat" } */
 
 struct s0 { }; /* { dg-message "note: originally defined here" } */
 struct s0;
diff --git a/gcc/testsuite/gcc.dg/pr18809-1.c
b/gcc/testsuite/gcc.dg/pr18809-1.c
index 5be41809da6..d7f55feae61 100644
--- a/gcc/testsuite/gcc.dg/pr18809-1.c
+++ b/gcc/testsuite/gcc.dg/pr18809-1.c
@@ -1,6 +1,7 @@
 /* PR c/18809 */
 /* Origin: Andrew Pinski <pins...@gcc.gnu.org> */
 
+/* { dg-options "-pedantic-errors -fno-tag-compat" } */
 /* { dg-do compile } */
 
 void foo(enum E e) {}   /* { dg-error "forward ref" "forward" } */
diff --git a/gcc/testsuite/gcc.dg/pr27953.c
b/gcc/testsuite/gcc.dg/pr27953.c
index 99ae0a3aa83..45710f722ab 100644
--- a/gcc/testsuite/gcc.dg/pr27953.c
+++ b/gcc/testsuite/gcc.dg/pr27953.c
@@ -1,4 +1,5 @@
 /* PR c/27953 */
+/* { dg-options "-fno-tag-compat" } */
 
 void foo(struct A a) {} /* { dg-line foo_first } */
 /* { dg-warning "declared inside parameter list" "inside" { target *-
*-* } .-1 } */
diff --git a/gcc/testsuite/gcc.dg/pr39084.c
b/gcc/testsuite/gcc.dg/pr39084.c
index ff731492154..776fbea6750 100644
--- a/gcc/testsuite/gcc.dg/pr39084.c
+++ b/gcc/testsuite/gcc.dg/pr39084.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O2" } */
+/* { dg-options "-O2 -fno-tag-compat" } */
 
 struct color { int i; }; /* { dg-message "note: originally defined
here" } */
 static const struct color col;
diff --git a/gcc/testsuite/gcc.dg/pr68533.c
b/gcc/testsuite/gcc.dg/pr68533.c
index 49e67a96168..89c03998fdf 100644
--- a/gcc/testsuite/gcc.dg/pr68533.c
+++ b/gcc/testsuite/gcc.dg/pr68533.c
@@ -1,6 +1,6 @@
 /* PR c/68533 */
 /* { dg-do compile } */
-/* { dg-options "" } */
+/* { dg-options "-fno-tag-compat" } */
 
 struct T { int t; };
 
diff --git a/gcc/testsuite/gcc.dg/pr79983.c
b/gcc/testsuite/gcc.dg/pr79983.c
index 1e292d42108..c9fe72f9169 100644
--- a/gcc/testsuite/gcc.dg/pr79983.c
+++ b/gcc/testsuite/gcc.dg/pr79983.c
@@ -1,6 +1,6 @@
 /* PR c/79983 */
 /* { dg-do compile } */
-/* { dg-options "" } */
+/* { dg-options "-fno-tag-compat" } */
 
 struct S;
 struct S { int i; }; /* { dg-message "originally defined here" } */
diff --git a/gcc/testsuite/gcc.dg/pr89211.c
b/gcc/testsuite/gcc.dg/pr89211.c
index cf721aa5f62..e2d40aaaf91 100644
--- a/gcc/testsuite/gcc.dg/pr89211.c
+++ b/gcc/testsuite/gcc.dg/pr89211.c
@@ -1,5 +1,6 @@
 /* PR c/89211 */
 /* { dg-do compile } */
+/* { dg-options "-fno-tag-compat" } */
 
 void foo ();
 void foo ()
diff --git a/gcc/testsuite/gcc.dg/tag-compat.c
b/gcc/testsuite/gcc.dg/tag-compat.c
new file mode 100644
index 00000000000..54869ea189f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat.c
@@ -0,0 +1,32 @@
+/*
+ * { dg-do run }
+ * { dg-options "-ftag-compat" }
+ */
+
+#define product_type(T, A, B) \
+struct product_ ## T { A a ; B b ; }
+#define sum_type(T, A, B) \
+struct sum_ ## T { _Bool flag ; union { A a ; B b ; } ; }
+
+float foo1(product_type(iSfd_, int, sum_type(fd, float, double)) x) 
+{
+       return x.b.a;
+}
+
+static void test1(void)
+{
+       product_type(iSfd_, int, sum_type(fd, float, double)) y = { 3,
{ 1, { .a = 1. } } };
+       product_type(iSfd_, int, sum_type(fd, float, double)) z = y;
+       product_type(iSfd_, int, sum_type(fd, float, double)) *zp =
&y;
+       float a = foo1(y);
+       product_type(iSid_, int, sum_type(id, int, double)) *wp = &y;
/* { dg-warning "incompatible pointer type" } */
+       float b = foo1(y);
+       product_type(iSid_, int, sum_type(id, int, double)) w = *wp;
+       (void)a; (void)b; (void)z; (void)zp; (void)w; (void)wp;
+}
+
+int main()
+{
+       test1();
+}
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat10.c
b/gcc/testsuite/gcc.dg/tag-compat10.c
new file mode 100644
index 00000000000..535793f6f6c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat10.c
@@ -0,0 +1,15 @@
+/*
+ * { dg-do compile }
+ * { dg-options "-ftag-compat" }
+ */
+
+
+extern struct __attribute__(( aligned (16) )) foo { int x; } x;
+extern struct bar { float x; } y;
+
+void test(void)
+{
+  extern struct __attribute__(( aligned (8) )) foo { int x; }
x;      /* { dg-error "conflicting types" } */
+  extern struct bar { int x; }
y;                                      /* { dg-error "conflicting types" } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat11.c
b/gcc/testsuite/gcc.dg/tag-compat11.c
new file mode 100644
index 00000000000..a83e6efb699
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat11.c
@@ -0,0 +1,10 @@
+/*
+ * { dg-do compile }
+ * { dg-options "-ftag-compat" }
+ */
+
+
+extern struct { int x; } a;
+extern struct { int x; } a;    /* { dg-error "conflicting types" } */
+
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat12.c
b/gcc/testsuite/gcc.dg/tag-compat12.c
new file mode 100644
index 00000000000..f605b24267a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat12.c
@@ -0,0 +1,76 @@
+/*
+ * { dg-do run }
+ * { dg-options "-ftag-compat -O2" }
+ */
+
+
+typedef struct { int x; } foo_t;
+
+int test_foo(foo_t* a, void* b)
+{
+       a->x = 1;
+
+       struct { int x; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+
+struct bar { int x; int f[]; };
+
+int test_bar1(struct bar* a, void* b)
+{
+       a->x = 1;
+
+       struct bar { int x; int f[]; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+int test_bar2(struct bar* a, void* b)
+{
+       a->x = 1;
+
+       struct bar { int x; int f[0]; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+int test_bar3(struct bar* a, void* b)
+{
+       a->x = 1;
+
+       struct bar { int x; int f[1]; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+
+
+int main()
+{
+       foo_t y;
+
+       // this works, but is not guaranteed by C
+       if (2 == test_foo(&y, &y))
+               __builtin_abort();
+
+       struct bar z;
+
+       if (2 != test_bar1(&z, &z))
+               __builtin_abort();
+
+       if (2 != test_bar2(&z, &z))
+               __builtin_abort();
+
+       if (2 == test_bar3(&z, &z))
+               __builtin_abort();
+       
+       return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat2.c
b/gcc/testsuite/gcc.dg/tag-compat2.c
new file mode 100644
index 00000000000..20dc1a9c894
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat2.c
@@ -0,0 +1,47 @@
+/*
+ * { dg-do compile }
+ * { dg-options "-ftag-compat" }
+ */
+
+typedef struct bar { int x; } X;
+typedef struct bar { float x; } Y; /* { dg-warning "redefinition of
struct or union" } */
+
+void test(void)
+{
+       struct foo { int x; };
+       struct foo { float x; }; /* { dg-warning "redefinition of
struct or union" } */
+}
+
+struct aa { int a; };
+
+void f(void)
+{
+       typedef struct aa A;
+       struct bb { struct aa a; } x;
+       struct aa { int a; };
+       typedef struct aa A;
+       struct bb { struct aa a; } y;
+       (void)x; (void)y;
+}
+
+
+union cc { int x; float y; } z;
+union cc { int x; float y; } z1;
+union cc { float y; int x; } z2;
+
+
+
+
+
+void g(void)
+{
+       struct s { int a; };
+       struct s { int a; } x0;
+       struct p { struct s c; } y1 = { x0 };
+       struct p { struct s { int a; } c; } y = { x0 };
+}
+
+
+
+
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat3.c
b/gcc/testsuite/gcc.dg/tag-compat3.c
new file mode 100644
index 00000000000..5cbee4c9f38
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat3.c
@@ -0,0 +1,49 @@
+/*
+ * { dg-do compile }
+ * { dg-options "-ftag-compat" }
+ */
+
+
+enum aa { A = 1 } *a;
+enum bb { B = 1 } *b;
+
+void test(void)
+{
+  enum aa { A = 1 } *c = a;
+  enum bb { B = 2 } *d = b;    /* { dg-warning "incompatible pointer
type" } */
+}
+
+enum cc { C = 1 };
+enum cc { D = 1 };             /* { dg-warning "conflicting
redefinition" } */      
+
+enum dd { E = 1 };
+enum dd { E = 2 };             /* { dg-warning "conflicting
redefinition" } */      
+                               /* { dg-error "redeclaration of
enumerator" "" { target *-*-* } .-1 } */        
+
+
+
+void test2(void)
+{
+  enum ee *a;
+  enum ee { F = 2 } *b;
+  b = a;
+}
+
+
+enum ff { G = 2 };
+enum gg { G = 2 };             /* { dg-error "redeclaration of
enumerator" } */
+enum g2 { G = 3 };             /* { dg-error "redeclaration of
enumerator" } */
+
+enum hh { H = 1, H = 1 };      /* { dg-error "redeclaration of
enumerator" } */
+
+enum ss { K = 2 };
+enum ss { K = 2 };
+
+enum tt { R = 2 } TT;
+enum tt {
+       R = _Generic(&TT, enum tt*: 2, default: 0)
+};
+
+
+
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat4.c
b/gcc/testsuite/gcc.dg/tag-compat4.c
new file mode 100644
index 00000000000..3d48767d7e0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat4.c
@@ -0,0 +1,29 @@
+/*
+ * { dg-do compile }
+ * { dg-options "-ftag-compat" }
+ */
+
+
+typedef struct p { int a; } pd_t;
+typedef struct p { int a; } pd_t;
+
+
+void test1(void)
+{
+  pd_t y0;
+  struct p { int a; } x;
+  y0 = x;
+}
+
+void test2(void)
+{
+  struct p { int a; } x;
+  struct p y0 = x;
+}
+
+void test3(void)
+{
+  struct p { int a; } x;
+  pd_t y0 = x;
+}
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat5.c
b/gcc/testsuite/gcc.dg/tag-compat5.c
new file mode 100644
index 00000000000..feeaeb78f57
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat5.c
@@ -0,0 +1,15 @@
+/*
+ * { dg-do compile }
+ * { dg-options "-ftag-compat" }
+ */
+
+
+extern struct foo { int x; } x;
+extern struct bar { float x; } y;
+
+void test(void)
+{
+  extern struct foo { int x; } x;
+  extern struct bar { int x; } y;      /* { dg-error "conflicting
types" } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat6.c
b/gcc/testsuite/gcc.dg/tag-compat6.c
new file mode 100644
index 00000000000..98ebab7f33d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat6.c
@@ -0,0 +1,48 @@
+/*
+ * { dg-do run }
+ * { dg-options "-ftag-compat -O2" }
+ */
+
+
+struct foo { int x; };
+
+int test_foo(struct foo* a, void* b)
+{
+       a->x = 1;
+
+       struct foo { int x; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+
+enum bar { A = 1, B = 3 };
+
+int test_bar(enum bar* a, void* b)
+{
+       *a = A;
+
+       enum bar { A = 1, B = 3 }* p = b;
+       *p = B;
+
+       return *a;
+}
+
+
+int main()
+{
+       struct foo y;
+
+       if (2 != test_foo(&y, &y))
+               __builtin_abort();
+
+       enum bar z;
+
+       if (A == test_bar(&z, &z))
+               __builtin_abort();
+
+       return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat7.c
b/gcc/testsuite/gcc.dg/tag-compat7.c
new file mode 100644
index 00000000000..e3550fcfee7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat7.c
@@ -0,0 +1,73 @@
+/*
+ * { dg-do run }
+ * { dg-options "-ftag-compat -O2" }
+ */
+
+
+struct foo { int x; };
+
+int test_foo1(struct foo* a, void* b)
+{
+       a->x = 1;
+
+       struct foo { int x; int y; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+int test_foo2(struct foo* a, void* b)
+{
+       a->x = 1;
+
+       struct fox { int x; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+enum bar { A = 1, B = 3, C = 5, D = 9 };
+
+int test_bar1(enum bar* a, void* b)
+{
+       *a = A;
+
+       enum bar { A = 1, B = 3, C = 6, D = 9 }* p = b;
+       *p = B;
+
+       return *a;
+}
+
+int test_bar2(enum bar* a, void* b)
+{
+       *a = A;
+
+       enum baX { A = 1, B = 3, C = 5, D = 9 }* p = b;
+       *p = B;
+
+       return *a;
+}
+
+
+int main()
+{
+       struct foo y;
+
+       if (1 != test_foo1(&y, &y))
+               __builtin_abort();
+
+       if (1 != test_foo2(&y, &y))
+               __builtin_abort();
+
+       enum bar z;
+
+       if (A == test_bar1(&z, &z))
+               __builtin_abort();
+
+       if (A == test_bar2(&z, &z))
+               __builtin_abort();
+
+       return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat8.c
b/gcc/testsuite/gcc.dg/tag-compat8.c
new file mode 100644
index 00000000000..210cb39839a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat8.c
@@ -0,0 +1,48 @@
+/*
+ * { dg-do run }
+ * { dg-options "-ftag-compat -flto -O2" }
+ */
+
+
+struct foo { int x; };
+
+int test_foo(struct foo* a, void* b)
+{
+       a->x = 1;
+
+       struct foo { int x; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+
+enum bar { A = 1, B = 3 };
+
+int test_bar(enum bar* a, void* b)
+{
+       *a = A;
+
+       enum bar { A = 1, B = 3 }* p = b;
+       *p = B;
+
+       return *a;
+}
+
+
+int main()
+{
+       struct foo y;
+
+       if (2 != test_foo(&y, &y))
+               __builtin_abort();
+
+       enum bar z;
+
+       if (A == test_bar(&z, &z))
+               __builtin_abort();
+
+       return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/tag-compat9.c
b/gcc/testsuite/gcc.dg/tag-compat9.c
new file mode 100644
index 00000000000..5bf5dd5fe4f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tag-compat9.c
@@ -0,0 +1,73 @@
+/*
+ * { dg-do run }
+ * { dg-options "-ftag-compat -flto -O2" }
+ */
+
+
+struct foo { int x; };
+
+int test_foo1(struct foo* a, void* b)
+{
+       a->x = 1;
+
+       struct foo { int x; int y; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+int test_foo2(struct foo* a, void* b)
+{
+       a->x = 1;
+
+       struct fox { int x; }* p = b;
+       p->x = 2;
+
+       return a->x;
+}
+
+enum bar { A = 1, B = 3, C = 5, D = 9 };
+
+int test_bar1(enum bar* a, void* b)
+{
+       *a = A;
+
+       enum bar { A = 1, B = 3, C = 6, D = 9 }* p = b;
+       *p = B;
+
+       return *a;
+}
+
+int test_bar2(enum bar* a, void* b)
+{
+       *a = A;
+
+       enum baX { A = 1, B = 3, C = 5, D = 9 }* p = b;
+       *p = B;
+
+       return *a;
+}
+
+
+int main()
+{
+       struct foo y;
+
+       if (1 != test_foo1(&y, &y))
+               __builtin_abort();
+
+       if (1 != test_foo2(&y, &y))
+               __builtin_abort();
+
+       enum bar z;
+
+       if (A == test_bar1(&z, &z))
+               __builtin_abort();
+
+       if (A == test_bar2(&z, &z))
+               __builtin_abort();
+
+       return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/vla-11.c b/gcc/testsuite/gcc.dg/vla-
11.c
index 1504853a55a..982383a980a 100644
--- a/gcc/testsuite/gcc.dg/vla-11.c
+++ b/gcc/testsuite/gcc.dg/vla-11.c
@@ -4,7 +4,7 @@
    these cases).  */
 /* Origin: Joseph Myers <jos...@codesourcery.com> */
 /* { dg-do compile } */
-/* { dg-options "-std=c99 -pedantic-errors" } */
+/* { dg-options "-std=c99 -pedantic-errors -fno-tag-compat" } */
 
 void foo11a(int x[sizeof(int *(*)[*])]);       /* { dg-warning "not
in a declaration" } */
 void foo11b(__SIZE_TYPE__ x, int y[(__UINTPTR_TYPE__)(int
(*)[*])x]);     /* { dg-warning "not in a declaration" } */
diff --git a/gcc/testsuite/gcc.dg/vla-stexp-2.c
b/gcc/testsuite/gcc.dg/vla-stexp-2.c
index 9f1512567f0..555c31d2a47 100644
--- a/gcc/testsuite/gcc.dg/vla-stexp-2.c
+++ b/gcc/testsuite/gcc.dg/vla-stexp-2.c
@@ -1,6 +1,6 @@
 /* PR101838 */
 /* { dg-do run } */
-/* { dg-options "-Wpedantic -O0" } */
+/* { dg-options "-Wpedantic -O0 -fno-tag-compat" } */
 /* { dg-require-effective-target alloca } */
 
 




Reply via email to