diff mbox series

[V4,3/4] c23: aliasing of compatible tagged types

Message ID 7a7036f0ac5d5bb1644f723ff0b30b81f8fca1c6.camel@tugraz.at
State New
Headers show
Series None | expand

Commit Message

Martin Uecker Nov. 27, 2023, 1:16 p.m. UTC
(this mostly got an extended description and more
comments, also tests were reorganized)



c23: aliasing of compatible tagged types

Tell the backend which types are equivalent by setting
TYPE_CANONICAL to one struct in the set of equivalent
structs.  Structs are considered equivalent by ignoring
all sizes of arrays nested in types below field level.

The following two structs are incompatible and lvalues
with these types can be assumed not to alias:

 struct foo { int a[3]; };
 struct foo { int a[4]; };

The following two structs are also incompatible, but
will get the same TYPE_CANONICAL and it is then not
exploited that lvalues with those types can not alias:

 struct bar { int (*p)[3]; };
 struct bar { int (*p)[4]; };

The reason is that both are compatible to

 struct bar { int (*p)[]; };

and therefore are in the same equivalence class.  For
the same reason all enums with the same underyling type
are in the same equivalence class.  Tests are added
for the expected aliasing behavior with optimization.

gcc/c:
	* c-decl.cc (c_struct_hasher): Hash stable for struct
	types.
	(c_struct_hasher::hash, c_struct_hasher::equal): New
	functions.
	(finish_struct): Set TYPE_CANONICAL to first struct in
	equivalence class.
	* c-objc-common.cc (c_get_alias_set): Let structs or
	unions with variable size alias anything.
	* c-tree.h (comptypes_equiv): New prototype.
	* c-typeck.cc (comptypes_equiv): New function.
	(comptypes_internal): Implement equivalence mode.
	(tagged_types_tu_compatible): Implement equivalence mode.

gcc/testsuite:
	* gcc.dg/c23-tag-2.c: Activate.
	* gcc.dg/c23-tag-6.c: Activate.
	* gcc.dg/c23-tag-alias-1.c: New test.
	* gcc.dg/c23-tag-alias-2.c: New test.
	* gcc.dg/gnu23-tag-alias-1.c: New test.
	* gcc.dg/gnu23-tag-alias-2.c: New test.
	* gcc.dg/gnu23-tag-alias-3.c: New test.
	* gcc.dg/gnu23-tag-alias-4.c: New test.
	* gcc.dg/gnu23-tag-alias-5.c: New test.
	* gcc.dg/gnu23-tag-alias-6.c: New test.
	* gcc.dg/gnu23-tag-alias-7.c: New test.
---
 gcc/c/c-decl.cc                          |  51 ++++++++++-
 gcc/c/c-objc-common.cc                   |   5 ++
 gcc/c/c-tree.h                           |   1 +
 gcc/c/c-typeck.cc                        |  31 +++++++
 gcc/testsuite/gcc.dg/c23-tag-2.c         |   2 +-
 gcc/testsuite/gcc.dg/c23-tag-5.c         |   2 +-
 gcc/testsuite/gcc.dg/c23-tag-alias-1.c   |  49 +++++++++++
 gcc/testsuite/gcc.dg/c23-tag-alias-2.c   |  50 +++++++++++
 gcc/testsuite/gcc.dg/c23-tag-alias-3.c   |  32 +++++++
 gcc/testsuite/gcc.dg/c23-tag-alias-4.c   |  54 ++++++++++++
 gcc/testsuite/gcc.dg/gnu23-tag-alias-1.c |  33 +++++++
 gcc/testsuite/gcc.dg/gnu23-tag-alias-2.c |  85 ++++++++++++++++++
 gcc/testsuite/gcc.dg/gnu23-tag-alias-3.c |  83 ++++++++++++++++++
 gcc/testsuite/gcc.dg/gnu23-tag-alias-4.c |  36 ++++++++
 gcc/testsuite/gcc.dg/gnu23-tag-alias-5.c | 107 +++++++++++++++++++++++
 gcc/testsuite/gcc.dg/gnu23-tag-alias-6.c |  60 +++++++++++++
 gcc/testsuite/gcc.dg/gnu23-tag-alias-7.c |  93 ++++++++++++++++++++
 17 files changed, 771 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/c23-tag-alias-1.c
 create mode 100644 gcc/testsuite/gcc.dg/c23-tag-alias-2.c
 create mode 100644 gcc/testsuite/gcc.dg/c23-tag-alias-3.c
 create mode 100644 gcc/testsuite/gcc.dg/c23-tag-alias-4.c
 create mode 100644 gcc/testsuite/gcc.dg/gnu23-tag-alias-1.c
 create mode 100644 gcc/testsuite/gcc.dg/gnu23-tag-alias-2.c
 create mode 100644 gcc/testsuite/gcc.dg/gnu23-tag-alias-3.c
 create mode 100644 gcc/testsuite/gcc.dg/gnu23-tag-alias-4.c
 create mode 100644 gcc/testsuite/gcc.dg/gnu23-tag-alias-5.c
 create mode 100644 gcc/testsuite/gcc.dg/gnu23-tag-alias-6.c
 create mode 100644 gcc/testsuite/gcc.dg/gnu23-tag-alias-7.c

Comments

Joseph Myers Dec. 14, 2023, 9:10 p.m. UTC | #1
On Mon, 27 Nov 2023, Martin Uecker wrote:

> diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
> index a5dd9a37944..ece5b6a5d26 100644
> --- a/gcc/c/c-tree.h
> +++ b/gcc/c/c-tree.h
> @@ -758,6 +758,7 @@ extern tree require_complete_type (location_t, tree);
>  extern bool same_translation_unit_p (const_tree, const_tree);
>  extern int comptypes (tree, tree);
>  extern bool comptypes_same_p (tree, tree);
> +extern int comptypes_equiv_p (tree, tree);

This function should return bool.

> @@ -1250,6 +1266,9 @@ comptypes_internal (const_tree type1, const_tree type2,
>  
>  	if ((d1 == NULL_TREE) != (d2 == NULL_TREE))
>  	  data->different_types_p = true;
> +	/* Ignore size mismatches.  */
> +	if (data->equiv)
> +	  return 1;
>  	/* Sizes must match unless one is missing or variable.  */
>  	if (d1 == NULL_TREE || d2 == NULL_TREE || d1 == d2)
>  	  return true;
> @@ -1467,6 +1486,9 @@ tagged_types_tu_compatible_p (const_tree t1, const_tree t2,
>  	if (list_length (TYPE_FIELDS (t1)) != list_length (TYPE_FIELDS (t2)))
>  	  return false;
>  
> +	if (data->equiv && (C_TYPE_VARIABLE_SIZE (t1) || C_TYPE_VARIABLE_SIZE (t2)))
> +	  return 0;
> +
>  	for (s1 = TYPE_FIELDS (t1), s2 = TYPE_FIELDS (t2);
>  	     s1 && s2;
>  	     s1 = DECL_CHAIN (s1), s2 = DECL_CHAIN (s2))
> @@ -1486,6 +1508,15 @@ tagged_types_tu_compatible_p (const_tree t1, const_tree t2,
>  		&& simple_cst_equal (DECL_FIELD_BIT_OFFSET (s1),
>  				     DECL_FIELD_BIT_OFFSET (s2)) != 1)
>  	      return false;
> +
> +	    tree st1 = TYPE_SIZE (TREE_TYPE (s1));
> +	    tree st2 = TYPE_SIZE (TREE_TYPE (s2));
> +
> +	    if (data->equiv
> +		&& st1 && TREE_CODE (st1) == INTEGER_CST
> +		&& st2 && TREE_CODE (st2) == INTEGER_CST
> +		&& !tree_int_cst_equal (st1, st2))
> +	     return 0;

And these functions do return bool, so you should use true and false 
instead of 1 and 0.

> +/* The structs are incompatible so can be assumed not to
> + * alias, but this is not exploited.  So do not check for 
> + * this below but check the warning about incompatibility.  */
> +
> +int test_bar3(struct bar* a, void* b)
> +{
> +	a->x = 1;
> +
> +	struct bar { int x; int f[1]; }* p = b;
> +	struct bar* q = a;			/* { dg-warning "incompatible" } */

I expect you'll now need -fpermissive or 
-Wno-error=incompatible-pointer-types (this is an execution test so you 
need to stop this being an error, but see below).

> +	// allow both results here
> +	int r = test_bar3(&z, &z);
> +	if ((r != 2) && (r != 1))
> +        	__builtin_abort();

I don't think you should really be executing this call at all (aliasing 
not allowed means undefined behavior at runtime); better to put this in a 
separate compile-only test (which would also avoid the need for 
-fpermissive or -Wno-error=incompatible-pointer-types because once it's no 
longer an execution test, having an error is OK).
diff mbox series

Patch

diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index bcc09ba479e..68cba131704 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -634,6 +634,36 @@  public:
   auto_vec<tree> typedefs_seen;
 };
 
+
+/* Hash table for structs and unions.  */
+struct c_struct_hasher : ggc_ptr_hash<tree_node>
+{
+  static hashval_t hash (tree t);
+  static bool equal (tree, tree);
+};
+
+/* Hash an RECORD OR UNION.  */
+hashval_t
+c_struct_hasher::hash (tree type)
+{
+  inchash::hash hstate;
+
+  hstate.add_int (TREE_CODE (type));
+  hstate.add_object (TYPE_NAME (type));
+
+  return hstate.end ();
+}
+
+/* Compare two RECORD or UNION types.  */
+bool
+c_struct_hasher::equal (tree t1,  tree t2)
+{
+  return comptypes_equiv_p (t1, t2);
+}
+
+/* All tagged typed so that TYPE_CANONICAL can be set correctly.  */
+static GTY (()) hash_table<c_struct_hasher> *c_struct_htab;
+
 /* 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;
@@ -8713,7 +8743,8 @@  parser_xref_tag (location_t loc, enum tree_code code, tree name,
   ref = lookup_tag (code, name, has_enum_type_specifier, &refloc);
 
   /* If the visble type is still being defined, see if there is
-     an earlier definition (which may be complete).  */
+     an earlier definition (which may be complete).  We do not
+     have to loop because nested redefinitions are not allowed.  */
   if (flag_isoc23 && ref && C_TYPE_BEING_DEFINED (ref))
     {
       tree vis = previous_tag (ref);
@@ -9656,6 +9687,24 @@  finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
 
   C_TYPE_BEING_DEFINED (t) = 0;
 
+  /* Set type canonical based on equivalence class.  */
+  if (flag_isoc23)
+    {
+      if (NULL == c_struct_htab)
+	c_struct_htab = hash_table<c_struct_hasher>::create_ggc (61);
+
+      hashval_t hash = c_struct_hasher::hash (t);
+
+      tree *e = c_struct_htab->find_slot_with_hash (t, hash, INSERT);
+      if (*e)
+	TYPE_CANONICAL (t) = *e;
+      else
+	{
+	  TYPE_CANONICAL (t) = t;
+	  *e = t;
+	}
+    }
+
   tree incomplete_vars = C_TYPE_INCOMPLETE_VARS (TYPE_MAIN_VARIANT (t));
   for (x = TYPE_MAIN_VARIANT (t); x; x = TYPE_NEXT_VARIANT (x))
     {
diff --git a/gcc/c/c-objc-common.cc b/gcc/c/c-objc-common.cc
index c8f49aa2370..738afbad770 100644
--- a/gcc/c/c-objc-common.cc
+++ b/gcc/c/c-objc-common.cc
@@ -389,6 +389,11 @@  c_get_alias_set (tree t)
   if (TREE_CODE (t) == ENUMERAL_TYPE)
     return get_alias_set (ENUM_UNDERLYING_TYPE (t));
 
+  /* Structs with variable size can alias different incompatible
+     structs.  Let them alias anything.   */
+  if (RECORD_OR_UNION_TYPE_P (t) && C_TYPE_VARIABLE_SIZE (t))
+    return 0;
+
   return c_common_get_alias_set (t);
 }
 
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index a5dd9a37944..ece5b6a5d26 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -758,6 +758,7 @@  extern tree require_complete_type (location_t, tree);
 extern bool same_translation_unit_p (const_tree, const_tree);
 extern int comptypes (tree, tree);
 extern bool comptypes_same_p (tree, tree);
+extern int comptypes_equiv_p (tree, tree);
 extern int comptypes_check_different_types (tree, tree, bool *);
 extern int comptypes_check_enum_int (tree, tree, bool *);
 extern bool c_mark_addressable (tree, bool = false);
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index 8116c9b3e68..00eb65dbcce 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -1063,6 +1063,7 @@  struct comptypes_data {
   bool different_types_p;
   bool warning_needed;
   bool anon_field;
+  bool equiv;
 
   const struct tagged_tu_seen_cache* cache;
 };
@@ -1123,6 +1124,21 @@  comptypes_check_different_types (tree type1, tree type2,
 
   return ret ? (data.warning_needed ? 2 : 1) : 0;
 }
+
+
+/* Like comptypes, but if it returns nonzero for struct and union
+   types considered equivalent for aliasing purposes.  */
+
+int
+comptypes_equiv_p (tree type1, tree type2)
+{
+  struct comptypes_data data = { };
+  data.equiv = true;
+  bool ret = comptypes_internal (type1, type2, &data);
+
+  return ret;
+}
+
 
 /* Return true if TYPE1 and TYPE2 are compatible types for assignment
    or various other operations.  If they are compatible but a warning may
@@ -1250,6 +1266,9 @@  comptypes_internal (const_tree type1, const_tree type2,
 
 	if ((d1 == NULL_TREE) != (d2 == NULL_TREE))
 	  data->different_types_p = true;
+	/* Ignore size mismatches.  */
+	if (data->equiv)
+	  return 1;
 	/* Sizes must match unless one is missing or variable.  */
 	if (d1 == NULL_TREE || d2 == NULL_TREE || d1 == d2)
 	  return true;
@@ -1467,6 +1486,9 @@  tagged_types_tu_compatible_p (const_tree t1, const_tree t2,
 	if (list_length (TYPE_FIELDS (t1)) != list_length (TYPE_FIELDS (t2)))
 	  return false;
 
+	if (data->equiv && (C_TYPE_VARIABLE_SIZE (t1) || C_TYPE_VARIABLE_SIZE (t2)))
+	  return 0;
+
 	for (s1 = TYPE_FIELDS (t1), s2 = TYPE_FIELDS (t2);
 	     s1 && s2;
 	     s1 = DECL_CHAIN (s1), s2 = DECL_CHAIN (s2))
@@ -1486,6 +1508,15 @@  tagged_types_tu_compatible_p (const_tree t1, const_tree t2,
 		&& simple_cst_equal (DECL_FIELD_BIT_OFFSET (s1),
 				     DECL_FIELD_BIT_OFFSET (s2)) != 1)
 	      return false;
+
+	    tree st1 = TYPE_SIZE (TREE_TYPE (s1));
+	    tree st2 = TYPE_SIZE (TREE_TYPE (s2));
+
+	    if (data->equiv
+		&& st1 && TREE_CODE (st1) == INTEGER_CST
+		&& st2 && TREE_CODE (st2) == INTEGER_CST
+		&& !tree_int_cst_equal (st1, st2))
+	     return 0;
 	  }
 	return true;
 
diff --git a/gcc/testsuite/gcc.dg/c23-tag-2.c b/gcc/testsuite/gcc.dg/c23-tag-2.c
index 5dd4a21e9df..444605a93c6 100644
--- a/gcc/testsuite/gcc.dg/c23-tag-2.c
+++ b/gcc/testsuite/gcc.dg/c23-tag-2.c
@@ -1,4 +1,4 @@ 
-/* { dg-do compile { target { ! "*-*-*" } } }
+/* { dg-do compile }
  * { dg-options "-std=c23" }
  */
 
diff --git a/gcc/testsuite/gcc.dg/c23-tag-5.c b/gcc/testsuite/gcc.dg/c23-tag-5.c
index ff40d07aef1..bd56e1bda1a 100644
--- a/gcc/testsuite/gcc.dg/c23-tag-5.c
+++ b/gcc/testsuite/gcc.dg/c23-tag-5.c
@@ -1,4 +1,4 @@ 
-/* { dg-do run { target { ! "*-*-*" } } }
+/* { dg-do run }
  * { dg-options "-std=c23" }
  */
 
diff --git a/gcc/testsuite/gcc.dg/c23-tag-alias-1.c b/gcc/testsuite/gcc.dg/c23-tag-alias-1.c
new file mode 100644
index 00000000000..c92f942af88
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c23-tag-alias-1.c
@@ -0,0 +1,49 @@ 
+/* { dg-do run }
+ * { dg-options "-std=c23 -O2" }
+ */
+
+
+/* These tests check that redefinitions of tagged
+   types can alias the original definitions.  */
+
+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 (B != test_bar(&z, &z))
+		__builtin_abort();
+
+	return 0;
+}
+
diff --git a/gcc/testsuite/gcc.dg/c23-tag-alias-2.c b/gcc/testsuite/gcc.dg/c23-tag-alias-2.c
new file mode 100644
index 00000000000..64ff67d8552
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c23-tag-alias-2.c
@@ -0,0 +1,50 @@ 
+/* { dg-do run }
+ * { dg-options "-std=c23 -flto -O2" }
+ */
+
+/* These tests check that compatible definitions of
+   tagged types can alias the original definitions
+   with LTO.  */
+
+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 (B != test_bar(&z, &z))
+		__builtin_abort();
+
+	return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/c23-tag-alias-3.c b/gcc/testsuite/gcc.dg/c23-tag-alias-3.c
new file mode 100644
index 00000000000..b9fe6f3b407
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c23-tag-alias-3.c
@@ -0,0 +1,32 @@ 
+/* { dg-do run }
+ * { dg-options "-std=c23 -O2" }
+ */
+
+/* These tests check that definitions of enums with 
+ * the same underlying type can alias, even when
+ * they are not compatible.  */
+
+enum bar : long { A = 1, B = 3 };
+
+int test_bar(enum bar* a, void* b)
+{
+	*a = A;
+
+	enum foo : long { C = 2, D = 4 }* p = b;
+	*p = B;
+
+	return *a;
+}
+
+
+int main()
+{
+	enum bar z;
+
+	if (B != test_bar(&z, &z))
+		__builtin_abort();
+
+	return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/c23-tag-alias-4.c b/gcc/testsuite/gcc.dg/c23-tag-alias-4.c
new file mode 100644
index 00000000000..53272b7e457
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/c23-tag-alias-4.c
@@ -0,0 +1,54 @@ 
+/* { dg-do run }
+ * { dg-options "-std=c23 -O2" }
+ */
+
+
+/* Here we check that structs with flexible array
+ * members can alias a compatible redefinition.  */
+
+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;
+	struct bar* q = a;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+/* The structs are incompatible so can be assumed not to
+ * alias, but this is not exploited.  So do not check for 
+ * this below but check the warning about incompatibility.  */
+
+int test_bar3(struct bar* a, void* b)
+{
+	a->x = 1;
+
+	struct bar { int x; int f[1]; }* p = b;
+	struct bar* q = a;			/* { dg-warning "incompatible" } */
+	p->x = 2;
+
+	return a->x;
+}
+
+
+int main()
+{
+	struct bar z;
+
+	if (2 != test_bar1(&z, &z))
+        	__builtin_abort();
+
+	// allow both results here
+	int r = test_bar3(&z, &z);
+	if ((r != 2) && (r != 1))
+        	__builtin_abort();
+
+	return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/gnu23-tag-alias-1.c b/gcc/testsuite/gcc.dg/gnu23-tag-alias-1.c
new file mode 100644
index 00000000000..c51417f831a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gnu23-tag-alias-1.c
@@ -0,0 +1,33 @@ 
+/* { dg-do run }
+ * { dg-options "-std=gnu23 -O2" }
+ */
+
+/* Check that structs with flexible array member can alias.  */
+
+struct bar { int x; int f[]; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar2(struct bar* a, void* b)
+{
+	a->x = 1;
+
+	struct bar { int x; int f[0]; }* p = b;
+	struct bar* q = a;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+
+int main()
+{
+	struct bar z;
+
+	if (2 != test_bar2(&z, &z))
+		__builtin_abort();
+
+	return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/gnu23-tag-alias-2.c b/gcc/testsuite/gcc.dg/gnu23-tag-alias-2.c
new file mode 100644
index 00000000000..c09c3ca40a6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gnu23-tag-alias-2.c
@@ -0,0 +1,85 @@ 
+/* { dg-do run }
+ * { dg-options "-std=gnu23 -O2" }
+ */
+
+
+/* These tests check that incompatible definitions of
+   tagged types can be assumed not to alias and that
+   this is exploited during optimization.  */
+
+struct foo { int x; };
+
+[[gnu::noinline,gnu::noipa]]
+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;
+}
+
+[[gnu::noinline,gnu::noipa]]
+int test_foo2(struct foo* a, void* b)
+{
+	a->x = 1;
+
+	struct fox { int x; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+
+/* While these tests check that incompatible enums can still
+ * alias, although this is not required.   */
+
+enum bar { A = 1, B = 3, C = 5, D = 9 };
+
+[[gnu::noinline,gnu::noipa]]
+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;
+}
+
+[[gnu::noinline,gnu::noipa]]
+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 (B != test_bar1(&z, &z))
+		__builtin_abort();
+
+	if (B != test_bar2(&z, &z))
+		__builtin_abort();
+
+	return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/gnu23-tag-alias-3.c b/gcc/testsuite/gcc.dg/gnu23-tag-alias-3.c
new file mode 100644
index 00000000000..a07a1e6fa38
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gnu23-tag-alias-3.c
@@ -0,0 +1,83 @@ 
+/* { dg-do run }
+ * { dg-options "-std=gnu23 -flto -O2" }
+ */
+
+/* These tests check that incompatible definitions of
+   tagged types can be assumed not to alias and that
+   this is exploited during optimization with LTO.  */
+
+struct foo { int x; };
+
+[[gnu::noinline,gnu::noipa]]
+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;
+}
+
+[[gnu::noinline,gnu::noipa]]
+int test_foo2(struct foo* a, void* b)
+{
+	a->x = 1;
+
+	struct fox { int x; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+/* While tese tests check that incompatible definitions
+ * of enums can alias.  */
+
+enum bar { A = 1, B = 3, C = 5, D = 9 };
+
+[[gnu::noinline,gnu::noipa]]
+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;
+}
+
+[[gnu::noinline,gnu::noipa]]
+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 (B != test_bar1(&z, &z))
+		__builtin_abort();
+
+	if (B != test_bar2(&z, &z))
+		__builtin_abort();
+
+	return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/gnu23-tag-alias-4.c b/gcc/testsuite/gcc.dg/gnu23-tag-alias-4.c
new file mode 100644
index 00000000000..1ea3a883d0c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gnu23-tag-alias-4.c
@@ -0,0 +1,36 @@ 
+/* { dg-do run }
+ * { dg-options "-std=gnu23 -O2" }
+ */
+
+/* This test checks that an incompatible definition of
+ * a tagged type without tag can be assumed not to alias.  
+ * and that this is exploited during optimization.  */
+
+
+// not sure this is wise, but this was already like this before
+
+typedef struct { int x; } foo_t;
+
+[[gnu::noinline,gnu::noipa]]
+int test_foo(foo_t* a, void* b)
+{
+	a->x = 1;
+
+	struct { int x; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+int main()
+{
+	foo_t y;
+
+	if (1 != test_foo(&y, &y))
+		__builtin_abort();
+
+	return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/gnu23-tag-alias-5.c b/gcc/testsuite/gcc.dg/gnu23-tag-alias-5.c
new file mode 100644
index 00000000000..5a83397bd6d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gnu23-tag-alias-5.c
@@ -0,0 +1,107 @@ 
+/* { dg-do run }
+ * { dg-options "-std=gnu23 -O2" }
+ */
+
+/* This test checks that different field offsets imply
+ * that the types can be assumed not to alias
+ * and that this is exploited during optimization.  */
+
+
+struct bar0 { int x; int f[3]; int y; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar0(struct bar0* a, void* b)
+{
+	a->x = 1;
+
+	struct bar0 { int x; int f[4]; int y; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+/* While these tests check that different structs with different
+ * sizes in arrays pointed to by field members can alias,
+ * even though the types are incompatible.  */
+
+
+struct bar1 { int x; int (*f)[3]; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar1(struct bar1* a, void* b)
+{
+	a->x = 1;
+
+	struct bar1 { int x; int (*f)[3]; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+struct bar2 { int x; int (*f)[3]; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar2(struct bar2* a, void* b)
+{
+	a->x = 1;
+
+	struct bar2 { int x; int (*f)[4]; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+
+/* This test checks that different structs with pointers to
+ * different compatible arrays types can alias.  */
+
+
+struct bar3 { int x; int (*f)[3]; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar3(struct bar3* a, void* b)
+{
+	a->x = 1;
+
+	struct bar3 { int x; int (*f)[]; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+
+
+int main()
+{
+	// control
+
+	struct bar0 z0;
+
+	if (1 != test_bar0(&z0, &z0))
+		__builtin_abort();
+
+	// this could be different
+	struct bar1 z1;
+
+	if (2 != test_bar1(&z1, &z1))
+		__builtin_abort();
+
+	struct bar2 z2;
+
+	if (2 != test_bar2(&z2, &z2))
+		__builtin_abort();
+
+	struct bar3 z3;
+
+	if (2 != test_bar3(&z3, &z3))
+		__builtin_abort();
+
+
+	return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/gnu23-tag-alias-6.c b/gcc/testsuite/gcc.dg/gnu23-tag-alias-6.c
new file mode 100644
index 00000000000..78d2abf2ed9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gnu23-tag-alias-6.c
@@ -0,0 +1,60 @@ 
+/* { dg-do run }
+ * { dg-options "-std=gnu23 -O2" }
+ */
+
+
+
+/* Here we check that struct with a variable size (GNU extension)
+ * can alias a struct with a flexible array member or a struct with a
+ * fixed size array as last element.  */
+
+struct bar { int x; int f[]; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar4(struct bar* a, void* b)
+{
+	a->x = 1;
+
+	int n = 3;
+	struct bar { int x; int f[n]; }* p = b;
+	struct bar* q = a;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+struct foo { int x; int f[3]; };
+
+
+[[gnu::noinline,gnu::noipa]]
+int test_foo1(struct foo* a, void* b)
+{
+	a->x = 1;
+
+	int n = 3;
+	struct foo { int x; int f[n]; }* p = b;
+	struct foo* q = a;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+
+int main()
+{
+	struct bar z;
+
+	if (2 != test_bar4(&z, &z))
+		__builtin_abort();
+
+	struct foo y;
+
+	if (2 != test_foo1(&y, &y))
+		__builtin_abort();
+
+	return 0;
+}
+
+
diff --git a/gcc/testsuite/gcc.dg/gnu23-tag-alias-7.c b/gcc/testsuite/gcc.dg/gnu23-tag-alias-7.c
new file mode 100644
index 00000000000..d3fc4bd57e1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gnu23-tag-alias-7.c
@@ -0,0 +1,93 @@ 
+/* { dg-do run }
+ * { dg-options "-std=gnu23 -O2" }
+ */
+
+
+/* We check that the incompatible enums as fields lead to
+ * incompatible types that can be assumed not to alias
+ * and that this is exploited during optimization.  */
+
+struct bar1 { int x; enum A1 { X1 = 1 } f; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar1(struct bar1* a, void* b)
+{
+	a->x = 1;
+
+	struct bar1 { int x; enum A1 { X1 = 2 } f; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+struct bar2 { int x; enum A2 { X2 = 1 } f; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar2(struct bar2* a, void* b)
+{
+	a->x = 1;
+
+	struct bar2 { int x; enum B2 { X2 = 1 } f; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+
+struct bar3 { int x; enum A3 { X3 = 1 } f; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar3(struct bar3* a, void* b)
+{
+	a->x = 1;
+
+	struct bar3 { int x; enum A3 { Y3 = 1 } f; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+struct bar4 { int x; enum { Z4 = 1 } f; };
+
+[[gnu::noinline,gnu::noipa]]
+int test_bar4(struct bar4* a, void* b)
+{
+	a->x = 1;
+
+	struct bar4 { int x; enum { Z4 = 1 } f; }* p = b;
+	p->x = 2;
+
+	return a->x;
+}
+
+
+
+int main()
+{
+	struct bar1 z1;
+
+	if (1 != test_bar1(&z1, &z1))
+		__builtin_abort();
+
+	struct bar2 z2;
+
+	if (1 != test_bar2(&z2, &z2))
+		__builtin_abort();
+
+	struct bar3 z3;
+
+	if (1 != test_bar3(&z3, &z3))
+		__builtin_abort();
+
+	struct bar4 z4;
+
+	if (1 != test_bar4(&z4, &z4))
+		__builtin_abort();
+
+	return 0;
+}
+
+