diff options
| author | Trevor Gross <t.gross35@gmail.com> | 2024-08-06 22:17:32 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-08-06 22:17:32 -0500 |
| commit | 10a7f93f12faab3b1bfa65688e3dcc6d1291fb0b (patch) | |
| tree | a497e113101174b8e6218b1707f29d71311fc806 | |
| parent | 0aaffdb25fa825ca32dca6e2e8d39abae26e0489 (diff) | |
| parent | 19679510c756926c4fda1c1d2daec5bea96ca6f2 (diff) | |
Rollup merge of #128362 - folkertdev:naked-function-symbol-visibility, r=bjorn3
add test for symbol visibility of `#[naked]` functions tracking issue: #90957 This test is extracted from https://github.com/rust-lang/rust/pull/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``
| -rw-r--r-- | tests/run-make/naked-symbol-visibility/a_rust_dylib.rs | 89 | ||||
| -rw-r--r-- | tests/run-make/naked-symbol-visibility/rmake.rs | 98 |
2 files changed, 187 insertions, 0 deletions
diff --git a/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs b/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs new file mode 100644 index 00000000000..f00123f006b --- /dev/null +++ b/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs @@ -0,0 +1,89 @@ +#![feature(naked_functions, asm_const, linkage)] +#![crate_type = "dylib"] + +use std::arch::asm; + +pub trait TraitWithConst { + const COUNT: u32; +} + +struct Test; + +impl TraitWithConst for Test { + const COUNT: u32 = 1; +} + +#[no_mangle] +fn entry() { + private_vanilla(); + private_naked(); + + public_vanilla_generic::<Test>(); + public_naked_generic::<Test>(); +} + +extern "C" fn private_vanilla() -> u32 { + 42 +} + +#[naked] +extern "C" fn private_naked() -> u32 { + unsafe { asm!("mov rax, 42", "ret", options(noreturn)) } +} + +#[no_mangle] +pub extern "C" fn public_vanilla() -> u32 { + 42 +} + +#[naked] +#[no_mangle] +pub extern "C" fn public_naked() -> u32 { + unsafe { asm!("mov rax, 42", "ret", options(noreturn)) } +} + +pub extern "C" fn public_vanilla_generic<T: TraitWithConst>() -> u32 { + T::COUNT +} + +#[naked] +pub extern "C" fn public_naked_generic<T: TraitWithConst>() -> u32 { + unsafe { asm!("mov rax, {}", "ret", const T::COUNT, options(noreturn)) } +} + +#[linkage = "external"] +extern "C" fn vanilla_external_linkage() -> u32 { + 42 +} + +#[naked] +#[linkage = "external"] +extern "C" fn naked_external_linkage() -> u32 { + unsafe { asm!("mov rax, 42", "ret", options(noreturn)) } +} + +#[cfg(not(windows))] +#[linkage = "weak"] +extern "C" fn vanilla_weak_linkage() -> u32 { + 42 +} + +#[naked] +#[cfg(not(windows))] +#[linkage = "weak"] +extern "C" fn naked_weak_linkage() -> u32 { + unsafe { asm!("mov rax, 42", "ret", options(noreturn)) } +} + +// functions that are declared in an `extern "C"` block are currently not exported +// this maybe should change in the future, this is just tracking the current behavior +// reported in https://github.com/rust-lang/rust/issues/128071 +std::arch::global_asm! { + ".globl function_defined_in_global_asm", + "function_defined_in_global_asm:", + "ret", +} + +extern "C" { + pub fn function_defined_in_global_asm(); +} diff --git a/tests/run-make/naked-symbol-visibility/rmake.rs b/tests/run-make/naked-symbol-visibility/rmake.rs new file mode 100644 index 00000000000..a32e62326eb --- /dev/null +++ b/tests/run-make/naked-symbol-visibility/rmake.rs @@ -0,0 +1,98 @@ +//@ ignore-windows +//@ only-x86_64 +use run_make_support::object::read::{File, Object, Symbol}; +use run_make_support::object::ObjectSymbol; +use run_make_support::targets::is_windows; +use run_make_support::{dynamic_lib_name, env_var, rfs, rustc}; + +fn main() { + let rdylib_name = dynamic_lib_name("a_rust_dylib"); + rustc().arg("-Zshare-generics=no").input("a_rust_dylib.rs").run(); + + let binary_data = rfs::read(&rdylib_name); + let rdylib = File::parse(&*binary_data).unwrap(); + + // naked should mirror vanilla + not_exported(&rdylib, "private_vanilla"); + not_exported(&rdylib, "private_naked"); + + global_function(&rdylib, "public_vanilla"); + global_function(&rdylib, "public_naked"); + + not_exported(&rdylib, "public_vanilla_generic"); + not_exported(&rdylib, "public_naked_generic"); + + global_function(&rdylib, "vanilla_external_linkage"); + global_function(&rdylib, "naked_external_linkage"); + + // FIXME: make this work on windows (gnu and msvc). See the PR + // https://github.com/rust-lang/rust/pull/128362 for some approaches + // that don't work + // + // #[linkage = "weak"] does not work well on windows, we get + // + // lib.def : error LNK2001: unresolved external symbol naked_weak_linkageā + // lib.def : error LNK2001: unresolved external symbol vanilla_weak_linkage + // + // so just skip weak symbols on windows (for now) + if !is_windows() { + weak_function(&rdylib, "vanilla_weak_linkage"); + weak_function(&rdylib, "naked_weak_linkage"); + } + + // functions that are declared in an `extern "C"` block are currently not exported + // this maybe should change in the future, this is just tracking the current behavior + // reported in https://github.com/rust-lang/rust/issues/128071 + not_exported(&rdylib, "function_defined_in_global_asm"); + + // share generics should expose the generic functions + rustc().arg("-Zshare-generics=yes").input("a_rust_dylib.rs").run(); + let binary_data = rfs::read(&rdylib_name); + let rdylib = File::parse(&*binary_data).unwrap(); + + global_function(&rdylib, "public_vanilla_generic"); + global_function(&rdylib, "public_naked_generic"); +} + +#[track_caller] +fn global_function(file: &File, symbol_name: &str) { + let symbols = find_dynamic_symbol(file, symbol_name); + let [symbol] = symbols.as_slice() else { + panic!("symbol {symbol_name} occurs {} times", symbols.len()) + }; + + assert!(symbol.is_definition(), "`{symbol_name}` is not a function"); + assert!(symbol.is_global(), "`{symbol_name}` is not marked as global"); +} + +#[track_caller] +fn weak_function(file: &File, symbol_name: &str) { + let symbols = find_dynamic_symbol(file, symbol_name); + let [symbol] = symbols.as_slice() else { + panic!("symbol {symbol_name} occurs {} times", symbols.len()) + }; + + assert!(symbol.is_definition(), "`{symbol_name}` is not a function"); + assert!(symbol.is_weak(), "`{symbol_name}` is not marked as weak"); +} + +#[track_caller] +fn not_exported(file: &File, symbol_name: &str) { + assert_eq!(find_dynamic_symbol(file, symbol_name).len(), 0) +} + +fn find_subsequence(haystack: &[u8], needle: &[u8]) -> bool { + haystack.windows(needle.len()).any(|window| window == needle) +} + +fn find_dynamic_symbol<'file, 'data>( + file: &'file File<'data>, + expected: &str, +) -> Vec<Symbol<'data, 'file>> { + file.exports() + .unwrap() + .into_iter() + .filter(|e| find_subsequence(e.name(), expected.as_bytes())) + .filter_map(|e| file.symbol_by_name_bytes(e.name())) + .collect() +} |
