Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions build_system/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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<bool, String> {
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.
Expand Down Expand Up @@ -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(())
Expand Down
12 changes: 11 additions & 1 deletion src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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).
// 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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment explaining why we use this specific flag and link to GCC's source code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see a new link to GCC's source code.
Did you add a link to this code as requested?
Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed adding the link earlier, added now, please review.

}

if let Some(model) = tcx.sess.code_model() {
use rustc_target::spec::CodeModel;

Expand Down
24 changes: 24 additions & 0 deletions tests/no_builtins/no_builtins.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
21 changes: 21 additions & 0 deletions tests/no_builtins/with_builtins.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}