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
77 changes: 77 additions & 0 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use rustc_metadata::{
walk_native_lib_search_dirs,
};
use rustc_middle::bug;
use rustc_middle::error::DuplicateEiiImpls;
use rustc_middle::lint::emit_lint_base;
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
use rustc_middle::middle::dependency_format::Linkage;
Expand Down Expand Up @@ -70,6 +71,73 @@ pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) {
}
}

fn eii_impl_crate_name(crate_info: &CrateInfo, cnum: CrateNum) -> Symbol {
if cnum == LOCAL_CRATE { crate_info.local_crate_name } else { crate_info.crate_name[&cnum] }
}

fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &CrateInfo) {
let Some(eii_linkage) = &crate_info.eii_linkage else {
return;
};

// A crate can request multiple linked outputs with overlapping dependency
// formats, so report each underlying conflict once.
let mut emitted = FxHashSet::default();

// This needs the dependency formats selected for the final artifact. The
// earlier EII pass still handles missing impls and duplicate explicit impls.
for dependency_formats in crate_info.dependency_formats.values() {
for (eii_index, eii) in eii_linkage.iter().enumerate() {
let mut explicit_impls =
eii.impls.iter().enumerate().filter(|(_, imp)| !imp.is_default);
let Some((explicit_index, explicit_impl)) = explicit_impls.next() else {
continue;
};
// If the explicit impl is already coming from a dylib, that dylib
// has already resolved the default-vs-explicit choice.
if explicit_impls.next().is_some()
|| matches!(
dependency_formats.get(explicit_impl.impl_crate),
Some(Linkage::Dynamic | Linkage::IncludedFromDylib)
)
{
continue;
}

let mut finalized_default_impls = eii.impls.iter().enumerate().filter(|(_, imp)| {
imp.is_default
&& matches!(
dependency_formats.get(imp.impl_crate),
Some(Linkage::Dynamic | Linkage::IncludedFromDylib)
)
});
let Some((default_index, default_impl)) = finalized_default_impls.next() else {
continue;
};

if !emitted.insert((eii_index, explicit_index, default_index)) {
continue;
}

let additional_crate_names = finalized_default_impls
.map(|(_, imp)| format!("`{}`", eii_impl_crate_name(crate_info, imp.impl_crate)))
.collect::<Vec<_>>();

sess.dcx().emit_err(DuplicateEiiImpls {
name: eii.name,
first_span: explicit_impl.span,
first_crate: eii_impl_crate_name(crate_info, explicit_impl.impl_crate),
second_span: default_impl.span,
second_crate: eii_impl_crate_name(crate_info, default_impl.impl_crate),
help: (),
additional_crates: (!additional_crate_names.is_empty()).then_some(()),
num_additional_crates: additional_crate_names.len(),
additional_crate_names: additional_crate_names.join(", "),
});
}
}
}

/// Performs the linkage portion of the compilation phase. This will generate all
/// of the requested outputs for this compilation session.
pub fn link_binary(
Expand All @@ -84,6 +152,7 @@ pub fn link_binary(
let _timer = sess.timer("link_binary");
let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata);
let mut tempfiles_for_stdout_output: Vec<PathBuf> = Vec::new();
let mut checked_eii_linkage = false;
for &crate_type in &crate_info.crate_types {
// Ignore executable crates if we have -Z no-codegen, as they will error.
if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen())
Expand All @@ -104,6 +173,14 @@ pub fn link_binary(
});

if outputs.outputs.should_link() {
if !checked_eii_linkage {
sess.time("check_externally_implementable_item_linkage", || {
check_externally_implementable_item_linkage(sess, &crate_info);
});
sess.dcx().abort_if_errors();
checked_eii_linkage = true;
}

let output = out_filename(sess, crate_type, outputs, crate_info.local_crate_name);
let tmpdir = TempDirBuilder::new()
.prefix("rustc")
Expand Down
80 changes: 73 additions & 7 deletions compiler/rustc_codegen_ssa/src/base.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
use std::cmp;
use std::collections::BTreeSet;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::{cmp, iter};

use itertools::Itertools;
use rustc_abi::FIRST_VARIANT;
use rustc_ast::expand::allocator::{
ALLOC_ERROR_HANDLER, ALLOCATOR_METHODS, AllocatorKind, AllocatorMethod, AllocatorMethodInput,
AllocatorTy,
};
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
use rustc_data_structures::profiling::{get_resident_set_size, print_time_passes_entry};
use rustc_data_structures::sync::{IntoDynSyncSend, par_map};
use rustc_data_structures::unord::UnordMap;
use rustc_hir::attrs::{DebuggerVisualizerType, OptimizeAttr};
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::attrs::{DebuggerVisualizerType, EiiDecl, EiiImpl, OptimizeAttr};
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
use rustc_hir::lang_items::LangItem;
use rustc_hir::{ItemId, Target, find_attr};
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
use rustc_middle::middle::dependency_format::Dependencies;
use rustc_middle::middle::dependency_format::{Dependencies, Linkage};
use rustc_middle::middle::exported_symbols::{self, SymbolExportKind};
use rustc_middle::middle::lang_items;
use rustc_middle::mir::BinOp;
Expand Down Expand Up @@ -50,7 +50,8 @@ use crate::mir::operand::OperandValue;
use crate::mir::place::PlaceRef;
use crate::traits::*;
use crate::{
CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, ModuleCodegen, errors, meth, mir,
CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, EiiLinkageImplInfo, EiiLinkageInfo,
ModuleCodegen, errors, meth, mir,
};

pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate {
Expand Down Expand Up @@ -897,6 +898,63 @@ pub fn is_call_from_compiler_builtins_to_upstream_monomorphization<'tcx>(
&& !tcx.should_codegen_locally(instance)
}

fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec<EiiLinkageInfo> {
#[derive(Debug)]
struct FoundImpl {
imp: EiiImpl,
impl_crate: CrateNum,
}

#[derive(Debug)]
struct FoundEii {
decl: EiiDecl,
impls: FxIndexMap<DefId, FoundImpl>,
}

let mut eiis = FxIndexMap::<DefId, FoundEii>::default();

for &cnum in tcx.crates(()).iter().chain(iter::once(&LOCAL_CRATE)) {
for (did, (decl, impls)) in tcx.externally_implementable_items(cnum) {
eiis.entry(*did)
.or_insert_with(|| FoundEii { decl: *decl, impls: Default::default() })
.impls
.extend(
impls
.into_iter()
.map(|(did, imp)| (*did, FoundImpl { imp: *imp, impl_crate: cnum })),
);
}
}

eiis.into_iter()
.filter_map(|(_, FoundEii { decl, impls })| {
let explicit_impl_count = impls.values().filter(|imp| !imp.imp.is_default).count();
let has_default_impl = impls.values().any(|imp| imp.imp.is_default);
// Only this case needs the link-time check. Missing impls and
// duplicate explicit impls are handled in `rustc_passes`.
(explicit_impl_count == 1 && has_default_impl).then(|| EiiLinkageInfo {
name: decl.name.name,
impls: impls
.into_iter()
.map(|(impl_did, FoundImpl { imp, impl_crate })| EiiLinkageImplInfo {
span: tcx.def_span(impl_did),
impl_crate,
is_default: imp.is_default,
})
.collect(),
})
})
.collect()
}

fn eii_linkage_needed(dependency_formats: &Dependencies) -> bool {
dependency_formats.values().any(|formats| {
formats
.iter()
.any(|&linkage| matches!(linkage, Linkage::Dynamic | Linkage::IncludedFromDylib))
})
}

impl CrateInfo {
pub fn new(tcx: TyCtxt<'_>, target_cpu: String) -> CrateInfo {
let crate_types = tcx.crate_types().to_vec();
Expand All @@ -908,6 +966,13 @@ impl CrateInfo {
crate_types.iter().map(|&c| (c, crate::back::linker::linked_symbols(tcx, c))).collect();
let local_crate_name = tcx.crate_name(LOCAL_CRATE);
let windows_subsystem = find_attr!(tcx, crate, WindowsSubsystem(kind) => *kind);
let dependency_formats = Arc::clone(tcx.dependency_formats(()));
let eii_linkage = if eii_linkage_needed(&dependency_formats) {
let eii_linkage = collect_eii_linkage(tcx);
(!eii_linkage.is_empty()).then_some(eii_linkage)
} else {
None
};

// This list is used when generating the command line to pass through to
// system linker. The linker expects undefined symbols on the left of the
Expand Down Expand Up @@ -952,7 +1017,8 @@ impl CrateInfo {
crate_name: UnordMap::with_capacity(n_crates),
used_crates,
used_crate_source: UnordMap::with_capacity(n_crates),
dependency_formats: Arc::clone(tcx.dependency_formats(())),
dependency_formats,
eii_linkage,
windows_subsystem,
natvis_debugger_visualizers: Default::default(),
lint_level_specs: CodegenLintLevelSpecs::from_tcx(tcx),
Expand Down
18 changes: 17 additions & 1 deletion compiler/rustc_codegen_ssa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use rustc_session::Session;
use rustc_session::config::{CrateType, OutputFilenames, OutputType};
use rustc_session::cstore::{self, CrateSource};
use rustc_session::lint::builtin::LINKER_MESSAGES;
use rustc_span::Symbol;
use rustc_span::{Span, Symbol};

pub mod assert_module_sources;
pub mod back;
Expand Down Expand Up @@ -195,6 +195,19 @@ impl From<&cstore::NativeLib> for NativeLib {
}
}

#[derive(Clone, Debug, Encodable, Decodable)]
pub struct EiiLinkageImplInfo {
pub span: Span,
pub impl_crate: CrateNum,
pub is_default: bool,
}

#[derive(Clone, Debug, Encodable, Decodable)]
pub struct EiiLinkageInfo {
pub name: Symbol,
pub impls: Vec<EiiLinkageImplInfo>,
}

/// Misc info we load from metadata to persist beyond the tcx.
///
/// Note: though `CrateNum` is only meaningful within the same tcx, information within `CrateInfo`
Expand All @@ -221,6 +234,9 @@ pub struct CrateInfo {
pub used_crate_source: UnordMap<CrateNum, Arc<CrateSource>>,
pub used_crates: Vec<CrateNum>,
pub dependency_formats: Arc<Dependencies>,
/// EII implementations used by the link-time duplicate check, so `-Zno-link` can serialize the data needed by a
/// later `-Zlink-only` invocation.
pub eii_linkage: Option<Vec<EiiLinkageInfo>>,
pub windows_subsystem: Option<WindowsSubsystemKind>,
pub natvis_debugger_visualizers: BTreeSet<DebuggerVisualizerFile>,
pub lint_level_specs: CodegenLintLevelSpecs,
Expand Down
29 changes: 29 additions & 0 deletions compiler/rustc_middle/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,32 @@ pub(crate) struct IncrementCompilation {
pub run_cmd: String,
pub dep_node: String,
}

#[derive(Diagnostic)]
#[diag("multiple implementations of `#[{$name}]`")]
pub struct DuplicateEiiImpls {
pub name: Symbol,

#[primary_span]
#[label("first implemented here in crate `{$first_crate}`")]
pub first_span: Span,
pub first_crate: Symbol,

#[label("also implemented here in crate `{$second_crate}`")]
pub second_span: Span,
pub second_crate: Symbol,

#[note("in addition to these two, { $num_additional_crates ->
[one] another implementation was found in crate {$additional_crate_names}
*[other] more implementations were also found in the following crates: {$additional_crate_names}
}")]
pub additional_crates: Option<()>,

pub num_additional_crates: usize,
pub additional_crate_names: String,

#[help(
"an \"externally implementable item\" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict"
)]
pub help: (),
}
3 changes: 2 additions & 1 deletion compiler/rustc_passes/src/eii.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ use std::iter;
use rustc_data_structures::fx::FxIndexMap;
use rustc_hir::attrs::{EiiDecl, EiiImpl};
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
use rustc_middle::error::DuplicateEiiImpls;
use rustc_middle::ty::TyCtxt;
use rustc_session::config::CrateType;

use crate::errors::{DuplicateEiiImpls, EiiWithoutImpl};
use crate::errors::EiiWithoutImpl;

#[derive(Clone, Copy, Debug)]
enum CheckingMode {
Expand Down
29 changes: 0 additions & 29 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1224,35 +1224,6 @@ pub(crate) struct EiiWithoutImpl {
pub help: (),
}

#[derive(Diagnostic)]
#[diag("multiple implementations of `#[{$name}]`")]
pub(crate) struct DuplicateEiiImpls {
pub name: Symbol,

#[primary_span]
#[label("first implemented here in crate `{$first_crate}`")]
pub first_span: Span,
pub first_crate: Symbol,

#[label("also implemented here in crate `{$second_crate}`")]
pub second_span: Span,
pub second_crate: Symbol,

#[note("in addition to these two, { $num_additional_crates ->
[one] another implementation was found in crate {$additional_crate_names}
*[other] more implementations were also found in the following crates: {$additional_crate_names}
}")]
pub additional_crates: Option<()>,

pub num_additional_crates: usize,
pub additional_crate_names: String,

#[help(
"an \"externally implementable item\" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict"
)]
pub help: (),
}

#[derive(Diagnostic)]
#[diag("function doesn't have a default implementation")]
pub(crate) struct FunctionNotHaveDefaultImplementation {
Expand Down
5 changes: 5 additions & 0 deletions tests/ui/eii/duplicate/auxiliary/dylib_default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#![crate_type = "dylib"]
#![feature(extern_item_impls)]

#[eii(eii1)]
fn decl1(x: u64) {}
20 changes: 20 additions & 0 deletions tests/ui/eii/duplicate/dylib_default_duplicate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//@ aux-build: dylib_default.rs
//@ needs-crate-type: dylib
//@ compile-flags: --emit link
//@ ignore-backends: gcc
// FIXME: linking on windows (specifically mingw) not yet supported, see tracking issue #125418
//@ ignore-windows
// Regression test for https://github.com/rust-lang/rust/issues/156320.
// A default implementation from an upstream dylib has already been selected and
// must not be overridden by a downstream explicit implementation.
#![feature(extern_item_impls)]

extern crate dylib_default;

#[unsafe(dylib_default::eii1)]
fn other(x: u64) {
//~^ ERROR multiple implementations of `#[eii1]`
println!("1{x}");
}

fn main() {}
15 changes: 15 additions & 0 deletions tests/ui/eii/duplicate/dylib_default_duplicate.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: multiple implementations of `#[eii1]`
--> $DIR/dylib_default_duplicate.rs:15:1
|
LL | fn other(x: u64) {
| ^^^^^^^^^^^^^^^^ first implemented here in crate `dylib_default_duplicate`
|
::: $DIR/auxiliary/dylib_default.rs:5:1
|
LL | fn decl1(x: u64) {}
| ---------------- also implemented here in crate `dylib_default`
|
= help: an "externally implementable item" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict

error: aborting due to 1 previous error

Loading