[gccrs COMMIT 1/2] gccrs: Backport `cfg_select!` macro

[email protected] Mon, 3 Aug 2026 10:42:56 +0000
Newsgroups gmane.comp.gcc.rust,gmane.comp.gcc.patches
Message-ID <[email protected]>
From: Yap Zhi Heng <[email protected]>

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]>
---
This change was merged into the gccrs repository and is posted here for
upstream visibility and potential drive-by review, as requested by GCC
release managers.
Each commit email contains a link to its details on github from where you can
find the Pull-Request and associated discussions.


Commit on github: https://github.com/Rust-GCC/gccrs/commit/b470ae200be3802a4799571d7e67315a9b71aad2

The commit has NOT been mentioned in any issue.

The commit has been mentioned in the following pull-request(s):
 - https://github.com/Rust-GCC/gccrs/pull/4714

 .../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 ++++-
 .../rust/execute/torture/c_string.rs          |  37 ++++-
 .../torture/c_string_ensure_null_term.rs      |  39 ++++-
 .../rust/execute/torture/cfg_select1.rs       |  31 ++++
 .../rust/execute/torture/cfg_select2.rs       |  31 ++++
 8 files changed, 301 insertions(+), 11 deletions(-)
 create mode 100644 gcc/testsuite/rust/execute/torture/cfg_select1.rs
 create mode 100644 gcc/testsuite/rust/execute/torture/cfg_select2.rs

diff --git a/gcc/rust/expand/rust-macro-builtins-utility.cc b/gcc/rust/expand/rust-macro-builtins-utility.cc
index 6ee771ec0..10be561a0 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 02856e97c..df651fd9b 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 98ff52645..b0c2db57f 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 89a4bcdca..6c872e000 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 9f4cd5c03..9df72b8f0 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 60da8a1dd..a880bb735 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 000000000..5a37dcc6c
--- /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 000000000..fe0ad608e
--- /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

base-commit: efaeba75ab09c066482927f8ef845b8f12c2d6fd
-- 
2.54.0