diff --git a/build_system/src/test.rs b/build_system/src/test.rs index 096f8c98376..0f9dc3c9383 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,65 @@ fn maybe_run_command_in_vm( Ok(()) } +/// 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); + 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(&[ + &src_file, + &"--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); + + 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." + .to_string()); + } + + // Test 2: WITHOUT #![no_builtins] - memset SHOULD be present + println!("[TEST] no_builtins attribute (without #![no_builtins])"); + 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()); + } + + 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 +1308,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 18058d9491a..333b840fac2 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,17 @@ 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). + // 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"); + } + if let Some(model) = tcx.sess.code_model() { use rustc_target::spec::CodeModel; diff --git a/tests/no_builtins/no_builtins.rs b/tests/no_builtins/no_builtins.rs new file mode 100644 index 00000000000..c332581d937 --- /dev/null +++ b/tests/no_builtins/no_builtins.rs @@ -0,0 +1,24 @@ +// Test that the #![no_builtins] attribute is honored. +// 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 +// 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/no_builtins/with_builtins.rs b/tests/no_builtins/with_builtins.rs new file mode 100644 index 00000000000..30271978a1b --- /dev/null +++ b/tests/no_builtins/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); + } +}