Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect VTable stats & add -Zprint-vtable-sizes #112400

Merged
merged 5 commits into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions compiler/rustc_driver_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,13 @@ fn run_compiler(
sess.code_stats.print_type_sizes();
}

if sess.opts.unstable_opts.print_vtable_sizes {
let crate_name =
compiler.session().opts.crate_name.as_deref().unwrap_or("<UNKNOWN_CRATE>");

sess.code_stats.print_vtable_sizes(crate_name);
}

let linker = queries.linker()?;
Ok(Some(linker))
})?;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_interface/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Se
};

let prof = compiler.sess.prof.clone();

prof.generic_activity("drop_compiler").run(move || drop(compiler));
r
})
Expand Down
94 changes: 94 additions & 0 deletions compiler/rustc_interface/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use rustc_parse::{parse_crate_from_file, parse_crate_from_source_str, validate_a
use rustc_passes::{self, hir_stats, layout_test};
use rustc_plugin_impl as plugin;
use rustc_resolve::Resolver;
use rustc_session::code_stats::VTableSizeInfo;
use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType};
use rustc_session::cstore::{MetadataLoader, Untracked};
use rustc_session::output::filename_for_input;
Expand Down Expand Up @@ -866,6 +867,99 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
sess.time("check_lint_expectations", || tcx.check_expectations(None));
});

if sess.opts.unstable_opts.print_vtable_sizes {
let traits = tcx.traits(LOCAL_CRATE);

for &tr in traits {
WaffleLapkin marked this conversation as resolved.
Show resolved Hide resolved
if !tcx.check_is_object_safe(tr) {
continue;
}

let name = ty::print::with_no_trimmed_paths!(tcx.def_path_str(tr));

let mut first_dsa = true;

// Number of vtable entries, if we didn't have upcasting
let mut unupcasted_cost = 0;
// Number of vtable entries needed solely for upcasting
let mut upcast_cost = 0;

let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(tcx, tr));

// A slightly edited version of the code in `rustc_trait_selection::traits::vtable::vtable_entries`,
// that works without self type and just counts number of entries.
//
// Note that this is technically wrong, for traits which have associated types in supertraits:
//
// trait A: AsRef<Self::T> + AsRef<()> { type T; }
//
// Without self type we can't normalize `Self::T`, so we can't know if `AsRef<Self::T>` and
// `AsRef<()>` are the same trait, thus we assume that those are different, and potentially
// over-estimate how many vtable entries there are.
//
// Similarly this is wrong for traits that have methods with possibly-impossible bounds.
// For example:
//
// trait B<T> { fn f(&self) where T: Copy; }
//
// Here `dyn B<u8>` will have 4 entries, while `dyn B<String>` will only have 3.
// However, since we don't know `T`, we can't know if `T: Copy` holds or not,
// thus we lean on the bigger side and say it has 4 entries.
traits::vtable::prepare_vtable_segments(tcx, trait_ref, |segment| {
match segment {
traits::vtable::VtblSegment::MetadataDSA => {
// If this is the first dsa, it would be included either way,
// otherwise it's needed for upcasting
if std::mem::take(&mut first_dsa) {
unupcasted_cost += 3;
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved
} else {
upcast_cost += 3;
}
}

traits::vtable::VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => {
let existential_trait_ref = trait_ref.map_bound(|trait_ref| {
ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
});

// Lookup the shape of vtable for the trait.
let own_existential_entries =
tcx.own_existential_vtable_entries(existential_trait_ref.def_id());
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
let existential_trait_ref = trait_ref.map_bound(|trait_ref| {
ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)
});
// Lookup the shape of vtable for the trait.
let own_existential_entries =
tcx.own_existential_vtable_entries(existential_trait_ref.def_id());
// Lookup the shape of vtable for the trait.
let own_existential_entries =
tcx.own_existential_vtable_entries(trait_ref.def_id());


let own_entries = own_existential_entries.iter().copied().map(|_def_id| {
// The original code here ignores the method if its predicates are impossible.
// We can't really do that as, for example, all not trivial bounds on generic
// parameters are impossible (since we don't know the parameters...),
// see the comment above.

1
});

unupcasted_cost += own_entries.sum::<usize>();
Copy link
Member

Choose a reason for hiding this comment

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

Can you uplift this comment and just use count 😅


if emit_vptr {
upcast_cost += 1;
}
}
}

std::ops::ControlFlow::Continue::<std::convert::Infallible>(())
});

sess.code_stats.record_vtable_size(
tr,
&name,
VTableSizeInfo {
trait_name: name.clone(),
size_words_without_upcasting: unupcasted_cost,
size_words_with_upcasting: unupcasted_cost + upcast_cost,
difference_words: upcast_cost,
difference_percent: upcast_cost as f64 / unupcasted_cost as f64 * 100.,
Copy link
Member

Choose a reason for hiding this comment

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

why isnt this (size_words_without_upcasting + size_words_with_upcasting) / size_words_without_upcasting... not sure if i totally understand the math here, that could use some explanation i think 😅

Copy link
Member

Choose a reason for hiding this comment

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

nvm, I think I get it. This creates a "how much % larger is the new vtable compared to the old table". could use doc tho

},
)
}
}

Ok(())
}

Expand Down
44 changes: 43 additions & 1 deletion compiler/rustc_session/src/code_stats.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::sync::Lock;
use rustc_span::def_id::DefId;
use rustc_span::Symbol;
use rustc_target::abi::{Align, Size};
use std::cmp;
Expand Down Expand Up @@ -65,9 +66,18 @@ pub struct TypeSizeInfo {
pub variants: Vec<VariantInfo>,
}

pub struct VTableSizeInfo {
pub trait_name: String,
pub size_words_without_upcasting: usize,
pub size_words_with_upcasting: usize,
pub difference_words: usize,
pub difference_percent: f64,
}

#[derive(Default)]
pub struct CodeStats {
type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
vtable_sizes: Lock<FxHashMap<DefId, VTableSizeInfo>>,
}

impl CodeStats {
Expand Down Expand Up @@ -101,6 +111,14 @@ impl CodeStats {
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();
let mut sorted: Vec<_> = type_sizes.iter().collect();
Expand Down Expand Up @@ -196,4 +214,28 @@ impl CodeStats {
}
}
}

pub fn print_vtable_sizes(&self, crate_name: &str) {
let mut rr = std::mem::take(&mut *self.vtable_sizes.lock()).into_iter().collect::<Vec<_>>();

rr.sort_by(|(_, stats_a), (_, stats_b)| {
stats_b.difference_percent.total_cmp(&stats_a.difference_percent)
});

for (
_,
VTableSizeInfo {
trait_name,
size_words_without_upcasting,
size_words_with_upcasting,
difference_words,
difference_percent,
},
) in rr
{
println!(
r#"print-vtable-sizes {{ "crate_name": "{crate_name}", "trait_name": "{trait_name}", "size_unupcastable_words": "{size_words_without_upcasting}", "size_upcastable_words": "{size_words_with_upcasting}", diff: "{difference_words}", diff_p: "{difference_percent}" }}"#
);
}
}
}
2 changes: 1 addition & 1 deletion compiler/rustc_session/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub use lint::{declare_lint, declare_lint_pass, declare_tool_lint, impl_lint_pas
pub use rustc_lint_defs as lint;
pub mod parse;

mod code_stats;
pub mod code_stats;
#[macro_use]
pub mod config;
pub mod cstore;
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1632,6 +1632,8 @@ options! {
"print the result of the monomorphization collection pass"),
print_type_sizes: bool = (false, parse_bool, [UNTRACKED],
"print layout information for each type encountered (default: no)"),
print_vtable_sizes: bool = (false, parse_bool, [UNTRACKED],
"print size comparison between old and new vtable layouts (default: no)"),
proc_macro_backtrace: bool = (false, parse_bool, [UNTRACKED],
"show backtraces for panics during proc-macro execution (default: no)"),
proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mod structural_match;
mod structural_normalize;
#[cfg_attr(not(bootstrap), allow(hidden_glob_reexports))]
mod util;
mod vtable;
pub mod vtable;
pub mod wf;

use crate::infer::outlives::env::OutlivesEnvironment;
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_trait_selection/src/traits/vtable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ use std::fmt::Debug;
use std::ops::ControlFlow;

#[derive(Clone, Debug)]
pub(super) enum VtblSegment<'tcx> {
pub enum VtblSegment<'tcx> {
MetadataDSA,
TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool },
}

/// Prepare the segments for a vtable
pub(super) fn prepare_vtable_segments<'tcx, T>(
pub fn prepare_vtable_segments<'tcx, T>(
tcx: TyCtxt<'tcx>,
trait_ref: ty::PolyTraitRef<'tcx>,
mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow<T>,
Expand Down