[gcc r17-3086] gccrs: Implement compilation of #[repr(transparent)] structs

Arthur Cohen via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:723537f6c46bf2b8995ae3450f835ffe00f2a35e

commit r17-3086-g723537f6c46bf2b8995ae3450f835ffe00f2a35e
Author: Yap Zhi Heng <[email protected]>
Date:   Tue Jun 30 22:26:54 2026 +0800

    gccrs: Implement compilation of #[repr(transparent)] structs
    
    gcc/rust/ChangeLog:
    
            * backend/rust-compile-expr.h (CompileExpr::compile_transparent_field_access): New
            helper function.
            * backend/rust-compile-expr.cc (CompileExpr::compile_transparent_field_access):
            Implement helper function for accessing the field of #[repr(transparent)] ADTs.
            (CompileExpr::visit (HIR::StructExprStructFields)): Support struct field construction
            properly for #[repr(transparent)] structs.
            (CompileExpr::visit (HIR::FieldAccessExpr)): Implement proper compilation of field
            access for #[repr(transparent)] ADTs.
            * backend/rust-compile-type.cc (TyTyResolveCompile::visit (TyTy::ADTType)): Implement
            proper compilation of typing for #[repr(transparent)] ADTs.
    
    gcc/testsuite/ChangeLog:
    
            * rust/execute/torture/c_string.rs: Fix missing #[repr(transparent)]
            * rust/execute/torture/c_string_ensure_null_term.rs: Ditto.
            * rust/compile/c_string_null_byte_check.rs: Ditto.
    
    Signed-Off-By: Yap Zhi Heng <[email protected]>

Diff:
---
 gcc/rust/backend/rust-compile-expr.cc              | 53 +++++++++++++++------
 gcc/rust/backend/rust-compile-expr.h               |  3 ++
 gcc/rust/backend/rust-compile-type.cc              | 54 +++++++++++++++++-----
 .../rust/compile/c_string_null_byte_check.rs       |  1 +
 .../rust/compile/repr_transparent_fields2.rs       | 21 +++++++++
 .../rust/compile/transparent_struct_deref.rs       | 43 +++++++++++++++++
 gcc/testsuite/rust/execute/torture/c_string.rs     |  1 +
 .../execute/torture/c_string_ensure_null_term.rs   |  1 +
 8 files changed, 152 insertions(+), 25 deletions(-)

diff --git a/gcc/rust/backend/rust-compile-expr.cc b/gcc/rust/backend/rust-compile-expr.cc
index 55ff3263a117..4bcd11da4166 100644
--- a/gcc/rust/backend/rust-compile-expr.cc
+++ b/gcc/rust/backend/rust-compile-expr.cc
@@ -769,10 +769,22 @@ CompileExpr::visit (HIR::StructExprStructFields &struct_expr)
 
   if (!adt->is_enum ())
     {
-      translated
-	= Backend::constructor_expression (compiled_adt_type, adt->is_enum (),
-					   arguments, union_disriminator,
-					   struct_expr.get_locus ());
+      auto repr_kind = adt->get_repr_options ().repr_kind;
+      if (repr_kind == TyTy::ADTType::ReprKind::TRANSPARENT)
+	{
+	  translated
+	    = fold_build1_loc (struct_expr.get_locus (), VIEW_CONVERT_EXPR,
+			       compiled_adt_type, arguments.front ());
+	}
+      else
+	{
+	  translated
+	    = Backend::constructor_expression (compiled_adt_type,
+					       adt->is_enum (), arguments,
+					       union_disriminator,
+					       struct_expr.get_locus ());
+	}
+
       return;
     }
 
@@ -843,6 +855,15 @@ CompileExpr::visit (HIR::FieldAccessExpr &expr)
       bool ok = variant->lookup_field (expr.get_field_name ().as_string (),
 				       nullptr, &field_index);
       rust_assert (ok);
+
+      auto repr_kind = adt->get_repr_options ().repr_kind;
+      if (repr_kind == TyTy::ADTType::ReprKind::TRANSPARENT)
+	{
+	  translated
+	    = compile_transparent_field_access (variant, expr.get_locus (),
+						receiver_ref);
+	  return;
+	}
     }
   else if (receiver->get_kind () == TyTy::TypeKind::REF)
     {
@@ -859,16 +880,12 @@ CompileExpr::visit (HIR::FieldAccessExpr &expr)
 				       nullptr, &field_index);
       rust_assert (ok);
 
-      // TODO this check is only used for CStr, test again when we support
-      // compilation of #[repr(transparent)] structs
-      if (RS_DST_FLAG_P (TREE_TYPE (receiver_ref)))
+      auto repr_kind = adt->get_repr_options ().repr_kind;
+      if (repr_kind == TyTy::ADTType::ReprKind::TRANSPARENT)
 	{
-	  const TyTy::StructFieldType *field
-	    = variant->get_field_at_index (field_index);
-	  tree field_type
-	    = TyTyResolveCompile::compile (ctx, field->get_field_type ());
-	  translated = fold_build1_loc (expr.get_locus (), VIEW_CONVERT_EXPR,
-					field_type, receiver_ref);
+	  translated
+	    = compile_transparent_field_access (variant, expr.get_locus (),
+						receiver_ref);
 	  return;
 	}
       else
@@ -2114,6 +2131,16 @@ CompileExpr::compile_c_string_literal (const HIR::LiteralExpr &expr,
 					  expr.get_locus ());
 }
 
+tree
+CompileExpr::compile_transparent_field_access (TyTy::VariantDef *variant,
+					       location_t locus,
+					       tree source_expr)
+{
+  const TyTy::StructFieldType *field = variant->get_field_at_index (0);
+  tree field_type = TyTyResolveCompile::compile (ctx, field->get_field_type ());
+  return fold_build1_loc (locus, VIEW_CONVERT_EXPR, field_type, source_expr);
+}
+
 tree
 CompileExpr::type_cast_expression (tree type_to_cast_to, tree expr_tree,
 				   location_t location)
diff --git a/gcc/rust/backend/rust-compile-expr.h b/gcc/rust/backend/rust-compile-expr.h
index 90e1985c9930..ad5c483221a9 100644
--- a/gcc/rust/backend/rust-compile-expr.h
+++ b/gcc/rust/backend/rust-compile-expr.h
@@ -145,6 +145,9 @@ protected:
 			  const TyTy::ArrayType &array_tyty, tree array_type,
 			  HIR::ArrayElemsCopied &elems);
 
+  tree compile_transparent_field_access (TyTy::VariantDef *variant,
+					 location_t locus, tree source_expr);
+
 protected:
   tree generate_closure_function (HIR::ClosureExpr &expr,
 				  TyTy::ClosureType &closure_tyty,
diff --git a/gcc/rust/backend/rust-compile-type.cc b/gcc/rust/backend/rust-compile-type.cc
index e71d404f1fac..01f80da52a48 100644
--- a/gcc/rust/backend/rust-compile-type.cc
+++ b/gcc/rust/backend/rust-compile-type.cc
@@ -295,7 +295,35 @@ void
 TyTyResolveCompile::visit (const TyTy::ADTType &type)
 {
   tree type_record = error_mark_node;
-  if (!type.is_enum ())
+
+  TyTy::ADTType::ReprOptions repr = type.get_repr_options ();
+  if (repr.repr_kind == TyTy::ADTType::ReprKind::TRANSPARENT)
+    {
+      rust_assert (type.number_of_variants () == 1);
+      TyTy::VariantDef &variant = *type.get_variants ().at (0);
+
+      rust_assert (variant.num_fields () <= 1);
+      if (variant.num_fields () == 0)
+	{
+	  // 0-field transparent repr
+	  // Rustonomicon states that transparent structs should have a single
+	  // non-zero-sized field, but rustc compiles one with 0 fields happily
+	  // without errors, so not sure what's the correct treatment.
+	  //
+	  // For now, treat it as a unit struct
+	  type_record = Backend::struct_type ({});
+	}
+      else
+	{
+	  // single field transparent repr
+	  const TyTy::StructFieldType *field = variant.get_field_at_index (0);
+	  type_record
+	    = TyTyResolveCompile::compile (ctx, field->get_field_type ());
+	}
+    }
+
+  // compilation of non-transparent ADTs below
+  else if (!type.is_enum ())
     {
       rust_assert (type.number_of_variants () == 1);
 
@@ -442,22 +470,24 @@ TyTyResolveCompile::visit (const TyTy::ADTType &type)
   // TODO: "packed" should only narrow type alignment and "align" should only
   // widen it. Do we need to check and enforce this here, or is it taken care of
   // later on in the gcc middle-end?
-  TyTy::ADTType::ReprOptions repr = type.get_repr_options ();
-  if (repr.pack)
+  if (repr.repr_kind != TyTy::ADTType::ReprKind::TRANSPARENT)
     {
-      TYPE_PACKED (type_record) = 1;
-      if (repr.pack > 1)
+      if (repr.pack)
+	{
+	  TYPE_PACKED (type_record) = 1;
+	  if (repr.pack > 1)
+	    {
+	      SET_TYPE_ALIGN (type_record, repr.pack * 8);
+	      TYPE_USER_ALIGN (type_record) = 1;
+	    }
+	}
+      else if (repr.align)
 	{
-	  SET_TYPE_ALIGN (type_record, repr.pack * 8);
+	  SET_TYPE_ALIGN (type_record, repr.align * 8);
 	  TYPE_USER_ALIGN (type_record) = 1;
 	}
+      layout_type (type_record);
     }
-  else if (repr.align)
-    {
-      SET_TYPE_ALIGN (type_record, repr.align * 8);
-      TYPE_USER_ALIGN (type_record) = 1;
-    }
-  layout_type (type_record);
 
   std::string named_struct_str
     = type.get_ident ().path.get () + type.subst_as_string ();
diff --git a/gcc/testsuite/rust/compile/c_string_null_byte_check.rs b/gcc/testsuite/rust/compile/c_string_null_byte_check.rs
index 040ba9ad468f..89a4bcdcaeff 100644
--- a/gcc/testsuite/rust/compile/c_string_null_byte_check.rs
+++ b/gcc/testsuite/rust/compile/c_string_null_byte_check.rs
@@ -5,6 +5,7 @@
 type c_char = u8;
 
 #[lang = "CStr"]
+#[repr(transparent)]
 pub struct CStr {
     inner: [c_char]
 }
diff --git a/gcc/testsuite/rust/compile/repr_transparent_fields2.rs b/gcc/testsuite/rust/compile/repr_transparent_fields2.rs
new file mode 100644
index 000000000000..611cd851c408
--- /dev/null
+++ b/gcc/testsuite/rust/compile/repr_transparent_fields2.rs
@@ -0,0 +1,21 @@
+// { dg-additional-options "-fdump-tree-gimple" }
+#![feature(no_core)]
+#![no_core]
+
+struct NonTransparent {
+    foo: i32
+}
+
+#[repr(transparent)]
+struct Transparent {
+    foo: i32
+}
+
+fn main () -> i32 {
+    // { dg-final { scan-tree-dump-times {(?n)my_obj . 42;$} 1 gimple } }
+    let mut my_obj = Transparent { foo: 42 };
+    // { dg-final { scan-tree-dump-times {(?n)my_obj2.foo . 40;$} 1 gimple } }
+    let my_obj2 = NonTransparent { foo: 40 };
+    my_obj.foo -= 2;
+    my_obj.foo - my_obj2.foo
+}
\ No newline at end of file
diff --git a/gcc/testsuite/rust/compile/transparent_struct_deref.rs b/gcc/testsuite/rust/compile/transparent_struct_deref.rs
new file mode 100644
index 000000000000..97ca93017819
--- /dev/null
+++ b/gcc/testsuite/rust/compile/transparent_struct_deref.rs
@@ -0,0 +1,43 @@
+#![feature(no_core, intrinsics, staged_api, lang_items)]
+#![no_core]
+
+#[lang = "sized"]
+pub trait Sized {}
+
+// below's helper code copied from issue-1232.rs
+extern "rust-intrinsic" {
+    #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")]
+    fn offset<T>(dst: *const T, offset: isize) -> *const T;
+}
+
+#[lang = "const_ptr"]
+impl<T> *const T {
+    pub const unsafe fn offset(self, count: isize) -> *const T {
+        unsafe { offset(self, count) }
+    }
+
+    pub const unsafe fn add(self, count: usize) -> Self {
+        unsafe { self.offset(count as isize) }
+    }
+
+    pub const fn as_ptr(self) -> *const T {
+        self as *const T
+    }
+}
+
+#[repr(transparent)]
+pub struct Foo {
+    inner: i32
+}
+
+impl Foo {
+    pub const fn to_ptr(&self) -> *const i32 {
+        &self.inner as *const i32
+    }
+}
+
+pub fn main() -> i32 {
+    let a = Foo { inner: 67 };
+    let val = unsafe { a.to_ptr() };
+    unsafe { *val - 67 }
+}
diff --git a/gcc/testsuite/rust/execute/torture/c_string.rs b/gcc/testsuite/rust/execute/torture/c_string.rs
index 5aef4db0c21a..9f4cd5c036a5 100644
--- a/gcc/testsuite/rust/execute/torture/c_string.rs
+++ b/gcc/testsuite/rust/execute/torture/c_string.rs
@@ -10,6 +10,7 @@ extern "C" {
 type c_char = u8;
 
 #[lang = "CStr"]
+#[repr(transparent)]
 pub struct CStr {
     inner: [c_char]
 }
diff --git a/gcc/testsuite/rust/execute/torture/c_string_ensure_null_term.rs b/gcc/testsuite/rust/execute/torture/c_string_ensure_null_term.rs
index 26e0fed2173c..60da8a1dd9e3 100644
--- a/gcc/testsuite/rust/execute/torture/c_string_ensure_null_term.rs
+++ b/gcc/testsuite/rust/execute/torture/c_string_ensure_null_term.rs
@@ -33,6 +33,7 @@ extern "C" {
 type c_char = u8;
 
 #[lang = "CStr"]
+#[repr(transparent)]
 pub struct CStr {
     inner: [c_char]
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.