Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:

- name: Run tests
run: |
./y.sh test --release --clean --build-sysroot ${{ matrix.commands }}
./y.sh test --release --clean --build-sysroot --no-builtins-tests ${{ matrix.commands }}

duplicates:
runs-on: ubuntu-24.04
Expand Down
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)?;
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
14 changes: 13 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,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;

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);
}
}
Loading