Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
75 changes: 75 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,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/no_builtins/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/no_builtins/with_builtins.rs",
Comment thread
antoyo marked this conversation as resolved.
Outdated
&"--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.
Expand Down Expand Up @@ -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)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The CI doesn't run tests through run_all, so please also add the flag to run these tests on this line.
Do not add it to matrix.commands since those tests are fast and as such, we don't need a separate CI job for them.

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).
// 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-tree-loop-distribute-patterns");
Comment thread
antoyo marked this conversation as resolved.
}

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 pass -fno-builtin to prevent
Comment thread
antoyo marked this conversation as resolved.
Outdated
// 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);
}
}
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);
}
}