Skip to content

Commit 5cc50ca

Browse files
authoredAug 7, 2024
Unrolled build for rust-lang#128362
Rollup merge of rust-lang#128362 - folkertdev:naked-function-symbol-visibility, r=bjorn3 add test for symbol visibility of `#[naked]` functions tracking issue: rust-lang#90957 This test is extracted from rust-lang#128004 That PR attempts to generated naked functions as an extern function declaration, combined with a global asm block that provides the implementation for that declaration. In order to link declaration and definition together, some flavor of external linking must be used: LLVM will error for other linkage types. Specifically the allowed options are `#[linkage = "external"]` and `#[linkage = "extern_weak"]`. That is kind of an implementation detail though: to the user, a naked function should just behave like a normal function. Hence it should be visible to the linker under the same circumstances as a normal, vanilla function and have the same attributes (Weak, External). Getting this behavior right will require some care, so I think it's a good idea to lock it in now, before making any changes, to make sure we don't regress. Are there any interesting cases that I missed here? E.g. is checking on different architectures worth it? I don't think the other binary types (rlib etc) are relevant here, but may be missing something. r? ``@bjorn3``
2 parents 2f3dc46 + 1967951 commit 5cc50ca

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#![feature(naked_functions, asm_const, linkage)]
2+
#![crate_type = "dylib"]
3+
4+
use std::arch::asm;
5+
6+
pub trait TraitWithConst {
7+
const COUNT: u32;
8+
}
9+
10+
struct Test;
11+
12+
impl TraitWithConst for Test {
13+
const COUNT: u32 = 1;
14+
}
15+
16+
#[no_mangle]
17+
fn entry() {
18+
private_vanilla();
19+
private_naked();
20+
21+
public_vanilla_generic::<Test>();
22+
public_naked_generic::<Test>();
23+
}
24+
25+
extern "C" fn private_vanilla() -> u32 {
26+
42
27+
}
28+
29+
#[naked]
30+
extern "C" fn private_naked() -> u32 {
31+
unsafe { asm!("mov rax, 42", "ret", options(noreturn)) }
32+
}
33+
34+
#[no_mangle]
35+
pub extern "C" fn public_vanilla() -> u32 {
36+
42
37+
}
38+
39+
#[naked]
40+
#[no_mangle]
41+
pub extern "C" fn public_naked() -> u32 {
42+
unsafe { asm!("mov rax, 42", "ret", options(noreturn)) }
43+
}
44+
45+
pub extern "C" fn public_vanilla_generic<T: TraitWithConst>() -> u32 {
46+
T::COUNT
47+
}
48+
49+
#[naked]
50+
pub extern "C" fn public_naked_generic<T: TraitWithConst>() -> u32 {
51+
unsafe { asm!("mov rax, {}", "ret", const T::COUNT, options(noreturn)) }
52+
}
53+
54+
#[linkage = "external"]
55+
extern "C" fn vanilla_external_linkage() -> u32 {
56+
42
57+
}
58+
59+
#[naked]
60+
#[linkage = "external"]
61+
extern "C" fn naked_external_linkage() -> u32 {
62+
unsafe { asm!("mov rax, 42", "ret", options(noreturn)) }
63+
}
64+
65+
#[cfg(not(windows))]
66+
#[linkage = "weak"]
67+
extern "C" fn vanilla_weak_linkage() -> u32 {
68+
42
69+
}
70+
71+
#[naked]
72+
#[cfg(not(windows))]
73+
#[linkage = "weak"]
74+
extern "C" fn naked_weak_linkage() -> u32 {
75+
unsafe { asm!("mov rax, 42", "ret", options(noreturn)) }
76+
}
77+
78+
// functions that are declared in an `extern "C"` block are currently not exported
79+
// this maybe should change in the future, this is just tracking the current behavior
80+
// reported in https://github.com/rust-lang/rust/issues/128071
81+
std::arch::global_asm! {
82+
".globl function_defined_in_global_asm",
83+
"function_defined_in_global_asm:",
84+
"ret",
85+
}
86+
87+
extern "C" {
88+
pub fn function_defined_in_global_asm();
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//@ ignore-windows
2+
//@ only-x86_64
3+
use run_make_support::object::read::{File, Object, Symbol};
4+
use run_make_support::object::ObjectSymbol;
5+
use run_make_support::targets::is_windows;
6+
use run_make_support::{dynamic_lib_name, env_var, rfs, rustc};
7+
8+
fn main() {
9+
let rdylib_name = dynamic_lib_name("a_rust_dylib");
10+
rustc().arg("-Zshare-generics=no").input("a_rust_dylib.rs").run();
11+
12+
let binary_data = rfs::read(&rdylib_name);
13+
let rdylib = File::parse(&*binary_data).unwrap();
14+
15+
// naked should mirror vanilla
16+
not_exported(&rdylib, "private_vanilla");
17+
not_exported(&rdylib, "private_naked");
18+
19+
global_function(&rdylib, "public_vanilla");
20+
global_function(&rdylib, "public_naked");
21+
22+
not_exported(&rdylib, "public_vanilla_generic");
23+
not_exported(&rdylib, "public_naked_generic");
24+
25+
global_function(&rdylib, "vanilla_external_linkage");
26+
global_function(&rdylib, "naked_external_linkage");
27+
28+
// FIXME: make this work on windows (gnu and msvc). See the PR
29+
// https://github.com/rust-lang/rust/pull/128362 for some approaches
30+
// that don't work
31+
//
32+
// #[linkage = "weak"] does not work well on windows, we get
33+
//
34+
// lib.def : error LNK2001: unresolved external symbol naked_weak_linkage␍
35+
// lib.def : error LNK2001: unresolved external symbol vanilla_weak_linkage
36+
//
37+
// so just skip weak symbols on windows (for now)
38+
if !is_windows() {
39+
weak_function(&rdylib, "vanilla_weak_linkage");
40+
weak_function(&rdylib, "naked_weak_linkage");
41+
}
42+
43+
// functions that are declared in an `extern "C"` block are currently not exported
44+
// this maybe should change in the future, this is just tracking the current behavior
45+
// reported in https://github.com/rust-lang/rust/issues/128071
46+
not_exported(&rdylib, "function_defined_in_global_asm");
47+
48+
// share generics should expose the generic functions
49+
rustc().arg("-Zshare-generics=yes").input("a_rust_dylib.rs").run();
50+
let binary_data = rfs::read(&rdylib_name);
51+
let rdylib = File::parse(&*binary_data).unwrap();
52+
53+
global_function(&rdylib, "public_vanilla_generic");
54+
global_function(&rdylib, "public_naked_generic");
55+
}
56+
57+
#[track_caller]
58+
fn global_function(file: &File, symbol_name: &str) {
59+
let symbols = find_dynamic_symbol(file, symbol_name);
60+
let [symbol] = symbols.as_slice() else {
61+
panic!("symbol {symbol_name} occurs {} times", symbols.len())
62+
};
63+
64+
assert!(symbol.is_definition(), "`{symbol_name}` is not a function");
65+
assert!(symbol.is_global(), "`{symbol_name}` is not marked as global");
66+
}
67+
68+
#[track_caller]
69+
fn weak_function(file: &File, symbol_name: &str) {
70+
let symbols = find_dynamic_symbol(file, symbol_name);
71+
let [symbol] = symbols.as_slice() else {
72+
panic!("symbol {symbol_name} occurs {} times", symbols.len())
73+
};
74+
75+
assert!(symbol.is_definition(), "`{symbol_name}` is not a function");
76+
assert!(symbol.is_weak(), "`{symbol_name}` is not marked as weak");
77+
}
78+
79+
#[track_caller]
80+
fn not_exported(file: &File, symbol_name: &str) {
81+
assert_eq!(find_dynamic_symbol(file, symbol_name).len(), 0)
82+
}
83+
84+
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> bool {
85+
haystack.windows(needle.len()).any(|window| window == needle)
86+
}
87+
88+
fn find_dynamic_symbol<'file, 'data>(
89+
file: &'file File<'data>,
90+
expected: &str,
91+
) -> Vec<Symbol<'data, 'file>> {
92+
file.exports()
93+
.unwrap()
94+
.into_iter()
95+
.filter(|e| find_subsequence(e.name(), expected.as_bytes()))
96+
.filter_map(|e| file.symbol_by_name_bytes(e.name()))
97+
.collect()
98+
}

0 commit comments

Comments
 (0)