[gcc r17-3133] gccrs: Backport `cfg_select!` macro

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

commit r17-3133-g57a148f47e0d5ab56df6207a1372f4cc4b4c7792
Author: Yap Zhi Heng <[email protected]>
Date:   Mon Jul 27 20:47:35 2026 +0800

    gccrs: Backport `cfg_select!` macro
    
    This macro is used in newer versions of `core` to compile `c_char`, that is used to compile
    `CStr` which has widespread use in Rust for Linux.
    
    gcc/rust/ChangeLog:
    
            * expand/rust-macro-builtins.h (BuiltinMacro::CfgSelect): New enum variant.
            (MacroBuiltin::cfg_select_handler): New function definition.
            * expand/rust-macro-builtins.cc (MacroBuiltin::builtins): New cfg_select entry.
            (MacroBuiltin::builtin_transcribers): New cfg_select_handler entry.
            * expand/rust-macro-builtins-utility.cc (MacroBuiltin::cfg_select_handler): Implement
            parsing of compilation configuration predicates and expansion of block expressions based
            on which predicate was matched.
    
    gcc/testsuite/ChangeLog:
            * rust/compile/c_string_null_byte_check.rs: Update definition of `c_char`.
            * rust/execute/torture/c_string_ensure_null_term.rs: Ditto.
            * rust/execute/torture/c_string.rs: Ditto.
    
    Signed-off-by: Yap Zhi Heng <[email protected]>

Diff:
---
 gcc/rust/expand/rust-macro-builtins-utility.cc     | 134 +++++++++++++++++++++
 gcc/rust/expand/rust-macro-builtins.cc             |   2 +
 gcc/rust/expand/rust-macro-builtins.h              |   5 +
 .../rust/compile/c_string_null_byte_check.rs       |  33 ++++-
 gcc/testsuite/rust/execute/torture/c_string.rs     |  37 +++++-
 .../execute/torture/c_string_ensure_null_term.rs   |  39 +++++-
 gcc/testsuite/rust/execute/torture/cfg_select1.rs  |  31 +++++
 gcc/testsuite/rust/execute/torture/cfg_select2.rs  |  31 +++++
 8 files changed, 301 insertions(+), 11 deletions(-)

diff --git a/gcc/rust/expand/rust-macro-builtins-utility.cc b/gcc/rust/expand/rust-macro-builtins-utility.cc
index 6ee771ec0ed4..10be561a0ff9 100644
--- a/gcc/rust/expand/rust-macro-builtins-utility.cc
+++ b/gcc/rust/expand/rust-macro-builtins-utility.cc
@@ -21,6 +21,7 @@
 #include "rust-macro-builtins.h"
 #include "rust-macro-builtins-helpers.h"
 #include "rust-session-manager.h"
+#include "rust-stmt.h"
 
 namespace Rust {
 
@@ -344,6 +345,139 @@ MacroBuiltin::cfg_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
   return AST::Fragment ({literal_exp}, std::move (tok));
 }
 
+tl::optional<AST::Fragment>
+MacroBuiltin::cfg_select_handler (location_t invoc_locus,
+				  AST::MacroInvocData &invoc,
+				  AST::InvocKind semicolon)
+{
+  auto invoc_token_tree = invoc.get_delim_tok_tree ();
+  MacroInvocLexer lex (invoc_token_tree.to_token_stream ());
+
+  Parser<MacroInvocLexer> parser (lex);
+
+  if (!parser.skip_token (LEFT_CURLY))
+    {
+      rust_error_at (invoc_locus, "expected %<(%> in %<cfg_select!%>");
+      return AST::Fragment::create_error ();
+    }
+
+  std::vector<AST::SingleASTNode> matched_body_nodes;
+  std::vector<std::unique_ptr<AST::Token>> matched_body_tokens;
+  bool has_match = false;
+
+  while (lex.peek_token ()->get_id () != RIGHT_CURLY
+	 && lex.peek_token ()->get_id () != END_OF_FILE)
+    {
+      if (lex.peek_token ()->get_id () == UNDERSCORE)
+	{
+	  // wildcard predicate
+	  lex.skip_token (); // consume '_'
+	  has_match = true;
+	}
+      else
+	{
+	  size_t pred_start = lex.get_offs ();
+
+	  // parse the predicate (until =>)
+	  while (lex.peek_token ()->get_id () != MATCH_ARROW)
+	    {
+	      if (lex.peek_token ()->get_id () == END_OF_FILE)
+		{
+		  rust_error_at (invoc_locus,
+				 "unterminated %<cfg_select!%>arm");
+		  return AST::Fragment::create_error ();
+		}
+	      lex.skip_token ();
+	    }
+
+	  size_t pred_end = lex.get_offs ();
+
+	  std::vector<const_TokenPtr> synth;
+	  synth.emplace_back (Token::make (LEFT_PAREN, invoc_locus));
+	  auto pred_tokens = lex.get_token_slice (pred_start, pred_end);
+	  for (auto &t : pred_tokens)
+	    synth.emplace_back (t->get_tok_ptr ());
+	  synth.emplace_back (Token::make (RIGHT_PAREN, invoc_locus));
+
+	  AST::AttributeParser attr_parser (std::move (synth));
+	  auto items = attr_parser.parse_meta_item_seq ();
+	  if (items.size () != 1)
+	    {
+	      rust_error_at (invoc_locus,
+			     "  %<cfg_select!%> arm predicate must "
+			     "be a single cfg expression");
+	      return AST::Fragment::create_error ();
+	    }
+
+	  bool result
+	    = items[0]->check_cfg_predicate (Session::get_instance ());
+	  if (result)
+	    has_match = true;
+	}
+
+      if (!parser.skip_token (MATCH_ARROW))
+	{
+	  rust_error_at (lex.peek_token ()->get_locus (),
+			 "expected %<=>%> in %<cfg_select!%> arm");
+	  return AST::Fragment::create_error ();
+	}
+
+      // parse the body (after =>)
+      // always parse the body regardless of whether has_match is set, so lex
+      // will be at the next predicate in the next loop
+      size_t body_start = lex.get_offs ();
+      auto block_res = parser.parse_block_expr ();
+      if (has_match)
+	{
+	  size_t body_end = lex.get_offs ();
+	  if (!block_res)
+	    {
+	      rust_error_at (lex.peek_token ()->get_locus (),
+			     "failed to parse %<cfg_select!%> arm body");
+	      return AST::Fragment::create_error ();
+	    }
+
+	  auto block = std::move (*block_res);
+	  for (auto &stmt : block->get_statements ())
+	    {
+	      if (stmt->get_stmt_kind () == AST::Stmt::Kind::Item)
+		{
+		  AST::Stmt *raw = stmt.release ();
+		  matched_body_nodes.emplace_back (std::unique_ptr<AST::Item> (
+		    static_cast<AST::Item *> (raw)));
+		}
+	      else
+		{
+		  matched_body_nodes.emplace_back (std::move (stmt));
+		}
+	    }
+	  if (block->has_tail_expr ())
+	    {
+	      auto tail = block->take_tail_expr ();
+	      matched_body_nodes.emplace_back (AST::SingleASTNode (
+		std::make_unique<AST::ExprStmt> (std::move (tail), invoc_locus,
+						 false)));
+	    }
+
+	  matched_body_tokens = lex.get_token_slice (body_start, body_end);
+	  break;
+	}
+
+      parser.maybe_skip_token (COMMA);
+    }
+
+  if (!has_match)
+    {
+      rust_error_at (
+	invoc_locus,
+	"no %<cfg_select!%> arm matched and no %<_%> arm was provided");
+      return AST::Fragment::create_error ();
+    }
+
+  return AST::Fragment (std::move (matched_body_nodes),
+			std::move (matched_body_tokens));
+}
+
 tl::optional<AST::Fragment>
 MacroBuiltin::stringify_handler (location_t invoc_locus,
 				 AST::MacroInvocData &invoc,
diff --git a/gcc/rust/expand/rust-macro-builtins.cc b/gcc/rust/expand/rust-macro-builtins.cc
index 02856e97cee1..df651fd9bbcc 100644
--- a/gcc/rust/expand/rust-macro-builtins.cc
+++ b/gcc/rust/expand/rust-macro-builtins.cc
@@ -55,6 +55,7 @@ const BiMap<std::string, BuiltinMacro> MacroBuiltin::builtins = {{
   {"env", BuiltinMacro::Env},
   {"option_env", BuiltinMacro::OptionEnv},
   {"cfg", BuiltinMacro::Cfg},
+  {"cfg_select", BuiltinMacro::CfgSelect},
   {"include", BuiltinMacro::Include},
   {"format_args", BuiltinMacro::FormatArgs},
   {"format_args_nl", BuiltinMacro::FormatArgsNl},
@@ -124,6 +125,7 @@ std::unordered_map<std::string, AST::MacroTranscriberFunc>
     {"concat", MacroBuiltin::concat_handler},
     {"env", MacroBuiltin::env_handler},
     {"cfg", MacroBuiltin::cfg_handler},
+    {"cfg_select", MacroBuiltin::cfg_select_handler},
     {"include", MacroBuiltin::include_handler},
     {"format_args", format_args_maker (AST::FormatArgs::Newline::No)},
     {"format_args_nl", format_args_maker (AST::FormatArgs::Newline::Yes)},
diff --git a/gcc/rust/expand/rust-macro-builtins.h b/gcc/rust/expand/rust-macro-builtins.h
index 98ff5264573d..b0c2db57f058 100644
--- a/gcc/rust/expand/rust-macro-builtins.h
+++ b/gcc/rust/expand/rust-macro-builtins.h
@@ -49,6 +49,7 @@ enum class BuiltinMacro
   Env,
   OptionEnv,
   Cfg,
+  CfgSelect,
   Include,
   FormatArgs,
   FormatArgsNl,
@@ -168,6 +169,10 @@ public:
 						  AST::MacroInvocData &invoc,
 						  AST::InvocKind semicolon);
 
+  static tl::optional<AST::Fragment>
+  cfg_select_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
+		      AST::InvocKind semicolon);
+
   static tl::optional<AST::Fragment>
   include_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
 		   AST::InvocKind semicolon);
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 89a4bcdcaeff..6c872e000761 100644
--- a/gcc/testsuite/rust/compile/c_string_null_byte_check.rs
+++ b/gcc/testsuite/rust/compile/c_string_null_byte_check.rs
@@ -1,8 +1,37 @@
 // { dg-additional-options "-frust-c-style-string-literals" }
-#![feature(no_core, lang_items)]
+#![feature(no_core, lang_items, rustc_attrs)]
 #![no_core]
 
-type c_char = u8;
+#[rustc_builtin_macro]
+macro_rules! cfg_select {
+    () => {{}};
+}
+
+cfg_select! {
+    all(
+        not(windows),
+        not(target_vendor = "apple"),
+        not(target_os = "vita"),
+        any(
+            target_arch = "aarch64",
+            target_arch = "arm",
+            target_arch = "csky",
+            target_arch = "hexagon",
+            target_arch = "msp430",
+            target_arch = "powerpc",
+            target_arch = "powerpc64",
+            target_arch = "riscv32",
+            target_arch = "riscv64",
+            target_arch = "s390x",
+            target_arch = "xtensa",
+        )
+    ) => {
+        pub type c_char = u8;
+    }
+    _ => {
+        pub type c_char = i8;
+    }
+}
 
 #[lang = "CStr"]
 #[repr(transparent)]
diff --git a/gcc/testsuite/rust/execute/torture/c_string.rs b/gcc/testsuite/rust/execute/torture/c_string.rs
index 9f4cd5c036a5..9df72b8f033a 100644
--- a/gcc/testsuite/rust/execute/torture/c_string.rs
+++ b/gcc/testsuite/rust/execute/torture/c_string.rs
@@ -1,13 +1,42 @@
 // { dg-additional-options "-frust-c-style-string-literals" }
 // { dg-output "gccrs" }
-#![feature(no_core, lang_items)]
+#![feature(no_core, lang_items, rustc_attrs)]
 #![no_core]
 
-extern "C" {
-    fn printf(s: *const u8, ...);
+#[rustc_builtin_macro]
+macro_rules! cfg_select {
+    () => {{}};
 }
 
-type c_char = u8;
+cfg_select! {
+    all(
+        not(windows),
+        not(target_vendor = "apple"),
+        not(target_os = "vita"),
+        any(
+            target_arch = "aarch64",
+            target_arch = "arm",
+            target_arch = "csky",
+            target_arch = "hexagon",
+            target_arch = "msp430",
+            target_arch = "powerpc",
+            target_arch = "powerpc64",
+            target_arch = "riscv32",
+            target_arch = "riscv64",
+            target_arch = "s390x",
+            target_arch = "xtensa",
+        )
+    ) => {
+        pub type c_char = u8;
+    }
+    _ => {
+        pub type c_char = i8;
+    }
+}
+
+extern "C" {
+    fn printf(s: *const c_char, ...);
+}
 
 #[lang = "CStr"]
 #[repr(transparent)]
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 60da8a1dd9e3..a880bb735a3d 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
@@ -1,5 +1,5 @@
 // { dg-additional-options "-frust-c-style-string-literals" }
-#![feature(no_core, intrinsics, staged_api, lang_items)]
+#![feature(no_core, intrinsics, staged_api, lang_items, rustc_attrs)]
 #![no_core]
 
 #[lang = "sized"]
@@ -26,11 +26,40 @@ impl<T> *const T {
     }
 }
 
-extern "C" {
-    fn printf(s: *const u8, ...);
+#[rustc_builtin_macro]
+macro_rules! cfg_select {
+    () => {{}};
 }
 
-type c_char = u8;
+cfg_select! {
+    all(
+        not(windows),
+        not(target_vendor = "apple"),
+        not(target_os = "vita"),
+        any(
+            target_arch = "aarch64",
+            target_arch = "arm",
+            target_arch = "csky",
+            target_arch = "hexagon",
+            target_arch = "msp430",
+            target_arch = "powerpc",
+            target_arch = "powerpc64",
+            target_arch = "riscv32",
+            target_arch = "riscv64",
+            target_arch = "s390x",
+            target_arch = "xtensa",
+        )
+    ) => {
+        pub type c_char = u8;
+    }
+    _ => {
+        pub type c_char = i8;
+    }
+}
+
+extern "C" {
+    fn printf(s: *const c_char, ...);
+}
 
 #[lang = "CStr"]
 #[repr(transparent)]
@@ -44,7 +73,7 @@ impl CStr {
     }
 }
 
-pub fn main() -> u8 {
+pub fn main() -> c_char {
     let a = c"gccrs";
     let val = unsafe { a.to_ptr().add(5) };
     unsafe { *val }
diff --git a/gcc/testsuite/rust/execute/torture/cfg_select1.rs b/gcc/testsuite/rust/execute/torture/cfg_select1.rs
new file mode 100644
index 000000000000..5a37dcc6cc2a
--- /dev/null
+++ b/gcc/testsuite/rust/execute/torture/cfg_select1.rs
@@ -0,0 +1,31 @@
+// { dg-additional-options "-frust-cfg=A=\"foo\"" }
+// { dg-output "wildcard\r*\n" }
+#![feature(no_core, rustc_attrs)]
+#![no_core]
+
+#[rustc_builtin_macro]
+macro_rules! cfg_select {
+    () => {{}};
+}
+
+extern "C" {
+    fn printf(s: *const i8, ...);
+}
+
+fn main() -> i32 {
+    cfg_select! {
+        A = "bar" => {
+            unsafe {
+                let a = "none\n\0";
+                printf(a as *const str as *const i8);
+            }
+        },
+        _ => {
+            unsafe {
+                let a = "wildcard\n\0";
+                printf(a as *const str as *const i8);
+            }
+        }
+    }
+    return 0;
+}
\ No newline at end of file
diff --git a/gcc/testsuite/rust/execute/torture/cfg_select2.rs b/gcc/testsuite/rust/execute/torture/cfg_select2.rs
new file mode 100644
index 000000000000..fe0ad608ec71
--- /dev/null
+++ b/gcc/testsuite/rust/execute/torture/cfg_select2.rs
@@ -0,0 +1,31 @@
+// { dg-additional-options "-frust-cfg=A=\"foo\"" }
+// { dg-output "pass\r*\n" }
+#![feature(no_core, rustc_attrs)]
+#![no_core]
+
+#[rustc_builtin_macro]
+macro_rules! cfg_select {
+    () => {{}};
+}
+
+extern "C" {
+    fn printf(s: *const i8, ...);
+}
+
+fn main() -> i32 {
+    cfg_select! {
+        A = "foo" => {
+            unsafe {
+                let a = "pass\n\0";
+                printf(a as *const str as *const i8);
+            }
+        }
+        _ => {
+            unsafe {
+                let a = "fail\n\0";
+                printf(a as *const str as *const i8);
+            }
+        }
+    }
+    return 0;
+}
\ No newline at end of file
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.