blob: f3c21992784c0cfcff8d2b93b508cbb12510ab56 [file] [log] [blame]
use std::cmp;
use rustc_abi::{Align, Size};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::sync::Lock;
use rustc_span::Symbol;
use rustc_span::def_id::DefId;
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct VariantInfo {
pub name: Option<Symbol>,
pub kind: SizeKind,
pub size: u64,
pub align: u64,
pub fields: Vec<FieldInfo>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum SizeKind {
Exact,
Min,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum FieldKind {
AdtField,
Upvar,
CoroutineLocal,
}
impl std::fmt::Display for FieldKind {
fn fmt(&self, w: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FieldKind::AdtField => write!(w, "field"),
FieldKind::Upvar => write!(w, "upvar"),
FieldKind::CoroutineLocal => write!(w, "local"),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct FieldInfo {
pub kind: FieldKind,
pub name: Symbol,
pub offset: u64,
pub size: u64,
pub align: u64,
/// Name of the type of this field.
/// Present only if the creator thought that this would be important for identifying the field,
/// typically because the field name is uninformative.
pub type_name: Option<Symbol>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum DataTypeKind {
Struct,
Union,
Enum,
Closure,
Coroutine,
}
#[derive(PartialEq, Eq, Hash, Debug)]
pub struct TypeSizeInfo {
pub kind: DataTypeKind,
pub type_description: String,
pub align: u64,
pub overall_size: u64,
pub packed: bool,
pub opt_discr_size: Option<u64>,
pub variants: Vec<VariantInfo>,
}
pub struct VTableSizeInfo {
pub trait_name: String,
/// Number of entries in a vtable with the current algorithm
/// (i.e. with upcasting).
pub entries: usize,
/// Number of entries in a vtable, as-if we did not have trait upcasting.
pub entries_ignoring_upcasting: usize,
/// Number of entries in a vtable needed solely for upcasting
/// (i.e. `entries - entries_ignoring_upcasting`).
pub entries_for_upcasting: usize,
/// Cost of having upcasting in % relative to the number of entries without
/// upcasting (i.e. `entries_for_upcasting / entries_ignoring_upcasting * 100%`).
pub upcasting_cost_percent: f64,
}
#[derive(Default)]
pub struct CodeStats {
type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
vtable_sizes: Lock<FxHashMap<DefId, VTableSizeInfo>>,
}
impl CodeStats {
pub fn record_type_size<S: ToString>(
&self,
kind: DataTypeKind,
type_desc: S,
align: Align,
overall_size: Size,
packed: bool,
opt_discr_size: Option<Size>,
mut variants: Vec<VariantInfo>,
) {
// Sort variants so the largest ones are shown first. A stable sort is
// used here so that source code order is preserved for all variants
// that have the same size.
// Except for Coroutines, whose variants are already sorted according to
// their yield points in `variant_info_for_coroutine`.
if kind != DataTypeKind::Coroutine {
variants.sort_by_key(|info| cmp::Reverse(info.size));
}
let info = TypeSizeInfo {
kind,
type_description: type_desc.to_string(),
align: align.bytes(),
overall_size: overall_size.bytes(),
packed,
opt_discr_size: opt_discr_size.map(|s| s.bytes()),
variants,
};
self.type_sizes.borrow_mut().insert(info);
}
pub fn record_vtable_size(&self, trait_did: DefId, trait_name: &str, info: VTableSizeInfo) {
let prev = self.vtable_sizes.lock().insert(trait_did, info);
assert!(
prev.is_none(),
"size of vtable for `{trait_name}` ({trait_did:?}) is already recorded"
);
}
pub fn print_type_sizes(&self) {
let type_sizes = self.type_sizes.borrow();
// We will soon sort, so the initial order does not matter.
#[allow(rustc::potential_query_instability)]
let mut sorted: Vec<_> = type_sizes.iter().collect();
// Primary sort: large-to-small.
// Secondary sort: description (dictionary order)
sorted.sort_by_key(|info| (cmp::Reverse(info.overall_size), &info.type_description));
for info in sorted {
let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info;
println!(
"print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes"
);
let indent = " ";
let discr_size = if let Some(discr_size) = info.opt_discr_size {
println!("print-type-size {indent}discriminant: {discr_size} bytes");
discr_size
} else {
0
};
// We start this at discr_size (rather than 0) because
// things like C-enums do not have variants but we still
// want the max_variant_size at the end of the loop below
// to reflect the presence of the discriminant.
let mut max_variant_size = discr_size;
let struct_like = match kind {
DataTypeKind::Struct | DataTypeKind::Closure => true,
DataTypeKind::Enum | DataTypeKind::Union | DataTypeKind::Coroutine => false,
};
for (i, variant_info) in variants.into_iter().enumerate() {
let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info;
let indent = if !struct_like {
let name = match name.as_ref() {
Some(name) => name.to_string(),
None => i.to_string(),
};
println!(
"print-type-size {indent}variant `{name}`: {diff} bytes",
diff = size - discr_size
);
" "
} else {
assert!(i < 1);
" "
};
max_variant_size = cmp::max(max_variant_size, size);
let mut min_offset = discr_size;
// We want to print fields by increasing offset. We also want
// zero-sized fields before non-zero-sized fields, otherwise
// the loop below goes wrong; hence the `f.size` in the sort
// key.
let mut fields = fields.clone();
fields.sort_by_key(|f| (f.offset, f.size));
for field in fields {
let FieldInfo { kind, ref name, offset, size, align, type_name } = field;
if offset > min_offset {
let pad = offset - min_offset;
println!("print-type-size {indent}padding: {pad} bytes");
}
if offset < min_offset {
// If this happens it's probably a union.
print!(
"print-type-size {indent}{kind} `.{name}`: {size} bytes, \
offset: {offset} bytes, \
alignment: {align} bytes"
);
} else if info.packed || offset == min_offset {
print!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
} else {
// Include field alignment in output only if it caused padding injection
print!(
"print-type-size {indent}{kind} `.{name}`: {size} bytes, \
alignment: {align} bytes"
);
}
if let Some(type_name) = type_name {
println!(", type: {type_name}");
} else {
println!();
}
min_offset = offset + size;
}
}
match overall_size.checked_sub(max_variant_size) {
None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"),
Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"),
Some(0) => {}
}
}
}
pub fn print_vtable_sizes(&self, crate_name: Symbol) {
// We will soon sort, so the initial order does not matter.
#[allow(rustc::potential_query_instability)]
let mut infos =
std::mem::take(&mut *self.vtable_sizes.lock()).into_values().collect::<Vec<_>>();
// Primary sort: cost % in reverse order (from largest to smallest)
// Secondary sort: trait_name
infos.sort_by(|a, b| {
a.upcasting_cost_percent
.total_cmp(&b.upcasting_cost_percent)
.reverse()
.then_with(|| a.trait_name.cmp(&b.trait_name))
});
for VTableSizeInfo {
trait_name,
entries,
entries_ignoring_upcasting,
entries_for_upcasting,
upcasting_cost_percent,
} in infos
{
println!(
r#"print-vtable-sizes {{ "crate_name": "{crate_name}", "trait_name": "{trait_name}", "entries": "{entries}", "entries_ignoring_upcasting": "{entries_ignoring_upcasting}", "entries_for_upcasting": "{entries_for_upcasting}", "upcasting_cost_percent": "{upcasting_cost_percent}" }}"#
);
}
}
}