From 290e46532e762e0f336e45671c02beb58c23e4dd Mon Sep 17 00:00:00 2001 From: Harin Date: Sun, 22 Feb 2026 21:43:22 +0530 Subject: [PATCH 1/5] Fix #570: Honor #![no_builtins] attribute by passing -fno-builtin to GCC --- src/base.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/base.rs b/src/base.rs index 18058d9491a..b43935e1f60 100644 --- a/src/base.rs +++ b/src/base.rs @@ -8,7 +8,8 @@ use rustc_codegen_ssa::ModuleCodegen; use rustc_codegen_ssa::base::maybe_create_entry_wrapper; use rustc_codegen_ssa::mono_item::MonoItemExt; use rustc_codegen_ssa::traits::DebugInfoCodegenMethods; -use rustc_hir::attrs::Linkage; +use rustc_hir::attrs::{AttributeKind, Linkage}; +use rustc_hir::find_attr; use rustc_middle::dep_graph; #[cfg(feature = "master")] use rustc_middle::mir::mono::Visibility; @@ -136,6 +137,15 @@ pub fn compile_codegen_unit( // NOTE: Rust relies on LLVM doing wrapping on overflow. context.add_command_line_option("-fwrapv"); + // NOTE: We need to honor the `#![no_builtins]` attribute to prevent GCC from + // replacing code patterns (like loops) with calls to builtins (like memset). + // This is important for crates like `compiler_builtins` that implement these functions. + // See https://github.com/rust-lang/rustc_codegen_gcc/issues/570 + let crate_attrs = tcx.hir_attrs(rustc_hir::CRATE_HIR_ID); + if find_attr!(crate_attrs, AttributeKind::NoBuiltins) { + context.add_command_line_option("-fno-builtin"); + } + if let Some(model) = tcx.sess.code_model() { use rustc_target::spec::CodeModel; From 118f663527cce7f14202fb2923184c443ea4f2e6 Mon Sep 17 00:00:00 2001 From: Harin Date: Mon, 23 Feb 2026 15:57:34 +0530 Subject: [PATCH 2/5] Add test to verify #![no_builtins] prevents GCC loop-to-memset optimization --- build_system/src/test.rs | 75 ++++++++++++++++++++++++++++++++++++++ src/base.rs | 2 +- tests/run/no_builtins.rs | 24 ++++++++++++ tests/run/with_builtins.rs | 21 +++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 tests/run/no_builtins.rs create mode 100644 tests/run/with_builtins.rs diff --git a/build_system/src/test.rs b/build_system/src/test.rs index 096f8c98376..b78498eff75 100644 --- a/build_system/src/test.rs +++ b/build_system/src/test.rs @@ -43,6 +43,7 @@ fn get_runners() -> Runners { runners.insert("--extended-regex-tests", ("Run extended regex tests", extended_regex_tests)); runners.insert("--mini-tests", ("Run mini tests", mini_tests)); runners.insert("--cargo-tests", ("Run cargo tests", cargo_tests)); + runners.insert("--no-builtins-tests", ("Test #![no_builtins] attribute", no_builtins_tests)); runners } @@ -317,6 +318,79 @@ fn maybe_run_command_in_vm( Ok(()) } +fn no_builtins_tests(env: &Env, args: &TestArg) -> Result<(), String> { + // Test that the #![no_builtins] attribute prevents GCC from replacing + // code patterns (like loops) with calls to builtins (like memset). + // See https://github.com/rust-lang/rustc_codegen_gcc/issues/570 + + let cargo_target_dir = Path::new(&args.config_info.cargo_target_dir); + + // Test 1: WITH #![no_builtins] - memset should NOT be present + println!("[TEST] no_builtins attribute (with #![no_builtins])"); + let obj_file = cargo_target_dir.join("no_builtins_test.o"); + let obj_file_str = obj_file.to_str().expect("obj_file to_str"); + + let mut command = args.config_info.rustc_command_vec(); + command.extend_from_slice(&[ + &"tests/run/no_builtins.rs", + &"--emit", + &"obj", + &"-O", + &"--target", + &args.config_info.target_triple, + &"-o", + ]); + command.push(&obj_file_str); + run_command_with_env(&command, None, Some(env))?; + + let nm_output = run_command_with_env(&[&"nm", &obj_file_str], None, Some(env))?; + let nm_stdout = String::from_utf8_lossy(&nm_output.stdout); + + if nm_stdout.contains("memset") { + return Err(format!( + "no_builtins test FAILED: Found 'memset' in object file.\n\ + The #![no_builtins] attribute should prevent GCC from replacing \n\ + code patterns with builtin calls.\n\ + nm output:\n{}", + nm_stdout + )); + } + println!("[TEST] no_builtins attribute (with #![no_builtins]): PASSED"); + + // Test 2: WITHOUT #![no_builtins] - memset SHOULD be present + println!("[TEST] no_builtins attribute (without #![no_builtins])"); + let obj_file = cargo_target_dir.join("with_builtins_test.o"); + let obj_file_str = obj_file.to_str().expect("obj_file to_str"); + + let mut command = args.config_info.rustc_command_vec(); + command.extend_from_slice(&[ + &"tests/run/with_builtins.rs", + &"--emit", + &"obj", + &"-O", + &"--target", + &args.config_info.target_triple, + &"-o", + ]); + command.push(&obj_file_str); + run_command_with_env(&command, None, Some(env))?; + + let nm_output = run_command_with_env(&[&"nm", &obj_file_str], None, Some(env))?; + let nm_stdout = String::from_utf8_lossy(&nm_output.stdout); + + if !nm_stdout.contains("memset") { + return Err(format!( + "no_builtins test FAILED: 'memset' NOT found in object file.\n\ + Without #![no_builtins], GCC should replace the loop with memset.\n\ + nm output:\n{}", + nm_stdout + )); + } + println!("[TEST] no_builtins attribute (without #![no_builtins]): PASSED"); + + Ok(()) +} + fn std_tests(env: &Env, args: &TestArg) -> Result<(), String> { let cargo_target_dir = Path::new(&args.config_info.cargo_target_dir); // FIXME: create a function "display_if_not_quiet" or something along the line. @@ -1248,6 +1322,7 @@ fn run_all(env: &Env, args: &TestArg) -> Result<(), String> { test_libcore(env, args)?; extended_sysroot_tests(env, args)?; cargo_tests(env, args)?; + no_builtins_tests(env, args)?; test_rustc(env, args)?; Ok(()) diff --git a/src/base.rs b/src/base.rs index b43935e1f60..49ec68d7350 100644 --- a/src/base.rs +++ b/src/base.rs @@ -143,7 +143,7 @@ pub fn compile_codegen_unit( // See https://github.com/rust-lang/rustc_codegen_gcc/issues/570 let crate_attrs = tcx.hir_attrs(rustc_hir::CRATE_HIR_ID); if find_attr!(crate_attrs, AttributeKind::NoBuiltins) { - context.add_command_line_option("-fno-builtin"); + context.add_command_line_option("-fno-tree-loop-distribute-patterns"); } if let Some(model) = tcx.sess.code_model() { diff --git a/tests/run/no_builtins.rs b/tests/run/no_builtins.rs new file mode 100644 index 00000000000..bc1dce18c77 --- /dev/null +++ b/tests/run/no_builtins.rs @@ -0,0 +1,24 @@ +// Test that the #![no_builtins] attribute is honored. +// When this attribute is present, GCC should pass -fno-builtin to prevent +// replacing code patterns (like loops) with calls to builtins (like memset). +// See https://github.com/rust-lang/rustc_codegen_gcc/issues/570 +// +// This test is verified by the build system test `--no-builtins-tests` which +// compiles this file and checks that `memset` is not referenced in the object file. + +#![no_std] +#![no_builtins] +#![crate_type = "lib"] + +// This function implements a byte-setting loop that GCC would typically +// optimize into a memset call. With #![no_builtins], GCC should preserve +// the loop instead of replacing it with a builtin call. +#[no_mangle] +#[inline(never)] +pub unsafe fn set_bytes(mut s: *mut u8, c: u8, n: usize) { + let end = s.add(n); + while s < end { + *s = c; + s = s.add(1); + } +} diff --git a/tests/run/with_builtins.rs b/tests/run/with_builtins.rs new file mode 100644 index 00000000000..30271978a1b --- /dev/null +++ b/tests/run/with_builtins.rs @@ -0,0 +1,21 @@ +// Test that without #![no_builtins], GCC DOES replace code patterns with builtins. +// This is the counterpart to no_builtins.rs - we verify that memset IS emitted +// when the no_builtins attribute is NOT present. +// +// This test is verified by the build system test `--no-builtins-tests` which +// compiles this file and checks that `memset` IS referenced in the object file. + +#![no_std] +#![crate_type = "lib"] + +// This function implements a byte-setting loop that GCC should optimize +// into a memset call when no_builtins is NOT set. +#[no_mangle] +#[inline(never)] +pub unsafe fn set_bytes(mut s: *mut u8, c: u8, n: usize) { + let end = s.add(n); + while s < end { + *s = c; + s = s.add(1); + } +} From 7b582f19cee664f3c2b9bfd80ad4ad379f609fda Mon Sep 17 00:00:00 2001 From: Harin Date: Mon, 23 Feb 2026 16:05:58 +0530 Subject: [PATCH 3/5] Moved builtins test to a seperate folder --- build_system/src/test.rs | 4 ++-- tests/{run => no_builtins}/no_builtins.rs | 0 tests/{run => no_builtins}/with_builtins.rs | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename tests/{run => no_builtins}/no_builtins.rs (100%) rename tests/{run => no_builtins}/with_builtins.rs (100%) diff --git a/build_system/src/test.rs b/build_system/src/test.rs index b78498eff75..5e7dca1f795 100644 --- a/build_system/src/test.rs +++ b/build_system/src/test.rs @@ -332,7 +332,7 @@ fn no_builtins_tests(env: &Env, args: &TestArg) -> Result<(), String> { let mut command = args.config_info.rustc_command_vec(); command.extend_from_slice(&[ - &"tests/run/no_builtins.rs", + &"tests/no_builtins/no_builtins.rs", &"--emit", &"obj", &"-O", @@ -364,7 +364,7 @@ fn no_builtins_tests(env: &Env, args: &TestArg) -> Result<(), String> { let mut command = args.config_info.rustc_command_vec(); command.extend_from_slice(&[ - &"tests/run/with_builtins.rs", + &"tests/no_builtins/with_builtins.rs", &"--emit", &"obj", &"-O", diff --git a/tests/run/no_builtins.rs b/tests/no_builtins/no_builtins.rs similarity index 100% rename from tests/run/no_builtins.rs rename to tests/no_builtins/no_builtins.rs diff --git a/tests/run/with_builtins.rs b/tests/no_builtins/with_builtins.rs similarity index 100% rename from tests/run/with_builtins.rs rename to tests/no_builtins/with_builtins.rs From 92f028d3355938ad508a9a649c860e25dbaf536c Mon Sep 17 00:00:00 2001 From: Harin Date: Tue, 24 Feb 2026 13:57:35 +0530 Subject: [PATCH 4/5] Code refactor and comment update --- build_system/src/test.rs | 76 +++++++++++++------------------- src/base.rs | 4 +- tests/no_builtins/no_builtins.rs | 4 +- 3 files changed, 35 insertions(+), 49 deletions(-) diff --git a/build_system/src/test.rs b/build_system/src/test.rs index 5e7dca1f795..0f9dc3c9383 100644 --- a/build_system/src/test.rs +++ b/build_system/src/test.rs @@ -318,21 +318,20 @@ fn maybe_run_command_in_vm( Ok(()) } -fn no_builtins_tests(env: &Env, args: &TestArg) -> Result<(), String> { - // Test that the #![no_builtins] attribute prevents GCC from replacing - // code patterns (like loops) with calls to builtins (like memset). - // See https://github.com/rust-lang/rustc_codegen_gcc/issues/570 - +/// Compile a source file to an object file and check if it contains a memset reference. +fn object_has_memset( + env: &Env, + args: &TestArg, + src_file: &str, + obj_file_name: &str, +) -> Result { let cargo_target_dir = Path::new(&args.config_info.cargo_target_dir); - - // Test 1: WITH #![no_builtins] - memset should NOT be present - println!("[TEST] no_builtins attribute (with #![no_builtins])"); - let obj_file = cargo_target_dir.join("no_builtins_test.o"); + let obj_file = cargo_target_dir.join(obj_file_name); let obj_file_str = obj_file.to_str().expect("obj_file to_str"); let mut command = args.config_info.rustc_command_vec(); command.extend_from_slice(&[ - &"tests/no_builtins/no_builtins.rs", + &src_file, &"--emit", &"obj", &"-O", @@ -346,47 +345,34 @@ fn no_builtins_tests(env: &Env, args: &TestArg) -> Result<(), String> { let nm_output = run_command_with_env(&[&"nm", &obj_file_str], None, Some(env))?; let nm_stdout = String::from_utf8_lossy(&nm_output.stdout); - if nm_stdout.contains("memset") { - return Err(format!( - "no_builtins test FAILED: Found 'memset' in object file.\n\ + Ok(nm_stdout.contains("memset")) +} + +fn no_builtins_tests(env: &Env, args: &TestArg) -> Result<(), String> { + // Test that the #![no_builtins] attribute prevents GCC from replacing + // code patterns (like loops) with calls to builtins (like memset). + // See https://github.com/rust-lang/rustc_codegen_gcc/issues/570 + + // Test 1: WITH #![no_builtins] - memset should NOT be present + println!("[TEST] no_builtins attribute (with #![no_builtins])"); + let has_memset = + object_has_memset(env, args, "tests/no_builtins/no_builtins.rs", "no_builtins_test.o")?; + if has_memset { + return Err("no_builtins test FAILED: Found 'memset' in object file.\n\ The #![no_builtins] attribute should prevent GCC from replacing \n\ - code patterns with builtin calls.\n\ - nm output:\n{}", - nm_stdout - )); + code patterns with builtin calls." + .to_string()); } - println!("[TEST] no_builtins attribute (with #![no_builtins]): PASSED"); // Test 2: WITHOUT #![no_builtins] - memset SHOULD be present println!("[TEST] no_builtins attribute (without #![no_builtins])"); - let obj_file = cargo_target_dir.join("with_builtins_test.o"); - let obj_file_str = obj_file.to_str().expect("obj_file to_str"); - - let mut command = args.config_info.rustc_command_vec(); - command.extend_from_slice(&[ - &"tests/no_builtins/with_builtins.rs", - &"--emit", - &"obj", - &"-O", - &"--target", - &args.config_info.target_triple, - &"-o", - ]); - command.push(&obj_file_str); - run_command_with_env(&command, None, Some(env))?; - - let nm_output = run_command_with_env(&[&"nm", &obj_file_str], None, Some(env))?; - let nm_stdout = String::from_utf8_lossy(&nm_output.stdout); - - if !nm_stdout.contains("memset") { - return Err(format!( - "no_builtins test FAILED: 'memset' NOT found in object file.\n\ - Without #![no_builtins], GCC should replace the loop with memset.\n\ - nm output:\n{}", - nm_stdout - )); + let has_memset = + object_has_memset(env, args, "tests/no_builtins/with_builtins.rs", "with_builtins_test.o")?; + if !has_memset { + return Err("no_builtins test FAILED: 'memset' NOT found in object file.\n\ + Without #![no_builtins], GCC should replace the loop with memset." + .to_string()); } - println!("[TEST] no_builtins attribute (without #![no_builtins]): PASSED"); Ok(()) } diff --git a/src/base.rs b/src/base.rs index 49ec68d7350..b77919d11ae 100644 --- a/src/base.rs +++ b/src/base.rs @@ -139,8 +139,8 @@ pub fn compile_codegen_unit( // NOTE: We need to honor the `#![no_builtins]` attribute to prevent GCC from // replacing code patterns (like loops) with calls to builtins (like memset). - // This is important for crates like `compiler_builtins` that implement these functions. - // See https://github.com/rust-lang/rustc_codegen_gcc/issues/570 + // The `-fno-tree-loop-distribute-patterns` flag disables the loop distribution pass + // that transforms loops into calls to library functions (memset, memcpy, etc.). let crate_attrs = tcx.hir_attrs(rustc_hir::CRATE_HIR_ID); if find_attr!(crate_attrs, AttributeKind::NoBuiltins) { context.add_command_line_option("-fno-tree-loop-distribute-patterns"); diff --git a/tests/no_builtins/no_builtins.rs b/tests/no_builtins/no_builtins.rs index bc1dce18c77..c332581d937 100644 --- a/tests/no_builtins/no_builtins.rs +++ b/tests/no_builtins/no_builtins.rs @@ -1,6 +1,6 @@ // Test that the #![no_builtins] attribute is honored. -// When this attribute is present, GCC should pass -fno-builtin to prevent -// replacing code patterns (like loops) with calls to builtins (like memset). +// When this attribute is present, GCC should not replace code patterns +// (like loops) with calls to builtins (like memset). // See https://github.com/rust-lang/rustc_codegen_gcc/issues/570 // // This test is verified by the build system test `--no-builtins-tests` which From 9246f9b9d91b4eccac6a7cf1ee9649fa33b0f6bc Mon Sep 17 00:00:00 2001 From: Harin Date: Tue, 24 Feb 2026 19:14:24 +0530 Subject: [PATCH 5/5] updated comments --- src/base.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/base.rs b/src/base.rs index b77919d11ae..333b840fac2 100644 --- a/src/base.rs +++ b/src/base.rs @@ -141,6 +141,8 @@ pub fn compile_codegen_unit( // replacing code patterns (like loops) with calls to builtins (like memset). // The `-fno-tree-loop-distribute-patterns` flag disables the loop distribution pass // that transforms loops into calls to library functions (memset, memcpy, etc.). + // See GCC handling for more details: + // https://github.com/rust-lang/gcc/blob/efdd0a7290c22f5438d7c5380105d353ee3e8518/gcc/c-family/c-opts.cc#L953 let crate_attrs = tcx.hir_attrs(rustc_hir::CRATE_HIR_ID); if find_attr!(crate_attrs, AttributeKind::NoBuiltins) { context.add_command_line_option("-fno-tree-loop-distribute-patterns");