From b12629f8a954cb59f7c3f1fa8052153dc35015f2 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Thu, 29 May 2025 23:52:29 +0000 Subject: [PATCH 1/4] Add new unstable attribute: `#[export_visibility = ...]`. --- .../src/attributes/codegen_attrs.rs | 41 ++++++- compiler/rustc_attr_parsing/src/context.rs | 1 + .../rustc_codegen_ssa/src/codegen_attrs.rs | 20 +++- compiler/rustc_codegen_ssa/src/errors.rs | 17 +++ compiler/rustc_feature/src/builtin_attrs.rs | 1 + compiler/rustc_feature/src/unstable.rs | 2 + .../rustc_hir/src/attrs/data_structures.rs | 15 +++ .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + .../src/middle/codegen_fn_attrs.rs | 7 ++ .../rustc_monomorphize/src/partitioning.rs | 5 + compiler/rustc_passes/src/check_attr.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + src/tools/run-make-support/Cargo.toml | 2 +- .../run-make-support/src/artifact_names.rs | 16 ++- src/tools/run-make-support/src/targets.rs | 14 +++ tests/codegen-llvm/export-visibility.rs | 102 ++++++++++++++++++ .../run-make/cdylib-export-visibility/foo.rs | 29 +++++ .../cdylib-export-visibility/rmake.rs | 87 +++++++++++++++ ...sibility-with-rustc-std-internal-symbol.rs | 11 ++ ...lity-with-rustc-std-internal-symbol.stderr | 8 ++ ...export-visibility-with-unrecognized-arg.rs | 34 ++++++ ...rt-visibility-with-unrecognized-arg.stderr | 57 ++++++++++ .../export-visibility-without-export-name.rs | 8 ++ ...port-visibility-without-export-name.stderr | 8 ++ .../feature-gate-export-visibility.rs | 11 ++ .../feature-gate-export-visibility.stderr | 13 +++ 26 files changed, 504 insertions(+), 8 deletions(-) create mode 100644 tests/codegen-llvm/export-visibility.rs create mode 100644 tests/run-make/cdylib-export-visibility/foo.rs create mode 100644 tests/run-make/cdylib-export-visibility/rmake.rs create mode 100644 tests/ui/attributes/export-visibility-with-rustc-std-internal-symbol.rs create mode 100644 tests/ui/attributes/export-visibility-with-rustc-std-internal-symbol.stderr create mode 100644 tests/ui/attributes/export-visibility-with-unrecognized-arg.rs create mode 100644 tests/ui/attributes/export-visibility-with-unrecognized-arg.stderr create mode 100644 tests/ui/attributes/export-visibility-without-export-name.rs create mode 100644 tests/ui/attributes/export-visibility-without-export-name.stderr create mode 100644 tests/ui/feature-gates/feature-gate-export-visibility.rs create mode 100644 tests/ui/feature-gates/feature-gate-export-visibility.stderr diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 4909e0d35173c..0100d34187aa7 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -1,4 +1,6 @@ -use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, RtsanSetting, SanitizerSet, UsedBy}; +use rustc_hir::attrs::{ + CoverageAttrKind, ExportVisibilityAttrValue, OptimizeAttr, RtsanSetting, SanitizerSet, UsedBy, +}; use rustc_session::parse::feature_err; use super::prelude::*; @@ -153,6 +155,43 @@ impl SingleAttributeParser for ExportNameParser { } } +pub(crate) struct ExportVisibilityParser; + +impl SingleAttributeParser for ExportVisibilityParser { + const PATH: &[rustc_span::Symbol] = &[sym::export_visibility]; + const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = + AllowedTargets::AllowList(&[Allow(Target::Fn), Allow(Target::Static)]); + const TEMPLATE: AttributeTemplate = template!(NameValueStr: "visibility"); + + fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { + let Some(nv) = args.name_value() else { + cx.expected_name_value(cx.attr_span, None); + return None; + }; + let Some(sv) = nv.value_as_str() else { + cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); + return None; + }; + + let str_to_visibility = [("target_default", ExportVisibilityAttrValue::TargetDefault)]; + for &(s, visibility) in str_to_visibility.iter() { + if s == sv.as_str() { + return Some(AttributeKind::ExportVisibility { visibility, span: cx.attr_span }); + } + } + + let allowed_str_values = str_to_visibility + .into_iter() + .map(|(s, _visibility)| s) + .map(Symbol::intern) + .collect::>(); + cx.expected_specific_argument_strings(nv.value_span, &allowed_str_values); + None + } +} + pub(crate) struct RustcObjcClassParser; impl SingleAttributeParser for RustcObjcClassParser { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 802ee56f504b0..72f30016b89b5 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -184,6 +184,7 @@ attribute_parsers!( Single, Single, Single, + Single, Single, Single, Single, diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 1ceb01337b118..dd6570801c05e 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -1,6 +1,7 @@ use rustc_abi::{Align, ExternAbi}; use rustc_hir::attrs::{ - AttributeKind, EiiImplResolution, InlineAttr, Linkage, RtsanSetting, UsedBy, + AttributeKind, EiiImplResolution, ExportVisibilityAttrValue, InlineAttr, Linkage, RtsanSetting, + UsedBy, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; @@ -70,6 +71,13 @@ fn process_builtin_attrs( match attr { AttributeKind::Cold(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD, AttributeKind::ExportName { name, .. } => codegen_fn_attrs.symbol_name = Some(*name), + AttributeKind::ExportVisibility { visibility, .. } => { + codegen_fn_attrs.export_visibility = Some(match visibility { + ExportVisibilityAttrValue::TargetDefault => { + tcx.sess.default_visibility().into() + } + }); + } AttributeKind::Inline(inline, span) => { codegen_fn_attrs.inline = *inline; interesting_spans.inline = Some(*span); @@ -533,6 +541,16 @@ fn handle_lang_items( } err.emit(); } + + if codegen_fn_attrs.export_visibility.is_some() { + let span = find_attr!(attrs, ExportVisibility{span, ..} => *span).unwrap_or_default(); + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) { + tcx.dcx().emit_err(errors::ExportVisibilityWithRustcStdInternalSymbol { span }); + } + if !codegen_fn_attrs.contains_extern_indicator() { + tcx.dcx().emit_err(errors::ExportVisibilityWithoutNoMangleNorExportName { span }); + } + } } /// Generate the [`CodegenFnAttrs`] for an item (identified by the [`LocalDefId`]). diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index be1965f674911..5d57ec8d3c6f1 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -1268,3 +1268,20 @@ pub(crate) struct LtoProcMacro; #[diag("cannot prefer dynamic linking when performing LTO")] #[note("only 'staticlib', 'bin', and 'cdylib' outputs are supported with LTO")] pub(crate) struct DynamicLinkingWithLTO; + +#[derive(Diagnostic)] +#[diag("`#[export_visibility = ...]` cannot be used on internal language items")] +pub(crate) struct ExportVisibilityWithRustcStdInternalSymbol { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag( + "`#[export_visibility = ...]` will be ignored \ + without `export_name`, `no_mangle`, or similar attribute" +)] +pub(crate) struct ExportVisibilityWithoutNoMangleNorExportName { + #[primary_span] + pub span: Span, +} diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index db8f459ef0451..d1f30a14e74c2 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -649,6 +649,7 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ template!(NameValueStr: "name", "https://doc.rust-lang.org/reference/abi.html#the-export_name-attribute"), FutureWarnPreceding, EncodeCrossCrate::No ), + gated!(export_visibility, Normal, template!(NameValueStr: "visibility"), ErrorPreceding, EncodeCrossCrate::No, experimental!(export_visibility)), ungated!( unsafe(Edition2024) link_section, Normal, template!(NameValueStr: "name", "https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute"), diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 1d123385961aa..6ea5b53c7ebdf 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -474,6 +474,8 @@ declare_features! ( (incomplete, explicit_tail_calls, "1.72.0", Some(112788)), /// Allows using `#[export_stable]` which indicates that an item is exportable. (incomplete, export_stable, "1.88.0", Some(139939)), + /// Allows `#[export_visibility]` on definitions of statics and/or functions. + (unstable, export_visibility, "CURRENT_RUSTC_VERSION", Some(151425)), /// Externally implementable items (unstable, extern_item_impls, "1.94.0", Some(125418)), /// Allows defining `extern type`s. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 91409108a7533..26b9def29fbd0 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -251,6 +251,15 @@ impl Deprecation { } } +/// Pre-parsed value of `#[export_visibility = ...]` attribute. +/// +/// In a future RFC we may consider adding support for `Hidden`, `Protected`, and/or +/// `Interposable`. +#[derive(Clone, Copy, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub enum ExportVisibilityAttrValue { + TargetDefault, +} + /// There are three valid forms of the attribute: /// `#[used]`, which is equivalent to `#[used(linker)]` on targets that support it, but `#[used(compiler)]` if not. /// `#[used(compiler)]` @@ -1045,6 +1054,12 @@ pub enum AttributeKind { /// Represents `#[export_stable]`. ExportStable, + /// Represents [`#[export_visibility = ...]`](https://github.com/rust-lang/rust/issues/151425) + ExportVisibility { + visibility: ExportVisibilityAttrValue, + span: Span, + }, + /// Represents `#[feature(...)]` Feature(ThinVec, Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index cd41a2b9b28c7..7ed1ced4c6e9c 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -43,6 +43,7 @@ impl AttributeKind { EiiImpls(..) => No, ExportName { .. } => Yes, ExportStable => No, + ExportVisibility { .. } => Yes, Feature(..) => No, FfiConst(..) => No, FfiPure(..) => No, diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs index 4f600af0cbfce..0fb60841f9faa 100644 --- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs +++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs @@ -79,6 +79,12 @@ pub struct CodegenFnAttrs { /// be set when `link_name` is set. This is for foreign items with the /// "raw-dylib" kind. pub link_ordinal: Option, + /// The `#[export_visibility = "..."]` attribute, with values interpreted + /// as follows: + /// * `None` - use the "inherent" visibility (either based on the target platform, or provided via + /// `-Zdefault-visibility=...` command-line flag) + /// * `Some(...)` - use the item/symbol-specific visibility + pub export_visibility: Option, /// The `#[target_feature(enable = "...")]` attribute and the enabled /// features (only enabled features are supported right now). /// Implied target features have already been applied. @@ -224,6 +230,7 @@ impl CodegenFnAttrs { optimize: OptimizeAttr::Default, symbol_name: None, link_ordinal: None, + export_visibility: None, target_features: vec![], foreign_item_symbol_aliases: vec![], safe_target_features: false, diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs index d8f4e01945075..c1d5706985cb7 100644 --- a/compiler/rustc_monomorphize/src/partitioning.rs +++ b/compiler/rustc_monomorphize/src/partitioning.rs @@ -931,6 +931,11 @@ fn mono_item_visibility<'tcx>( } fn default_visibility(tcx: TyCtxt<'_>, id: DefId, is_generic: bool) -> Visibility { + // If present, then symbol-specific `#[export_visibility = ...]` "wins". + if let Some(visibility) = tcx.codegen_fn_attrs(id).export_visibility { + return visibility; + } + // Fast-path to avoid expensive query call below if tcx.sess.default_visibility() == SymbolVisibility::Interposable { return Visibility::Default; diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index dacb02afe1612..b7176c05be1ed 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -254,6 +254,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::EiiDeclaration { .. } | AttributeKind::ExportName { .. } | AttributeKind::ExportStable + | AttributeKind::ExportVisibility { .. } | AttributeKind::Feature(..) | AttributeKind::FfiConst(..) | AttributeKind::Fundamental diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 731a838530729..35c614e9e5771 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -907,6 +907,7 @@ symbols! { export_name, export_stable, export_symbols: "export-symbols", + export_visibility, expr, expr_2021, expr_fragment_specifier_2024, diff --git a/src/tools/run-make-support/Cargo.toml b/src/tools/run-make-support/Cargo.toml index 918f5ef0d5069..8f43c266ca359 100644 --- a/src/tools/run-make-support/Cargo.toml +++ b/src/tools/run-make-support/Cargo.toml @@ -13,7 +13,7 @@ edition = "2024" bstr = "1.12" gimli = "0.32" libc = "0.2" -object = "0.37" +object = { version = "0.37", features = ["read", "compression", "wasm"] } regex = "1.11" serde_json = "1.0" similar = "2.7" diff --git a/src/tools/run-make-support/src/artifact_names.rs b/src/tools/run-make-support/src/artifact_names.rs index a2bb118694462..bbb3c3e734a1f 100644 --- a/src/tools/run-make-support/src/artifact_names.rs +++ b/src/tools/run-make-support/src/artifact_names.rs @@ -2,7 +2,7 @@ //! libraries which are target-dependent. use crate::target; -use crate::targets::is_windows_msvc; +use crate::targets::{is_wasi, is_windows, is_windows_msvc}; /// Construct the static library name based on the target. #[track_caller] @@ -22,18 +22,24 @@ pub fn dynamic_lib_name(name: &str) -> String { format!("{}{name}.{}", dynamic_lib_prefix(), dynamic_lib_extension()) } +/// Returns the value of `DLL_PREFIX` from `library/std/src/sys/env_consts.rs` +/// for the target platform indicated by `crate::target()`. fn dynamic_lib_prefix() -> &'static str { - if target().contains("windows") { "" } else { "lib" } + // FIXME: Cover more exotic platform like `uefi`. + if is_wasi() || is_windows() { "" } else { "lib" } } -/// Construct the dynamic library extension based on the target. +/// Returns the value of `DLL_EXTENSION` from `library/std/src/sys/env_consts.rs` +/// for the target platform indicated by `crate::target()`. #[must_use] pub fn dynamic_lib_extension() -> &'static str { + // FIXME: Cover more exotic platform like `uefi`. let target = target(); - if target.contains("apple") { "dylib" - } else if target.contains("windows") { + } else if is_wasi() { + "wasm" + } else if is_windows() { "dll" } else if target.contains("aix") { "a" diff --git a/src/tools/run-make-support/src/targets.rs b/src/tools/run-make-support/src/targets.rs index 6288f5f7c59da..dcc23f6a9f82b 100644 --- a/src/tools/run-make-support/src/targets.rs +++ b/src/tools/run-make-support/src/targets.rs @@ -34,6 +34,20 @@ pub fn is_win7() -> bool { target().contains("win7") } +/// Check if target uses WASI +pub fn is_wasi() -> bool { + // The condition below is roughly equivalent to the following `cfg` + // attribute from `library/std/src/sys/env_consts.rs`. + // + // ``` + // #[cfg(all( + // target_family = "wasm", + // not(any(target_os = "emscripten", target_os = "linux")) + // ))] + // ``` + target().starts_with("wasm") && !target().contains("linux") && !target().contains("emscripten") +} + /// Check if target uses macOS. #[must_use] pub fn is_darwin() -> bool { diff --git a/tests/codegen-llvm/export-visibility.rs b/tests/codegen-llvm/export-visibility.rs new file mode 100644 index 0000000000000..ff0b785e53d97 --- /dev/null +++ b/tests/codegen-llvm/export-visibility.rs @@ -0,0 +1,102 @@ +// Verifies that `#[export_visibility = ...]` can override the visibility +// that is normally implied by `#[export_name]` or `#[no_mangle]`. +// +// High-level test expectations for items with `#[export_name = ...]` +// (or with `#[no_mangle]`) and: +// +// * Without `#[export_visibility = ...]` => public +// * `#[export_visibility = "target_default"]` => value inherited from the target +// platform or from the `-Zdefault-visibility=...` command-line flag +// (this expectation depends on whether the `...-HIDDEN` vs `...-PROTECTED` +// test revisions are used). +// +// Note that what we call "public" in the expectations above is also referred +// to as "default" in LLVM docs - see +// https://llvm.org/docs/LangRef.html#visibility-styles + +//@ revisions: LINUX-X86-HIDDEN LINUX-X86-PROTECTED +//@[LINUX-X86-HIDDEN] compile-flags: -Zdefault-visibility=hidden +//@[LINUX-X86-PROTECTED] compile-flags: -Zdefault-visibility=protected + +// Exact LLVM IR differs depending on the target triple (e.g. `hidden constant` +// vs `internal constant` vs `constant`). Because of this, we only apply the +// specific test expectations below to one specific target triple. +// +// Note that `tests/run-make/cdylib-export-visibility` provides similar +// test coverage, but in an LLVM-IR-agnostic / platform-agnostic way. +//@[LINUX-X86-HIDDEN] needs-llvm-components: x86 +//@[LINUX-X86-HIDDEN] compile-flags: --target x86_64-unknown-linux-gnu +//@[LINUX-X86-PROTECTED] needs-llvm-components: x86 +//@[LINUX-X86-PROTECTED] compile-flags: --target x86_64-unknown-linux-gnu + +// This test focuses on rlib to exercise the scenario described in +// https://github.com/rust-lang/rust/issues/73958#issuecomment-2891711649 +#![crate_type = "rlib"] +#![feature(export_visibility)] +// Relying on `minicore` makes it easier to run the test, even if the host is +// not a linux-x86 machine. +//@ add-minicore +//@ edition: 2024 +#![feature(no_core)] +#![no_core] +use minicore::*; + +/////////////////////////////////////////////////////////////////////// +// The tests below focus on how `#[export_visibility = ...]` works for +// a `static`. The tests are based on similar tests in +// `tests/codegen/default-visibility.rs` + +#[unsafe(export_name = "static_export_name_no_attr")] +pub static TEST_STATIC_NO_ATTR: u32 = 1101; + +#[unsafe(export_name = "static_export_name_target_default")] +#[export_visibility = "target_default"] +pub static TESTED_STATIC_ATTR_ASKS_TO_TARGET_DEFAULT: u32 = 1102; + +#[unsafe(no_mangle)] +pub static static_no_mangle_no_attr: u32 = 1201; + +#[unsafe(no_mangle)] +#[export_visibility = "target_default"] +pub static static_no_mangle_target_default: u32 = 1202; + +// LINUX-X86-HIDDEN: @static_export_name_no_attr = local_unnamed_addr constant +// LINUX-X86-HIDDEN: @static_export_name_target_default = hidden local_unnamed_addr constant +// LINUX-X86-HIDDEN: @static_no_mangle_no_attr = local_unnamed_addr constant +// LINUX-X86-HIDDEN: @static_no_mangle_target_default = hidden local_unnamed_addr constant + +// LINUX-X86-PROTECTED: @static_export_name_no_attr = local_unnamed_addr constant +// LINUX-X86-PROTECTED: @static_export_name_target_default = protected local_unnamed_addr constant +// LINUX-X86-PROTECTED: @static_no_mangle_no_attr = local_unnamed_addr constant +// LINUX-X86-PROTECTED: @static_no_mangle_target_default = protected local_unnamed_addr constant + +/////////////////////////////////////////////////////////////////////// +// The tests below focus on how `#[export_visibility = ...]` works for +// a `fn`. +// +// The tests below try to mimics how `cxx` exports known/hardcoded helpers (e.g. +// `cxxbridge1$string$drop` [1]) as well as build-time-generated thunks (e.g. +// `serde_json_lenient$cxxbridge1$decode_json` from https://crbug.com/418073233#comment7). +// +// [1] +// https://github.com/dtolnay/cxx/blob/ebdd6a0c63ae10dc5224ed21970b7a0504657434/src/symbols/rust_string.rs#L83-L86 + +#[unsafe(export_name = "test_fn_no_attr")] +unsafe extern "C" fn test_fn_no_attr() -> u32 { + // We return a unique integer to ensure that each function has a unique body + // and therefore that identical code folding (ICF) won't fold the functions + // when linking. + 2001 +} + +#[unsafe(export_name = "test_fn_target_default")] +#[export_visibility = "target_default"] +unsafe extern "C" fn test_fn_asks_for_target_default() -> u32 { + 2002 +} + +// LINUX-X86-HIDDEN: define noundef i32 @test_fn_no_attr +// LINUX-X86-HIDDEN: define hidden noundef i32 @test_fn_target_default + +// LINUX-X86-PROTECTED: define noundef i32 @test_fn_no_attr +// LINUX-X86-PROTECTED: define protected noundef i32 @test_fn_target_default diff --git a/tests/run-make/cdylib-export-visibility/foo.rs b/tests/run-make/cdylib-export-visibility/foo.rs new file mode 100644 index 0000000000000..0b0b9239a2080 --- /dev/null +++ b/tests/run-make/cdylib-export-visibility/foo.rs @@ -0,0 +1,29 @@ +#![crate_type = "cdylib"] +#![feature(export_visibility)] +// `no_std` makes it slightly easier to run the test when cross-compiling. +// Ideally the test would use `no_core`, but `//@ add-minicore` doesn't seem +// to work... +#![no_std] +//@ edition: 2024 + +#[unsafe(no_mangle)] +unsafe extern "C" fn test_fn_no_export_visibility_attribute() -> u32 { + // Using unique integer means that the functions return different results + // and therefore identical code folding (ICF) in the linker won't apply. + 16 // line number; can't use `line!` with `no_core` +} + +#[unsafe(no_mangle)] +#[export_visibility = "target_default"] +unsafe extern "C" fn test_fn_export_visibility_asks_for_target_default() -> u32 { + // Using unique integer means that the functions return different results + // and therefore identical code folding (ICF) in the linker won't apply. + 24 // line number; can't use `line!` with `no_core` +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // The infinite loop should never run - we only look at symbol visibilities + // of `test_fn_...` above. + loop {} +} diff --git a/tests/run-make/cdylib-export-visibility/rmake.rs b/tests/run-make/cdylib-export-visibility/rmake.rs new file mode 100644 index 0000000000000..05baefd65dcb0 --- /dev/null +++ b/tests/run-make/cdylib-export-visibility/rmake.rs @@ -0,0 +1,87 @@ +// This test builds `foo.rs` into a `cdylib` and verifies that +// `#[export_visibility = ...]` affects visibility of symbols. +// +// This test is loosely based on manual test steps described when +// discussing the related RFC at: +// https://github.com/rust-lang/rfcs/pull/3834#issuecomment-3403039933 + +//@ needs-crate-type: cdylib +//@ needs-dynamic-linking + +// See https://github.com/rust-lang/rust/pull/151431#issuecomment-3923604898 +// and earlier comments that explain the problems encountered when attempting +// to enable this test for WASM. +//@ ignore-wasm + +// See https://github.com/rust-lang/rust/pull/151431#issuecomment-3923203589 +// for why this test skips `nvptx64-nvidia-cuda` and similar targets. +//@ ignore-nvptx64 + +use std::collections::HashSet; + +use run_make_support::symbols::exported_dynamic_symbol_names; +use run_make_support::targets::is_wasi; +use run_make_support::{dynamic_lib_name, object, rustc}; + +struct TestCase { + name: &'static str, + extra_rustc_arg: Option<&'static str>, + expected_exported_symbols: &'static [&'static str], + expected_private_symbols: &'static [&'static str], +} + +impl TestCase { + fn run(&self) { + let test_name = self.name; + + let mut rustc = rustc(); + rustc.input("foo.rs"); + rustc.arg("-Cpanic=abort"); + if let Some(extra_arg) = self.extra_rustc_arg { + rustc.arg(extra_arg); + } + rustc.run(); + + let lib_path = dynamic_lib_name("foo"); + let object_file_bytes = std::fs::read(&lib_path) + .unwrap_or_else(|e| panic!("{test_name}: failed to read `{lib_path}`: {e}")); + let object_file = object::File::parse(object_file_bytes.as_slice()) + .unwrap_or_else(|e| panic!("{test_name}: failed to parse `{lib_path}`: {e}")); + let actual_exported_symbols = + exported_dynamic_symbol_names(&object_file).into_iter().collect::>(); + + for s in self.expected_exported_symbols { + assert!( + actual_exported_symbols.contains(s), + "{test_name}: Expecting `{s}` to be an actually exported symbol in `{lib_path}`", + ); + } + for s in self.expected_private_symbols { + assert!( + !actual_exported_symbols.contains(s), + "{test_name}: Expecting `{s}` to *not* be exported from `{lib_path}`", + ); + } + } +} + +fn main() { + TestCase { + name: "Test #1", + extra_rustc_arg: Some("-Zdefault-visibility=hidden"), + expected_exported_symbols: &["test_fn_no_export_visibility_attribute"], + expected_private_symbols: &["test_fn_export_visibility_asks_for_target_default"], + } + .run(); + + TestCase { + name: "Test #2", + extra_rustc_arg: None, + expected_exported_symbols: &[ + "test_fn_no_export_visibility_attribute", + "test_fn_export_visibility_asks_for_target_default", + ], + expected_private_symbols: &[], + } + .run(); +} diff --git a/tests/ui/attributes/export-visibility-with-rustc-std-internal-symbol.rs b/tests/ui/attributes/export-visibility-with-rustc-std-internal-symbol.rs new file mode 100644 index 0000000000000..fcacaf160827d --- /dev/null +++ b/tests/ui/attributes/export-visibility-with-rustc-std-internal-symbol.rs @@ -0,0 +1,11 @@ +// This test verfies that `#[export_visibility = ...]` will report an error +// when applied to an item that also has `#[rustc_std_internal_symbol]` +// attribute. +#![feature(export_visibility)] +#![feature(rustc_attrs)] +#[export_visibility = "target_default"] +//~^ERROR: #[export_visibility = ...]` cannot be used on internal language items +#[rustc_std_internal_symbol] +pub static TESTED_STATIC: [u8; 6] = *b"foobar"; + +fn main() {} diff --git a/tests/ui/attributes/export-visibility-with-rustc-std-internal-symbol.stderr b/tests/ui/attributes/export-visibility-with-rustc-std-internal-symbol.stderr new file mode 100644 index 0000000000000..6685a20905760 --- /dev/null +++ b/tests/ui/attributes/export-visibility-with-rustc-std-internal-symbol.stderr @@ -0,0 +1,8 @@ +error: `#[export_visibility = ...]` cannot be used on internal language items + --> $DIR/export-visibility-with-rustc-std-internal-symbol.rs:6:1 + | +LL | #[export_visibility = "target_default"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/attributes/export-visibility-with-unrecognized-arg.rs b/tests/ui/attributes/export-visibility-with-unrecognized-arg.rs new file mode 100644 index 0000000000000..17aeddbd7cc13 --- /dev/null +++ b/tests/ui/attributes/export-visibility-with-unrecognized-arg.rs @@ -0,0 +1,34 @@ +// This test verfies that `#[export_visibility = ...]` will report an error +// when the argument cannot be parsed. +#![feature(export_visibility)] +#[no_mangle] +#[export_visibility = "unrecognized visibility value"] +//~^ ERROR: malformed `export_visibility` attribute input +pub static TESTED_STATIC: [u8; 6] = *b"foobar"; + +// The following `static`s verify that `hidden`, `protected`, and `interposable` +// are not supported yet. +#[no_mangle] +#[export_visibility = "hidden"] +//~^ ERROR: malformed `export_visibility` attribute input +pub static TESTED_STATIC_HIDDEN: [u8; 6] = *b"foobar"; +#[no_mangle] +#[export_visibility = "protected"] +//~^ ERROR: malformed `export_visibility` attribute input +pub static TESTED_STATIC_PROTECTED: [u8; 6] = *b"foobar"; +#[no_mangle] +#[export_visibility = "interposable"] +//~^ ERROR: malformed `export_visibility` attribute input +pub static TESTED_STATIC_INTERPOSABLE: [u8; 6] = *b"foobar"; + +// The following `static`s verify that other visibility spellings are also not supported. +#[no_mangle] +#[export_visibility = "default"] +//~^ ERROR: malformed `export_visibility` attribute input +pub static TESTED_STATIC_DEFAULT: [u8; 6] = *b"foobar"; +#[no_mangle] +#[export_visibility = "public"] +//~^ ERROR: malformed `export_visibility` attribute input +pub static TESTED_STATIC_PUBLIC: [u8; 6] = *b"foobar"; + +fn main() {} diff --git a/tests/ui/attributes/export-visibility-with-unrecognized-arg.stderr b/tests/ui/attributes/export-visibility-with-unrecognized-arg.stderr new file mode 100644 index 0000000000000..1c75610f8755c --- /dev/null +++ b/tests/ui/attributes/export-visibility-with-unrecognized-arg.stderr @@ -0,0 +1,57 @@ +error[E0539]: malformed `export_visibility` attribute input + --> $DIR/export-visibility-with-unrecognized-arg.rs:5:1 + | +LL | #[export_visibility = "unrecognized visibility value"] + | ^^^^^^^^^^^^^^^^^^^^^^-------------------------------^ + | | | + | | the only valid argument here is "target_default" + | help: must be of the form: `#[export_visibility = "visibility"]` + +error[E0539]: malformed `export_visibility` attribute input + --> $DIR/export-visibility-with-unrecognized-arg.rs:12:1 + | +LL | #[export_visibility = "hidden"] + | ^^^^^^^^^^^^^^^^^^^^^^--------^ + | | | + | | the only valid argument here is "target_default" + | help: must be of the form: `#[export_visibility = "visibility"]` + +error[E0539]: malformed `export_visibility` attribute input + --> $DIR/export-visibility-with-unrecognized-arg.rs:16:1 + | +LL | #[export_visibility = "protected"] + | ^^^^^^^^^^^^^^^^^^^^^^-----------^ + | | | + | | the only valid argument here is "target_default" + | help: must be of the form: `#[export_visibility = "visibility"]` + +error[E0539]: malformed `export_visibility` attribute input + --> $DIR/export-visibility-with-unrecognized-arg.rs:20:1 + | +LL | #[export_visibility = "interposable"] + | ^^^^^^^^^^^^^^^^^^^^^^--------------^ + | | | + | | the only valid argument here is "target_default" + | help: must be of the form: `#[export_visibility = "visibility"]` + +error[E0539]: malformed `export_visibility` attribute input + --> $DIR/export-visibility-with-unrecognized-arg.rs:26:1 + | +LL | #[export_visibility = "default"] + | ^^^^^^^^^^^^^^^^^^^^^^---------^ + | | | + | | the only valid argument here is "target_default" + | help: must be of the form: `#[export_visibility = "visibility"]` + +error[E0539]: malformed `export_visibility` attribute input + --> $DIR/export-visibility-with-unrecognized-arg.rs:30:1 + | +LL | #[export_visibility = "public"] + | ^^^^^^^^^^^^^^^^^^^^^^--------^ + | | | + | | the only valid argument here is "target_default" + | help: must be of the form: `#[export_visibility = "visibility"]` + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0539`. diff --git a/tests/ui/attributes/export-visibility-without-export-name.rs b/tests/ui/attributes/export-visibility-without-export-name.rs new file mode 100644 index 0000000000000..bc50388206ad8 --- /dev/null +++ b/tests/ui/attributes/export-visibility-without-export-name.rs @@ -0,0 +1,8 @@ +// This test verfies that `#[export_visibility = ...]` cannot be used without +// either `#[export_name = ...]` or `#[no_mangle]`. +#![feature(export_visibility)] +#[export_visibility = "target_default"] +//~^ ERROR: #[export_visibility = ...]` will be ignored +pub static TESTED_STATIC: [u8; 6] = *b"foobar"; + +fn main() {} diff --git a/tests/ui/attributes/export-visibility-without-export-name.stderr b/tests/ui/attributes/export-visibility-without-export-name.stderr new file mode 100644 index 0000000000000..027c4fd62faa0 --- /dev/null +++ b/tests/ui/attributes/export-visibility-without-export-name.stderr @@ -0,0 +1,8 @@ +error: `#[export_visibility = ...]` will be ignored without `export_name`, `no_mangle`, or similar attribute + --> $DIR/export-visibility-without-export-name.rs:4:1 + | +LL | #[export_visibility = "target_default"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/feature-gates/feature-gate-export-visibility.rs b/tests/ui/feature-gates/feature-gate-export-visibility.rs new file mode 100644 index 0000000000000..eb059b2729fe0 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-export-visibility.rs @@ -0,0 +1,11 @@ +// This test verfies that `#[export_visibility = ...]` cannot be used without +// opting into the corresponding unstable feature via +// `#![feature(export_visibility)]`. +#[export_visibility = "target_default"] +//~^ ERROR: the `#[export_visibility]` attribute is an experimental feature +// `#[export_name = ...]` is present to avoid hitting the following error: +// export visibility will be ignored without `export_name`, `no_mangle`, or similar attribute +#[unsafe(export_name = "exported_static")] +pub static TESTED_STATIC: [u8; 6] = *b"foobar"; + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-export-visibility.stderr b/tests/ui/feature-gates/feature-gate-export-visibility.stderr new file mode 100644 index 0000000000000..9e5bc27bc745e --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-export-visibility.stderr @@ -0,0 +1,13 @@ +error[E0658]: the `#[export_visibility]` attribute is an experimental feature + --> $DIR/feature-gate-export-visibility.rs:4:1 + | +LL | #[export_visibility = "target_default"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151425 for more information + = help: add `#![feature(export_visibility)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. From c63b5375d1ea29624cefb9004923388aac730ed4 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Fri, 27 Feb 2026 22:39:19 +0000 Subject: [PATCH 2/4] `@ ignore-aarch64-apple` in the new `cdylib-export-visibility/rmake.rs` test. --- tests/run-make/cdylib-export-visibility/rmake.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/run-make/cdylib-export-visibility/rmake.rs b/tests/run-make/cdylib-export-visibility/rmake.rs index 05baefd65dcb0..37c56d18d7953 100644 --- a/tests/run-make/cdylib-export-visibility/rmake.rs +++ b/tests/run-make/cdylib-export-visibility/rmake.rs @@ -17,6 +17,10 @@ // for why this test skips `nvptx64-nvidia-cuda` and similar targets. //@ ignore-nvptx64 +// See https://github.com/rust-lang/rust/pull/151431#issuecomment-3975560321 +// for why this test skips the `aarch64-apple` target. +//@ ignore-aarch64-apple + use std::collections::HashSet; use run_make_support::symbols::exported_dynamic_symbol_names; From b7119a7dbd45da67bfb6974e0a8de845cb3c6bb6 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Sat, 28 Feb 2026 00:02:37 +0000 Subject: [PATCH 3/4] Fixing the spelling of `@ ignore-aarch64-apple-darwin` --- tests/run-make/cdylib-export-visibility/rmake.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run-make/cdylib-export-visibility/rmake.rs b/tests/run-make/cdylib-export-visibility/rmake.rs index 37c56d18d7953..20f72b5d65e4f 100644 --- a/tests/run-make/cdylib-export-visibility/rmake.rs +++ b/tests/run-make/cdylib-export-visibility/rmake.rs @@ -18,8 +18,8 @@ //@ ignore-nvptx64 // See https://github.com/rust-lang/rust/pull/151431#issuecomment-3975560321 -// for why this test skips the `aarch64-apple` target. -//@ ignore-aarch64-apple +// for why this test skips the `aarch64-apple-darwin` target. +//@ ignore-aarch64-apple-darwin use std::collections::HashSet; From a980a3ae35f5c18d9fd9b32ca60aeda0edef7721 Mon Sep 17 00:00:00 2001 From: Lukasz Anforowicz Date: Tue, 3 Mar 2026 21:17:50 +0000 Subject: [PATCH 4/4] Revert src/tools/run-make-support/ changes --- src/tools/run-make-support/Cargo.toml | 2 +- src/tools/run-make-support/src/artifact_names.rs | 16 +++++----------- src/tools/run-make-support/src/targets.rs | 14 -------------- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/src/tools/run-make-support/Cargo.toml b/src/tools/run-make-support/Cargo.toml index 8f43c266ca359..918f5ef0d5069 100644 --- a/src/tools/run-make-support/Cargo.toml +++ b/src/tools/run-make-support/Cargo.toml @@ -13,7 +13,7 @@ edition = "2024" bstr = "1.12" gimli = "0.32" libc = "0.2" -object = { version = "0.37", features = ["read", "compression", "wasm"] } +object = "0.37" regex = "1.11" serde_json = "1.0" similar = "2.7" diff --git a/src/tools/run-make-support/src/artifact_names.rs b/src/tools/run-make-support/src/artifact_names.rs index bbb3c3e734a1f..a2bb118694462 100644 --- a/src/tools/run-make-support/src/artifact_names.rs +++ b/src/tools/run-make-support/src/artifact_names.rs @@ -2,7 +2,7 @@ //! libraries which are target-dependent. use crate::target; -use crate::targets::{is_wasi, is_windows, is_windows_msvc}; +use crate::targets::is_windows_msvc; /// Construct the static library name based on the target. #[track_caller] @@ -22,24 +22,18 @@ pub fn dynamic_lib_name(name: &str) -> String { format!("{}{name}.{}", dynamic_lib_prefix(), dynamic_lib_extension()) } -/// Returns the value of `DLL_PREFIX` from `library/std/src/sys/env_consts.rs` -/// for the target platform indicated by `crate::target()`. fn dynamic_lib_prefix() -> &'static str { - // FIXME: Cover more exotic platform like `uefi`. - if is_wasi() || is_windows() { "" } else { "lib" } + if target().contains("windows") { "" } else { "lib" } } -/// Returns the value of `DLL_EXTENSION` from `library/std/src/sys/env_consts.rs` -/// for the target platform indicated by `crate::target()`. +/// Construct the dynamic library extension based on the target. #[must_use] pub fn dynamic_lib_extension() -> &'static str { - // FIXME: Cover more exotic platform like `uefi`. let target = target(); + if target.contains("apple") { "dylib" - } else if is_wasi() { - "wasm" - } else if is_windows() { + } else if target.contains("windows") { "dll" } else if target.contains("aix") { "a" diff --git a/src/tools/run-make-support/src/targets.rs b/src/tools/run-make-support/src/targets.rs index dcc23f6a9f82b..6288f5f7c59da 100644 --- a/src/tools/run-make-support/src/targets.rs +++ b/src/tools/run-make-support/src/targets.rs @@ -34,20 +34,6 @@ pub fn is_win7() -> bool { target().contains("win7") } -/// Check if target uses WASI -pub fn is_wasi() -> bool { - // The condition below is roughly equivalent to the following `cfg` - // attribute from `library/std/src/sys/env_consts.rs`. - // - // ``` - // #[cfg(all( - // target_family = "wasm", - // not(any(target_os = "emscripten", target_os = "linux")) - // ))] - // ``` - target().starts_with("wasm") && !target().contains("linux") && !target().contains("emscripten") -} - /// Check if target uses macOS. #[must_use] pub fn is_darwin() -> bool {