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
41 changes: 40 additions & 1 deletion compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -153,6 +155,43 @@ 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 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::<Vec<_>>();
cx.expected_specific_argument_strings(nv.value_span, &allowed_str_values);
None
}
}

pub(crate) struct ObjcClassParser;

impl<S: Stage> SingleAttributeParser<S> for ObjcClassParser {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ attribute_parsers!(
Single<DoNotRecommendParser>,
Single<DummyParser>,
Single<ExportNameParser>,
Single<ExportVisibilityParser>,
Single<IgnoreParser>,
Single<InlineParser>,
Single<InstructionSetParser>,
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
17 changes: 17 additions & 0 deletions compiler/rustc_codegen_ssa/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1299,3 +1299,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,
}
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 @@ -478,6 +478,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
12 changes: 12 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,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)]`
Expand Down Expand Up @@ -872,6 +881,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 @@ -44,6 +44,7 @@ impl AttributeKind {
EiiImpls(..) => No,
ExportName { .. } => Yes,
ExportStable => No,
ExportVisibility { .. } => Yes,
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 @@ -252,6 +252,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 @@ -1003,6 +1003,7 @@ symbols! {
export_name,
export_stable,
export_symbols: "export-symbols",
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
15 changes: 15 additions & 0 deletions tests/run-make/cdylib-export-visibility/foo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#![crate_type = "cdylib"]
#![feature(export_visibility)]

#[unsafe(no_mangle)]
unsafe extern "C" fn test_fn_no_attr() -> u32 {
// Using `line!()` means that the functions return different results
// and therefore identical code folding (ICF) in the linker won't apply.
line!()
}

#[unsafe(no_mangle)]
#[export_visibility = "target_default"]
unsafe extern "C" fn test_fn_export_visibility_asks_for_target_default() -> u32 {
line!()
}
Loading
Loading