#![deny(clippy::arithmetic_side_effects)]
use std::fmt::Write as _;
use hashbrown::HashMap;
use rayon::prelude::*;
use crate::args::LinkOptions;
use crate::diag::{Diagnostic, DiagnosticSink};
use crate::ids::{FileId, SymbolId};
use crate::symbols::{DefinitionKind, Resolution, ResolveFile, SymbolTable, SymbolUse};
use super::inputs::{ElfInput, InputRole};
fn live_inputs<'f, 'a, F: crate::elf::read::ElfFormat>(
files: &'f [ElfInput<'a, F>],
resolution: &Resolution<'_>,
) -> impl Iterator<Item = (usize, &'f ElfInput<'a, F>)> {
files.iter().enumerate().filter(|(index, file)| {
file.role != InputRole::Internal && resolution.is_live(FileId::new(*index))
})
}
pub fn trace_symbols<F: crate::elf::read::ElfFormat>(
files: &[ElfInput<'_, F>],
resolution: &Resolution<'_>,
options: &LinkOptions,
diagnostics: &dyn DiagnosticSink,
) {
if options.trace_symbols.is_empty() {
return;
}
for (_, file) in live_inputs(files, resolution) {
let names = file.symbol_names();
for (local, name) in names.iter().enumerate() {
if name.version().is_some()
|| !options
.trace_symbols
.iter()
.any(|t| t.as_bytes() == name.bytes())
{
continue;
}
let what = match file.symbol_use(local) {
SymbolUse::Reference { .. } => "reference to",
SymbolUse::Definition { .. } => "definition of",
_ => continue,
};
diagnostics.emit(
Diagnostic::new(
crate::diag::Severity::Note,
format!("{}: {what} {}", file.display(), name.display()),
)
.order(file.position.raw()),
);
}
}
}
#[derive(Clone, Copy, Debug)]
enum Held {
Common { size: u64, file: usize },
Defined { file: usize },
}
pub fn warn_common<F: crate::elf::read::ElfFormat>(
files: &[ElfInput<'_, F>],
resolution: &Resolution<'_>,
options: &LinkOptions,
diagnostics: &dyn DiagnosticSink,
) {
if !options.warn_common {
return;
}
let mut commons: Vec<SymbolId> = files
.par_iter()
.enumerate()
.filter(|(index, file)| file.object.is_some() && resolution.is_live(FileId::new(*index)))
.flat_map_iter(|(index, file)| {
let ids = resolution.symbol_ids(FileId::new(index));
(0..ids.len())
.filter(|&local| {
matches!(
file.symbol_use(local),
SymbolUse::Definition {
kind: DefinitionKind::Common,
..
}
)
})
.filter_map(|local| ids.get(local).copied())
.collect::<Vec<_>>()
})
.collect();
if commons.is_empty() {
return;
}
commons.par_sort_unstable();
commons.dedup();
let mut held: HashMap<SymbolId, Held, foldhash::fast::FixedState> =
HashMap::with_hasher(foldhash::fast::FixedState::with_seed(0x636f_6d6d));
for (index, file) in live_inputs(files, resolution) {
let Some(object) = &file.object else {
continue;
};
let ids = resolution.symbol_ids(FileId::new(index));
for (local, &id) in ids.iter().enumerate() {
if commons.binary_search(&id).is_err() {
continue;
}
let SymbolUse::Definition { kind, aux } = file.symbol_use(local) else {
continue;
};
let new = match kind {
DefinitionKind::Common => Held::Common {
size: aux & !super::resolve::AUX_COMDAT,
file: index,
},
DefinitionKind::Regular | DefinitionKind::Weak => Held::Defined { file: index },
_ => continue,
};
let name = object.names.get(local).map_or_else(String::new, |n| {
String::from_utf8_lossy(n.bytes()).into_owned()
});
let display = |file: usize| files.get(file).map_or_else(String::new, ElfInput::display);
let (message, keep_new) = match (held.get(&id).copied(), new) {
(None, _) => (None, true),
(Some(Held::Defined { .. }), Held::Defined { .. }) => (None, false),
(Some(Held::Common { file: old, .. }), Held::Defined { file: this }) => (
Some(format!(
"{}: definition of `{name}' overriding common from {}",
display(this),
display(old)
)),
true,
),
(Some(Held::Defined { file: old }), Held::Common { file: this, .. }) => (
Some(format!(
"{}: common of `{name}' overridden by definition from {}",
display(this),
display(old)
)),
false,
),
(
Some(Held::Common {
size: old_size,
file: old,
}),
Held::Common { size, file: this },
) => {
if old_size > size {
(
Some(format!(
"{}: common of `{name}' overridden by larger common from {}",
display(this),
display(old)
)),
false,
)
} else if size > old_size {
(
Some(format!(
"{}: common of `{name}' overriding smaller common from {}",
display(this),
display(old)
)),
true,
)
} else {
(
Some(format!(
"{} and {}: multiple common of `{name}'",
display(this),
display(old)
)),
false,
)
}
}
};
if let Some(message) = message {
diagnostics.emit(Diagnostic::warning(message).order(file.position.raw()));
}
if keep_new {
held.insert(id, new);
}
}
}
}
const CREF_COLUMN: usize = 50;
#[must_use]
pub fn cross_reference<F: crate::elf::read::ElfFormat>(
files: &[ElfInput<'_, F>],
symbols: &SymbolTable<'_>,
resolution: &Resolution<'_>,
options: &LinkOptions,
) -> Option<String> {
if !options.cref {
return None;
}
let mut mentions: Vec<(SymbolId, usize, bool)> = live_inputs(files, resolution)
.collect::<Vec<_>>()
.into_par_iter()
.flat_map_iter(|(index, file)| {
let ids = resolution.symbol_ids(FileId::new(index));
let regular = file.object.is_some();
(0..ids.len())
.filter(|&local| {
matches!(
file.symbol_use(local),
SymbolUse::Reference { .. } | SymbolUse::Definition { .. }
)
})
.filter_map(|local| ids.get(local).map(|&id| (id, index, regular)))
.collect::<Vec<_>>()
})
.collect();
mentions.par_sort_unstable();
mentions.dedup_by_key(|(id, file, _)| (*id, *file));
let mut rows: Vec<(&[u8], SymbolId, Vec<usize>)> = mentions
.chunk_by(|a, b| a.0 == b.0)
.filter(|group| group.iter().any(|&(_, _, regular)| regular))
.filter_map(|group| {
let id = group.first()?.0;
let name = symbols.name(id);
if name.version().is_some() {
return None;
}
let definer = symbols.definition(id);
let defined = !matches!(
definer.kind,
DefinitionKind::Undefined | DefinitionKind::Lazy
);
let mut list: Vec<usize> = Vec::with_capacity(group.len());
if defined && group.iter().any(|&(_, f, _)| f == definer.file.index()) {
list.push(definer.file.index());
}
list.extend(
group
.iter()
.map(|&(_, f, _)| f)
.filter(|&f| !(defined && f == definer.file.index())),
);
Some((name.bytes(), id, list))
})
.collect();
rows.par_sort_unstable_by(|a, b| a.0.cmp(b.0).then(a.1.cmp(&b.1)));
let mut text = String::from("\nCross Reference Table\n\nSymbol");
text.push_str(&" ".repeat(CREF_COLUMN.saturating_sub(6)));
text.push_str("File\n");
for (name, _, list) in rows {
let name = String::from_utf8_lossy(name);
for (position, file) in list.iter().enumerate() {
let display = files.get(*file).map_or_else(String::new, ElfInput::display);
if position == 0 {
text.push_str(&name);
if name.len() >= CREF_COLUMN {
text.push('\n');
text.push_str(&" ".repeat(CREF_COLUMN));
} else {
text.push_str(&" ".repeat(CREF_COLUMN.saturating_sub(name.len())));
}
} else {
text.push_str(&" ".repeat(CREF_COLUMN));
}
let _ = writeln!(text, "{display}");
}
}
Some(text)
}