Skip to content

Commit daf8bb9

Browse files
authored
Fix #570: Honor #![no_builtins] attribute by passing -fno-builtin to GCC (#854)
1 parent 445413c commit daf8bb9

5 files changed

Lines changed: 120 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ jobs:
119119

120120
- name: Run tests
121121
run: |
122-
./y.sh test --release --clean --build-sysroot ${{ matrix.commands }}
122+
./y.sh test --release --clean --build-sysroot --no-builtins-tests ${{ matrix.commands }}
123123
124124
duplicates:
125125
runs-on: ubuntu-24.04

build_system/src/test.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ fn get_runners() -> Runners {
4343
runners.insert("--extended-regex-tests", ("Run extended regex tests", extended_regex_tests));
4444
runners.insert("--mini-tests", ("Run mini tests", mini_tests));
4545
runners.insert("--cargo-tests", ("Run cargo tests", cargo_tests));
46+
runners.insert("--no-builtins-tests", ("Test #![no_builtins] attribute", no_builtins_tests));
4647
runners
4748
}
4849

@@ -317,6 +318,65 @@ fn maybe_run_command_in_vm(
317318
Ok(())
318319
}
319320

321+
/// Compile a source file to an object file and check if it contains a memset reference.
322+
fn object_has_memset(
323+
env: &Env,
324+
args: &TestArg,
325+
src_file: &str,
326+
obj_file_name: &str,
327+
) -> Result<bool, String> {
328+
let cargo_target_dir = Path::new(&args.config_info.cargo_target_dir);
329+
let obj_file = cargo_target_dir.join(obj_file_name);
330+
let obj_file_str = obj_file.to_str().expect("obj_file to_str");
331+
332+
let mut command = args.config_info.rustc_command_vec();
333+
command.extend_from_slice(&[
334+
&src_file,
335+
&"--emit",
336+
&"obj",
337+
&"-O",
338+
&"--target",
339+
&args.config_info.target_triple,
340+
&"-o",
341+
]);
342+
command.push(&obj_file_str);
343+
run_command_with_env(&command, None, Some(env))?;
344+
345+
let nm_output = run_command_with_env(&[&"nm", &obj_file_str], None, Some(env))?;
346+
let nm_stdout = String::from_utf8_lossy(&nm_output.stdout);
347+
348+
Ok(nm_stdout.contains("memset"))
349+
}
350+
351+
fn no_builtins_tests(env: &Env, args: &TestArg) -> Result<(), String> {
352+
// Test that the #![no_builtins] attribute prevents GCC from replacing
353+
// code patterns (like loops) with calls to builtins (like memset).
354+
// See https://github.com/rust-lang/rustc_codegen_gcc/issues/570
355+
356+
// Test 1: WITH #![no_builtins] - memset should NOT be present
357+
println!("[TEST] no_builtins attribute (with #![no_builtins])");
358+
let has_memset =
359+
object_has_memset(env, args, "tests/no_builtins/no_builtins.rs", "no_builtins_test.o")?;
360+
if has_memset {
361+
return Err("no_builtins test FAILED: Found 'memset' in object file.\n\
362+
The #![no_builtins] attribute should prevent GCC from replacing \n\
363+
code patterns with builtin calls."
364+
.to_string());
365+
}
366+
367+
// Test 2: WITHOUT #![no_builtins] - memset SHOULD be present
368+
println!("[TEST] no_builtins attribute (without #![no_builtins])");
369+
let has_memset =
370+
object_has_memset(env, args, "tests/no_builtins/with_builtins.rs", "with_builtins_test.o")?;
371+
if !has_memset {
372+
return Err("no_builtins test FAILED: 'memset' NOT found in object file.\n\
373+
Without #![no_builtins], GCC should replace the loop with memset."
374+
.to_string());
375+
}
376+
377+
Ok(())
378+
}
379+
320380
fn std_tests(env: &Env, args: &TestArg) -> Result<(), String> {
321381
let cargo_target_dir = Path::new(&args.config_info.cargo_target_dir);
322382
// 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> {
12481308
test_libcore(env, args)?;
12491309
extended_sysroot_tests(env, args)?;
12501310
cargo_tests(env, args)?;
1311+
no_builtins_tests(env, args)?;
12511312
test_rustc(env, args)?;
12521313

12531314
Ok(())

src/base.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use rustc_codegen_ssa::ModuleCodegen;
88
use rustc_codegen_ssa::base::maybe_create_entry_wrapper;
99
use rustc_codegen_ssa::mono_item::MonoItemExt;
1010
use rustc_codegen_ssa::traits::DebugInfoCodegenMethods;
11-
use rustc_hir::attrs::Linkage;
11+
use rustc_hir::attrs::{AttributeKind, Linkage};
12+
use rustc_hir::find_attr;
1213
use rustc_middle::dep_graph;
1314
#[cfg(feature = "master")]
1415
use rustc_middle::mir::mono::Visibility;
@@ -136,6 +137,17 @@ pub fn compile_codegen_unit(
136137
// NOTE: Rust relies on LLVM doing wrapping on overflow.
137138
context.add_command_line_option("-fwrapv");
138139

140+
// NOTE: We need to honor the `#![no_builtins]` attribute to prevent GCC from
141+
// replacing code patterns (like loops) with calls to builtins (like memset).
142+
// The `-fno-tree-loop-distribute-patterns` flag disables the loop distribution pass
143+
// that transforms loops into calls to library functions (memset, memcpy, etc.).
144+
// See GCC handling for more details:
145+
// https://github.com/rust-lang/gcc/blob/efdd0a7290c22f5438d7c5380105d353ee3e8518/gcc/c-family/c-opts.cc#L953
146+
let crate_attrs = tcx.hir_attrs(rustc_hir::CRATE_HIR_ID);
147+
if find_attr!(crate_attrs, AttributeKind::NoBuiltins) {
148+
context.add_command_line_option("-fno-tree-loop-distribute-patterns");
149+
}
150+
139151
if let Some(model) = tcx.sess.code_model() {
140152
use rustc_target::spec::CodeModel;
141153

tests/no_builtins/no_builtins.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Test that the #![no_builtins] attribute is honored.
2+
// When this attribute is present, GCC should not replace code patterns
3+
// (like loops) with calls to builtins (like memset).
4+
// See https://github.com/rust-lang/rustc_codegen_gcc/issues/570
5+
//
6+
// This test is verified by the build system test `--no-builtins-tests` which
7+
// compiles this file and checks that `memset` is not referenced in the object file.
8+
9+
#![no_std]
10+
#![no_builtins]
11+
#![crate_type = "lib"]
12+
13+
// This function implements a byte-setting loop that GCC would typically
14+
// optimize into a memset call. With #![no_builtins], GCC should preserve
15+
// the loop instead of replacing it with a builtin call.
16+
#[no_mangle]
17+
#[inline(never)]
18+
pub unsafe fn set_bytes(mut s: *mut u8, c: u8, n: usize) {
19+
let end = s.add(n);
20+
while s < end {
21+
*s = c;
22+
s = s.add(1);
23+
}
24+
}

tests/no_builtins/with_builtins.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Test that without #![no_builtins], GCC DOES replace code patterns with builtins.
2+
// This is the counterpart to no_builtins.rs - we verify that memset IS emitted
3+
// when the no_builtins attribute is NOT present.
4+
//
5+
// This test is verified by the build system test `--no-builtins-tests` which
6+
// compiles this file and checks that `memset` IS referenced in the object file.
7+
8+
#![no_std]
9+
#![crate_type = "lib"]
10+
11+
// This function implements a byte-setting loop that GCC should optimize
12+
// into a memset call when no_builtins is NOT set.
13+
#[no_mangle]
14+
#[inline(never)]
15+
pub unsafe fn set_bytes(mut s: *mut u8, c: u8, n: usize) {
16+
let end = s.add(n);
17+
while s < end {
18+
*s = c;
19+
s = s.add(1);
20+
}
21+
}

0 commit comments

Comments
 (0)