Skip to content
Open
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
3 changes: 3 additions & 0 deletions compiler/rustc_attr_parsing/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ attr_parsing_invalid_attr_unsafe = `{$name}` is not an unsafe attribute
.suggestion = remove the `unsafe(...)`
.note = extraneous unsafe is not allowed in attributes

attr_parsing_invalid_export_visibility =
invalid export visibility: {$unrecognized_visibility}

attr_parsing_invalid_issue_string =
`issue` must be a non-zero numeric string or "none"
.must_not_be_zero = `issue` must not be "0", use "none" instead
Expand Down
40 changes: 37 additions & 3 deletions compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, RtsanSetting, SanitizerSet, UsedBy};
use std::str::FromStr;

use rustc_hir::attrs::{
CoverageAttrKind, ExportVisibilityAttrValue, OptimizeAttr, RtsanSetting, SanitizerSet, UsedBy,
};
use rustc_session::parse::feature_err;

use super::prelude::*;
use crate::session_diagnostics::{
NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector,
ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral,
InvalidExportVisibility, NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass,
NullOnObjcSelector, ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral,
};
use crate::target_checking::Policy::AllowSilent;

Expand Down Expand Up @@ -153,6 +157,36 @@ impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
}
}

pub(crate) struct ExportVisibilityParser;

impl<S: Stage> SingleAttributeParser<S> for ExportVisibilityParser {
const PATH: &[rustc_span::Symbol] = &[sym::export_visibility];
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
const ON_DUPLICATE: OnDuplicate<S> = 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<AttributeKind> {
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 Ok(visibility) = ExportVisibilityAttrValue::from_str(sv.as_str()) else {
cx.emit_err(InvalidExportVisibility {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this use cx.expected_specific_argument_strings instead?
(not sure)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also not sure. Right now the new attribute expects a string literal as an argument (e.g. #[export_visibility = "target_default"] - this is the syntax that has been used so far by the RFC) . And it seems that expected_specific_argument_strings is meant to be used with symbols rather than with string literals (e.g. #[export_visibility = target_default]). Do you think the new attribute should use the latter syntax?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the attribute should continue to use the #[export_visibility = "target_default"] syntax.
If expected_specific_argument_strings does not give the proper suggestions, could you make a new method that does?

span: nv.value_span,
unrecognized_visibility: sv.to_string(),
});
return None;
};
Some(AttributeKind::ExportVisibility { visibility, span: cx.attr_span })
}
}

pub(crate) struct ObjcClassParser;

impl<S: Stage> SingleAttributeParser<S> for ObjcClassParser {
Expand Down
9 changes: 5 additions & 4 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ use crate::attributes::allow_unstable::{
use crate::attributes::body::CoroutineParser;
use crate::attributes::cfi_encoding::CfiEncodingParser;
use crate::attributes::codegen_attrs::{
ColdParser, CoverageParser, EiiForeignItemParser, ExportNameParser, ForceTargetFeatureParser,
NakedParser, NoMangleParser, ObjcClassParser, ObjcSelectorParser, OptimizeParser,
PatchableFunctionEntryParser, RustcPassIndirectlyInNonRusticAbisParser, SanitizeParser,
TargetFeatureParser, ThreadLocalParser, TrackCallerParser, UsedParser,
ColdParser, CoverageParser, EiiForeignItemParser, ExportNameParser, ExportVisibilityParser,
ForceTargetFeatureParser, NakedParser, NoMangleParser, ObjcClassParser, ObjcSelectorParser,
OptimizeParser, PatchableFunctionEntryParser, RustcPassIndirectlyInNonRusticAbisParser,
SanitizeParser, TargetFeatureParser, ThreadLocalParser, TrackCallerParser, UsedParser,
};
use crate::attributes::confusables::ConfusablesParser;
use crate::attributes::crate_level::{
Expand Down Expand Up @@ -212,6 +212,7 @@ attribute_parsers!(
Single<DoNotRecommendParser>,
Single<DummyParser>,
Single<ExportNameParser>,
Single<ExportVisibilityParser>,
Single<IgnoreParser>,
Single<InlineParser>,
Single<InstructionSetParser>,
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,14 @@ pub(crate) struct UnusedMultiple {
pub name: Symbol,
}

#[derive(Diagnostic)]
#[diag(attr_parsing_invalid_export_visibility)]
pub(crate) struct InvalidExportVisibility {
#[primary_span]
pub span: Span,
pub unrecognized_visibility: String,
}

#[derive(Diagnostic)]
#[diag(attr_parsing_null_on_export, code = E0648)]
pub(crate) struct NullOnExport {
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_codegen_ssa/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ codegen_ssa_error_creating_remark_dir = failed to create remark directory: {$err
codegen_ssa_error_writing_def_file =
error writing .DEF file: {$error}

codegen_ssa_export_visibility_with_rustc_std_internal_symbol =
`#[export_visibility = ...]` cannot be used on internal language items

codegen_ssa_export_visibility_without_no_mangle_nor_export_name =
`#[export_visibility = ...]` will be ignored without `export_name`, `no_mangle`, or similar attribute

codegen_ssa_extern_funcs_not_found = some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified

codegen_ssa_extract_bundled_libs_archive_member = failed to get data from archive member '{$rlib}': {$error}
Expand Down
21 changes: 20 additions & 1 deletion compiler/rustc_codegen_ssa/src/codegen_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use rustc_abi::{Align, ExternAbi};
use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, DiffActivity, DiffMode};
use rustc_ast::{LitKind, MetaItem, MetaItemInner};
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};
Expand Down Expand Up @@ -75,6 +76,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);
Expand Down Expand Up @@ -542,6 +550,17 @@ fn handle_lang_items(
}
err.emit();
}

if codegen_fn_attrs.export_visibility.is_some() {
let span = find_attr!(attrs, AttributeKind::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`]).
Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_codegen_ssa/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1254,3 +1254,17 @@ pub(crate) struct LtoProcMacro;
#[diag(codegen_ssa_dynamic_linking_with_lto)]
#[note]
pub(crate) struct DynamicLinkingWithLTO;

#[derive(Diagnostic)]
#[diag(codegen_ssa_export_visibility_with_rustc_std_internal_symbol)]
pub(crate) struct ExportVisibilityWithRustcStdInternalSymbol {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(codegen_ssa_export_visibility_without_no_mangle_nor_export_name)]
pub(crate) struct ExportVisibilityWithoutNoMangleNorExportName {
#[primary_span]
pub span: Span,
}
1 change: 1 addition & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,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.
Expand Down
24 changes: 24 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::borrow::Cow;
use std::path::PathBuf;
use std::str::FromStr;

pub use ReprAttr::*;
use rustc_abi::Align;
Expand Down Expand Up @@ -187,6 +188,26 @@ 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,
}

impl FromStr for ExportVisibilityAttrValue {
type Err = ();

fn from_str(s: &str) -> Result<ExportVisibilityAttrValue, Self::Err> {
match s {
"target_default" => Ok(ExportVisibilityAttrValue::TargetDefault),
_ => Err(()),
}
}
}

/// 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)]`
Expand Down Expand Up @@ -874,6 +895,9 @@ pub enum AttributeKind {
/// Represents `#[export_stable]`.
ExportStable,

/// Represents [`#[export_visibility = ...]`](https://github.com/rust-lang/rust/issues/151425)
ExportVisibility { visibility: ExportVisibilityAttrValue, span: Span },

/// Represents `#[ffi_const]`.
FfiConst(Span),

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ impl AttributeKind {
EiiImpls(..) => No,
ExportName { .. } => Yes,
ExportStable => No,
ExportVisibility { .. } => Yes,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is exporting this attribute needed? (Not sure, genuine question)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also not sure. I think if #[export_name = ...] needs to be exported, then so does #[export_visibility = ...] although I can't really convincingly point to a specific scenario where this is needed.

Copy link
Contributor

@JonathanBrouwer JonathanBrouwer Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think export_name is exported for rustdoc, tho not sure. I suppose it can't hurt to set this to Yes for now to be sure, it's a pretty niche attribute anyways

FfiConst(..) => No,
FfiPure(..) => No,
Fundamental { .. } => Yes,
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u16>,
/// 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<Visibility>,
/// The `#[target_feature(enable = "...")]` attribute and the enabled
/// features (only enabled features are supported right now).
/// Implied target features have already been applied.
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_monomorphize/src/partitioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::EiiForeignItem
| AttributeKind::ExportName { .. }
| AttributeKind::ExportStable
| AttributeKind::ExportVisibility { .. }
| AttributeKind::FfiConst(..)
| AttributeKind::Fundamental
| AttributeKind::Ignore { .. }
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,7 @@ symbols! {
explicit_tail_calls,
export_name,
export_stable,
export_visibility,
expr,
expr_2021,
expr_fragment_specifier_2024,
Expand Down
102 changes: 102 additions & 0 deletions tests/codegen-llvm/export-visibility.rs
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading