1
Fork 0

Auto merge of #96285 - flip1995:pk-vfe, r=nagisa

Introduce `-Zvirtual-function-elimination` codegen flag

Fixes #68262

This PR adds a codegen flag `-Zvirtual-function-elimination` to enable the VFE optimization in LLVM. To make this work, additonal  information has to be added to vtables ([`!vcall_visibility` metadata](https://llvm.org/docs/TypeMetadata.html#vcall-visibility-metadata) and a `typeid` of the trait). Furthermore, instead of just `load`ing functions, the [`llvm.type.checked.load` intrinsic](https://llvm.org/docs/LangRef.html#llvm-type-checked-load-intrinsic) has to be used to map functions to vtables.

For technical details of the changes, see the commit messages.

I also tested this flag on https://github.com/tock/tock on different boards to verify that this fixes the issue https://github.com/tock/tock/issues/2594. This flag is able to improve the size of the resulting binary by about 8k-9k bytes by removing the unused debug print functions.

[Rendered documentation update](https://github.com/flip1995/rust/blob/pk-vfe/src/doc/rustc/src/codegen-options/index.md#virtual-function-elimination)
This commit is contained in:
bors 2022-06-14 21:37:11 +00:00
commit 2d1e075079
20 changed files with 431 additions and 32 deletions

View file

@ -3695,6 +3695,7 @@ dependencies = [
"rustc_serialize", "rustc_serialize",
"rustc_session", "rustc_session",
"rustc_span", "rustc_span",
"rustc_symbol_mangling",
"rustc_target", "rustc_target",
"smallvec", "smallvec",
"tracing", "tracing",

View file

@ -356,6 +356,16 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'gcc, 'tcx> {
self.context.new_rvalue_from_int(self.int_type, 0) self.context.new_rvalue_from_int(self.int_type, 0)
} }
fn type_checked_load(
&mut self,
_llvtable: Self::Value,
_vtable_byte_offset: u64,
_typeid: Self::Value,
) -> Self::Value {
// Unsupported.
self.context.new_rvalue_from_int(self.int_type, 0)
}
fn va_start(&mut self, _va_list: RValue<'gcc>) -> RValue<'gcc> { fn va_start(&mut self, _va_list: RValue<'gcc>) -> RValue<'gcc> {
unimplemented!(); unimplemented!();
} }

View file

@ -19,6 +19,7 @@ rustc-demangle = "0.1.21"
rustc_arena = { path = "../rustc_arena" } rustc_arena = { path = "../rustc_arena" }
rustc_attr = { path = "../rustc_attr" } rustc_attr = { path = "../rustc_attr" }
rustc_codegen_ssa = { path = "../rustc_codegen_ssa" } rustc_codegen_ssa = { path = "../rustc_codegen_ssa" }
rustc_symbol_mangling = { path = "../rustc_symbol_mangling" }
rustc_data_structures = { path = "../rustc_data_structures" } rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" } rustc_errors = { path = "../rustc_errors" }
rustc_fs_util = { path = "../rustc_fs_util" } rustc_fs_util = { path = "../rustc_fs_util" }

View file

@ -586,9 +586,21 @@ pub(crate) fn run_pass_manager(
// LTO-specific optimization passes that LLVM provides. // LTO-specific optimization passes that LLVM provides.
// //
// This code is based off the code found in llvm's LTO code generator: // This code is based off the code found in llvm's LTO code generator:
// tools/lto/LTOCodeGenerator.cpp // llvm/lib/LTO/LTOCodeGenerator.cpp
debug!("running the pass manager"); debug!("running the pass manager");
unsafe { unsafe {
if !llvm::LLVMRustHasModuleFlag(
module.module_llvm.llmod(),
"LTOPostLink".as_ptr().cast(),
11,
) {
llvm::LLVMRustAddModuleFlag(
module.module_llvm.llmod(),
llvm::LLVMModFlagBehavior::Error,
"LTOPostLink\0".as_ptr().cast(),
1,
);
}
if llvm_util::should_use_new_llvm_pass_manager( if llvm_util::should_use_new_llvm_pass_manager(
&config.new_llvm_pass_manager, &config.new_llvm_pass_manager,
&cgcx.target_arch, &cgcx.target_arch,

View file

@ -326,6 +326,15 @@ pub unsafe fn create_module<'ll>(
) )
} }
if sess.opts.debugging_opts.virtual_function_elimination {
llvm::LLVMRustAddModuleFlag(
llmod,
llvm::LLVMModFlagBehavior::Error,
"Virtual Function Elim\0".as_ptr().cast(),
1,
);
}
llmod llmod
} }
@ -656,6 +665,7 @@ impl<'ll> CodegenCx<'ll, '_> {
let t_isize = self.type_isize(); let t_isize = self.type_isize();
let t_f32 = self.type_f32(); let t_f32 = self.type_f32();
let t_f64 = self.type_f64(); let t_f64 = self.type_f64();
let t_metadata = self.type_metadata();
ifn!("llvm.wasm.trunc.unsigned.i32.f32", fn(t_f32) -> t_i32); ifn!("llvm.wasm.trunc.unsigned.i32.f32", fn(t_f32) -> t_i32);
ifn!("llvm.wasm.trunc.unsigned.i32.f64", fn(t_f64) -> t_i32); ifn!("llvm.wasm.trunc.unsigned.i32.f64", fn(t_f64) -> t_i32);
@ -881,11 +891,12 @@ impl<'ll> CodegenCx<'ll, '_> {
ifn!("llvm.instrprof.increment", fn(i8p, t_i64, t_i32, t_i32) -> void); ifn!("llvm.instrprof.increment", fn(i8p, t_i64, t_i32, t_i32) -> void);
} }
ifn!("llvm.type.test", fn(i8p, self.type_metadata()) -> i1); ifn!("llvm.type.test", fn(i8p, t_metadata) -> i1);
ifn!("llvm.type.checked.load", fn(i8p, t_i32, t_metadata) -> mk_struct! {i8p, i1});
if self.sess().opts.debuginfo != DebugInfo::None { if self.sess().opts.debuginfo != DebugInfo::None {
ifn!("llvm.dbg.declare", fn(self.type_metadata(), self.type_metadata()) -> void); ifn!("llvm.dbg.declare", fn(t_metadata, t_metadata) -> void);
ifn!("llvm.dbg.value", fn(self.type_metadata(), t_i64, self.type_metadata()) -> void); ifn!("llvm.dbg.value", fn(t_metadata, t_i64, t_metadata) -> void);
} }
None None
} }

View file

@ -30,20 +30,21 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_index::vec::{Idx, IndexVec}; use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::bug; use rustc_middle::bug;
use rustc_middle::mir::{self, GeneratorLayout}; use rustc_middle::mir::{self, GeneratorLayout};
use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::subst::GenericArgKind; use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, AdtKind, Instance, ParamEnv, Ty, TyCtxt}; use rustc_middle::ty::{
use rustc_session::config::{self, DebugInfo}; self, AdtKind, Instance, ParamEnv, PolyExistentialTraitRef, Ty, TyCtxt, Visibility,
};
use rustc_session::config::{self, DebugInfo, Lto};
use rustc_span::symbol::Symbol; use rustc_span::symbol::Symbol;
use rustc_span::FileName; use rustc_span::FileName;
use rustc_span::FileNameDisplayPreference; use rustc_span::{self, FileNameDisplayPreference, SourceFile};
use rustc_span::{self, SourceFile}; use rustc_symbol_mangling::typeid_for_trait_ref;
use rustc_target::abi::{Align, Size}; use rustc_target::abi::{Align, Size};
use smallvec::smallvec; use smallvec::smallvec;
use tracing::debug; use tracing::debug;
use libc::{c_longlong, c_uint}; use libc::{c_char, c_longlong, c_uint};
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
@ -1468,6 +1469,84 @@ fn build_vtable_type_di_node<'ll, 'tcx>(
.di_node .di_node
} }
fn vcall_visibility_metadata<'ll, 'tcx>(
cx: &CodegenCx<'ll, 'tcx>,
ty: Ty<'tcx>,
trait_ref: Option<PolyExistentialTraitRef<'tcx>>,
vtable: &'ll Value,
) {
enum VCallVisibility {
Public = 0,
LinkageUnit = 1,
TranslationUnit = 2,
}
let Some(trait_ref) = trait_ref else { return };
let trait_ref_self = trait_ref.with_self_ty(cx.tcx, ty);
let trait_ref_self = cx.tcx.erase_regions(trait_ref_self);
let trait_def_id = trait_ref_self.def_id();
let trait_vis = cx.tcx.visibility(trait_def_id);
let cgus = cx.sess().codegen_units();
let single_cgu = cgus == 1;
let lto = cx.sess().lto();
// Since LLVM requires full LTO for the virtual function elimination optimization to apply,
// only the `Lto::Fat` cases are relevant currently.
let vcall_visibility = match (lto, trait_vis, single_cgu) {
// If there is not LTO and the visibility in public, we have to assume that the vtable can
// be seen from anywhere. With multiple CGUs, the vtable is quasi-public.
(Lto::No | Lto::ThinLocal, Visibility::Public, _)
| (Lto::No, Visibility::Restricted(_) | Visibility::Invisible, false) => {
VCallVisibility::Public
}
// With LTO and a quasi-public visibility, the usages of the functions of the vtable are
// all known by the `LinkageUnit`.
// FIXME: LLVM only supports this optimization for `Lto::Fat` currently. Once it also
// supports `Lto::Thin` the `VCallVisibility` may have to be adjusted for those.
(Lto::Fat | Lto::Thin, Visibility::Public, _)
| (
Lto::ThinLocal | Lto::Thin | Lto::Fat,
Visibility::Restricted(_) | Visibility::Invisible,
false,
) => VCallVisibility::LinkageUnit,
// If there is only one CGU, private vtables can only be seen by that CGU/translation unit
// and therefore we know of all usages of functions in the vtable.
(_, Visibility::Restricted(_) | Visibility::Invisible, true) => {
VCallVisibility::TranslationUnit
}
};
let trait_ref_typeid = typeid_for_trait_ref(cx.tcx, trait_ref);
unsafe {
let typeid = llvm::LLVMMDStringInContext(
cx.llcx,
trait_ref_typeid.as_ptr() as *const c_char,
trait_ref_typeid.as_bytes().len() as c_uint,
);
let v = [cx.const_usize(0), typeid];
llvm::LLVMRustGlobalAddMetadata(
vtable,
llvm::MD_type as c_uint,
llvm::LLVMValueAsMetadata(llvm::LLVMMDNodeInContext(
cx.llcx,
v.as_ptr(),
v.len() as c_uint,
)),
);
let vcall_visibility = llvm::LLVMValueAsMetadata(cx.const_u64(vcall_visibility as u64));
let vcall_visibility_metadata = llvm::LLVMMDNodeInContext2(cx.llcx, &vcall_visibility, 1);
llvm::LLVMGlobalSetMetadata(
vtable,
llvm::MetadataType::MD_vcall_visibility as c_uint,
vcall_visibility_metadata,
);
}
}
/// Creates debug information for the given vtable, which is for the /// Creates debug information for the given vtable, which is for the
/// given type. /// given type.
/// ///
@ -1478,6 +1557,12 @@ pub fn create_vtable_di_node<'ll, 'tcx>(
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>, poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
vtable: &'ll Value, vtable: &'ll Value,
) { ) {
// FIXME(flip1995): The virtual function elimination optimization only works with full LTO in
// LLVM at the moment.
if cx.sess().opts.debugging_opts.virtual_function_elimination && cx.sess().lto() == Lto::Fat {
vcall_visibility_metadata(cx, ty, poly_trait_ref, vtable);
}
if cx.dbg_cx.is_none() { if cx.dbg_cx.is_none() {
return; return;
} }

View file

@ -406,6 +406,16 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> {
self.call_intrinsic("llvm.type.test", &[bitcast, typeid]) self.call_intrinsic("llvm.type.test", &[bitcast, typeid])
} }
fn type_checked_load(
&mut self,
llvtable: &'ll Value,
vtable_byte_offset: u64,
typeid: &'ll Value,
) -> Self::Value {
let vtable_byte_offset = self.const_i32(vtable_byte_offset as i32);
self.call_intrinsic("llvm.type.checked.load", &[llvtable, vtable_byte_offset, typeid])
}
fn va_start(&mut self, va_list: &'ll Value) -> &'ll Value { fn va_start(&mut self, va_list: &'ll Value) -> &'ll Value {
self.call_intrinsic("llvm.va_start", &[va_list]) self.call_intrinsic("llvm.va_start", &[va_list])
} }

View file

@ -442,6 +442,7 @@ pub enum MetadataType {
MD_nonnull = 11, MD_nonnull = 11,
MD_align = 17, MD_align = 17,
MD_type = 19, MD_type = 19,
MD_vcall_visibility = 28,
MD_noundef = 29, MD_noundef = 29,
} }
@ -1067,6 +1068,7 @@ extern "C" {
pub fn LLVMReplaceAllUsesWith<'a>(OldVal: &'a Value, NewVal: &'a Value); pub fn LLVMReplaceAllUsesWith<'a>(OldVal: &'a Value, NewVal: &'a Value);
pub fn LLVMSetMetadata<'a>(Val: &'a Value, KindID: c_uint, Node: &'a Value); pub fn LLVMSetMetadata<'a>(Val: &'a Value, KindID: c_uint, Node: &'a Value);
pub fn LLVMGlobalSetMetadata<'a>(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata); pub fn LLVMGlobalSetMetadata<'a>(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata);
pub fn LLVMRustGlobalAddMetadata<'a>(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata);
pub fn LLVMValueAsMetadata(Node: &Value) -> &Metadata; pub fn LLVMValueAsMetadata(Node: &Value) -> &Metadata;
// Operations on constants of any type // Operations on constants of any type
@ -1080,6 +1082,11 @@ extern "C" {
Vals: *const &'a Value, Vals: *const &'a Value,
Count: c_uint, Count: c_uint,
) -> &'a Value; ) -> &'a Value;
pub fn LLVMMDNodeInContext2<'a>(
C: &'a Context,
Vals: *const &'a Metadata,
Count: size_t,
) -> &'a Metadata;
pub fn LLVMAddNamedMetadataOperand<'a>(M: &'a Module, Name: *const c_char, Val: &'a Value); pub fn LLVMAddNamedMetadataOperand<'a>(M: &'a Module, Name: *const c_char, Val: &'a Value);
// Operations on scalar constants // Operations on scalar constants
@ -1936,6 +1943,7 @@ extern "C" {
name: *const c_char, name: *const c_char,
value: u32, value: u32,
); );
pub fn LLVMRustHasModuleFlag(M: &Module, name: *const c_char, len: size_t) -> bool;
pub fn LLVMRustMetadataAsValue<'a>(C: &'a Context, MD: &'a Metadata) -> &'a Value; pub fn LLVMRustMetadataAsValue<'a>(C: &'a Context, MD: &'a Metadata) -> &'a Value;

View file

@ -1,6 +1,8 @@
use crate::traits::*; use crate::traits::*;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, subst::GenericArgKind, ExistentialPredicate, Ty, TyCtxt};
use rustc_session::config::Lto;
use rustc_symbol_mangling::typeid_for_trait_ref;
use rustc_target::abi::call::FnAbi; use rustc_target::abi::call::FnAbi;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -15,20 +17,32 @@ impl<'a, 'tcx> VirtualIndex {
self, self,
bx: &mut Bx, bx: &mut Bx,
llvtable: Bx::Value, llvtable: Bx::Value,
ty: Ty<'tcx>,
fn_abi: &FnAbi<'tcx, Ty<'tcx>>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
) -> Bx::Value { ) -> Bx::Value {
// Load the data pointer from the object. // Load the data pointer from the object.
debug!("get_fn({:?}, {:?})", llvtable, self); debug!("get_fn({llvtable:?}, {ty:?}, {self:?})");
let llty = bx.fn_ptr_backend_type(fn_abi); let llty = bx.fn_ptr_backend_type(fn_abi);
let llvtable = bx.pointercast(llvtable, bx.type_ptr_to(llty)); let llvtable = bx.pointercast(llvtable, bx.type_ptr_to(llty));
let ptr_align = bx.tcx().data_layout.pointer_align.abi;
let gep = bx.inbounds_gep(llty, llvtable, &[bx.const_usize(self.0)]); if bx.cx().sess().opts.debugging_opts.virtual_function_elimination
let ptr = bx.load(llty, gep, ptr_align); && bx.cx().sess().lto() == Lto::Fat
bx.nonnull_metadata(ptr); {
// Vtable loads are invariant. let typeid =
bx.set_invariant_load(ptr); bx.typeid_metadata(typeid_for_trait_ref(bx.tcx(), get_trait_ref(bx.tcx(), ty)));
ptr let vtable_byte_offset = self.0 * bx.data_layout().pointer_size.bytes();
let type_checked_load = bx.type_checked_load(llvtable, vtable_byte_offset, typeid);
let func = bx.extract_value(type_checked_load, 0);
bx.pointercast(func, llty)
} else {
let ptr_align = bx.tcx().data_layout.pointer_align.abi;
let gep = bx.inbounds_gep(llty, llvtable, &[bx.const_usize(self.0)]);
let ptr = bx.load(llty, gep, ptr_align);
bx.nonnull_metadata(ptr);
// Vtable loads are invariant.
bx.set_invariant_load(ptr);
ptr
}
} }
pub fn get_usize<Bx: BuilderMethods<'a, 'tcx>>( pub fn get_usize<Bx: BuilderMethods<'a, 'tcx>>(
@ -50,6 +64,24 @@ impl<'a, 'tcx> VirtualIndex {
} }
} }
fn get_trait_ref<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ty::PolyExistentialTraitRef<'tcx> {
for arg in ty.peel_refs().walk() {
if let GenericArgKind::Type(ty) = arg.unpack() {
if let ty::Dynamic(trait_refs, _) = ty.kind() {
return trait_refs[0].map_bound(|trait_ref| match trait_ref {
ExistentialPredicate::Trait(tr) => tr,
ExistentialPredicate::Projection(proj) => proj.trait_ref(tcx),
ExistentialPredicate::AutoTrait(_) => {
bug!("auto traits don't have functions")
}
});
}
}
}
bug!("expected a `dyn Trait` ty, found {ty:?}")
}
/// Creates a dynamic vtable for the given type and vtable origin. /// Creates a dynamic vtable for the given type and vtable origin.
/// This is used only for objects. /// This is used only for objects.
/// ///

View file

@ -401,7 +401,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
args = &args[..1]; args = &args[..1];
( (
meth::VirtualIndex::from_index(ty::COMMON_VTABLE_ENTRIES_DROPINPLACE) meth::VirtualIndex::from_index(ty::COMMON_VTABLE_ENTRIES_DROPINPLACE)
.get_fn(&mut bx, vtable, &fn_abi), .get_fn(&mut bx, vtable, ty, &fn_abi),
fn_abi, fn_abi,
) )
} }
@ -819,9 +819,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
// the data pointer as the first argument // the data pointer as the first argument
match op.val { match op.val {
Pair(data_ptr, meta) => { Pair(data_ptr, meta) => {
llfn = Some( llfn = Some(meth::VirtualIndex::from_index(idx).get_fn(
meth::VirtualIndex::from_index(idx).get_fn(&mut bx, meta, &fn_abi), &mut bx,
); meta,
op.layout.ty,
&fn_abi,
));
llargs.push(data_ptr); llargs.push(data_ptr);
continue 'make_args; continue 'make_args;
} }
@ -829,7 +832,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
} }
} else if let Ref(data_ptr, Some(meta), _) = op.val { } else if let Ref(data_ptr, Some(meta), _) = op.val {
// by-value dynamic dispatch // by-value dynamic dispatch
llfn = Some(meth::VirtualIndex::from_index(idx).get_fn(&mut bx, meta, &fn_abi)); llfn = Some(meth::VirtualIndex::from_index(idx).get_fn(
&mut bx,
meta,
op.layout.ty,
&fn_abi,
));
llargs.push(data_ptr); llargs.push(data_ptr);
continue; continue;
} else { } else {

View file

@ -22,6 +22,14 @@ pub trait IntrinsicCallMethods<'tcx>: BackendTypes {
fn expect(&mut self, cond: Self::Value, expected: bool) -> Self::Value; fn expect(&mut self, cond: Self::Value, expected: bool) -> Self::Value;
/// Trait method used to test whether a given pointer is associated with a type identifier. /// Trait method used to test whether a given pointer is associated with a type identifier.
fn type_test(&mut self, pointer: Self::Value, typeid: Self::Value) -> Self::Value; fn type_test(&mut self, pointer: Self::Value, typeid: Self::Value) -> Self::Value;
/// Trait method used to load a function while testing if it is associated with a type
/// identifier.
fn type_checked_load(
&mut self,
llvtable: Self::Value,
vtable_byte_offset: u64,
typeid: Self::Value,
) -> Self::Value;
/// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in /// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in
/// Rust defined C-variadic functions. /// Rust defined C-variadic functions.
fn va_start(&mut self, val: Self::Value) -> Self::Value; fn va_start(&mut self, val: Self::Value) -> Self::Value;

View file

@ -797,6 +797,7 @@ fn test_debugging_options_tracking_hash() {
tracked!(unleash_the_miri_inside_of_you, true); tracked!(unleash_the_miri_inside_of_you, true);
tracked!(use_ctors_section, Some(true)); tracked!(use_ctors_section, Some(true));
tracked!(verify_llvm_ir, true); tracked!(verify_llvm_ir, true);
tracked!(virtual_function_elimination, true);
tracked!(wasi_exec_model, Some(WasiExecModel::Reactor)); tracked!(wasi_exec_model, Some(WasiExecModel::Reactor));
macro_rules! tracked_no_crate_hash { macro_rules! tracked_no_crate_hash {

View file

@ -672,10 +672,20 @@ extern "C" void LLVMRustAddModuleFlag(
unwrap(M)->addModuleFlag(MergeBehavior, Name, Value); unwrap(M)->addModuleFlag(MergeBehavior, Name, Value);
} }
extern "C" bool LLVMRustHasModuleFlag(LLVMModuleRef M, const char *Name,
size_t Len) {
return unwrap(M)->getModuleFlag(StringRef(Name, Len)) != nullptr;
}
extern "C" LLVMValueRef LLVMRustMetadataAsValue(LLVMContextRef C, LLVMMetadataRef MD) { extern "C" LLVMValueRef LLVMRustMetadataAsValue(LLVMContextRef C, LLVMMetadataRef MD) {
return wrap(MetadataAsValue::get(*unwrap(C), unwrap(MD))); return wrap(MetadataAsValue::get(*unwrap(C), unwrap(MD)));
} }
extern "C" void LLVMRustGlobalAddMetadata(
LLVMValueRef Global, unsigned Kind, LLVMMetadataRef MD) {
unwrap<GlobalObject>(Global)->addMetadata(Kind, *unwrap<MDNode>(MD));
}
extern "C" LLVMRustDIBuilderRef LLVMRustDIBuilderCreate(LLVMModuleRef M) { extern "C" LLVMRustDIBuilderRef LLVMRustDIBuilderCreate(LLVMModuleRef M) {
return new DIBuilder(*unwrap(M)); return new DIBuilder(*unwrap(M));
} }

View file

@ -1585,6 +1585,9 @@ options! {
"in general, enable more debug printouts (default: no)"), "in general, enable more debug printouts (default: no)"),
verify_llvm_ir: bool = (false, parse_bool, [TRACKED], verify_llvm_ir: bool = (false, parse_bool, [TRACKED],
"verify LLVM IR (default: no)"), "verify LLVM IR (default: no)"),
virtual_function_elimination: bool = (false, parse_bool, [TRACKED],
"enables dead virtual function elimination optimization. \
Requires `-Clto[=[fat,yes]]`"),
wasi_exec_model: Option<WasiExecModel> = (None, parse_wasi_exec_model, [TRACKED], wasi_exec_model: Option<WasiExecModel> = (None, parse_wasi_exec_model, [TRACKED],
"whether to build a wasi command or reactor"), "whether to build a wasi command or reactor"),

View file

@ -1433,14 +1433,14 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
); );
} }
// LLVM CFI requires LTO. // LLVM CFI and VFE both require LTO.
if sess.is_sanitizer_cfi_enabled() { if sess.lto() != config::Lto::Fat {
if sess.opts.cg.lto == config::LtoCli::Unspecified if sess.is_sanitizer_cfi_enabled() {
|| sess.opts.cg.lto == config::LtoCli::No
|| sess.opts.cg.lto == config::LtoCli::Thin
{
sess.err("`-Zsanitizer=cfi` requires `-Clto`"); sess.err("`-Zsanitizer=cfi` requires `-Clto`");
} }
if sess.opts.debugging_opts.virtual_function_elimination {
sess.err("`-Zvirtual-function-elimination` requires `-Clto`");
}
} }
if sess.opts.debugging_opts.stack_protector != StackProtector::None { if sess.opts.debugging_opts.stack_protector != StackProtector::None {

View file

@ -155,6 +155,13 @@ pub fn typeid_for_fnabi<'tcx>(tcx: TyCtxt<'tcx>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>)
v0::mangle_typeid_for_fnabi(tcx, fn_abi) v0::mangle_typeid_for_fnabi(tcx, fn_abi)
} }
pub fn typeid_for_trait_ref<'tcx>(
tcx: TyCtxt<'tcx>,
trait_ref: ty::PolyExistentialTraitRef<'tcx>,
) -> String {
v0::mangle_typeid_for_trait_ref(tcx, trait_ref)
}
/// Computes the symbol name for the given instance. This function will call /// Computes the symbol name for the given instance. This function will call
/// `compute_instantiating_crate` if it needs to factor the instantiating crate /// `compute_instantiating_crate` if it needs to factor the instantiating crate
/// into the symbol name. /// into the symbol name.

View file

@ -94,6 +94,24 @@ pub(super) fn mangle_typeid_for_fnabi<'tcx>(
format!("typeid{}", arg_count) format!("typeid{}", arg_count)
} }
pub(super) fn mangle_typeid_for_trait_ref<'tcx>(
tcx: TyCtxt<'tcx>,
trait_ref: ty::PolyExistentialTraitRef<'tcx>,
) -> String {
// FIXME(flip1995): See comment in `mangle_typeid_for_fnabi`.
let mut cx = &mut SymbolMangler {
tcx,
start_offset: 0,
paths: FxHashMap::default(),
types: FxHashMap::default(),
consts: FxHashMap::default(),
binders: vec![],
out: String::new(),
};
cx = cx.print_def_path(trait_ref.def_id(), &[]).unwrap();
std::mem::take(&mut cx.out)
}
struct BinderLevel { struct BinderLevel {
/// The range of distances from the root of what's /// The range of distances from the root of what's
/// being printed, to the lifetimes in a binder. /// being printed, to the lifetimes in a binder.

View file

@ -0,0 +1,39 @@
# `virtual-function-elimination`
This option controls whether LLVM runs the Virtual Function Elimination (VFE)
optimization. This optimization in only available with LTO, so this flag can
only be passed if [`-Clto`][Clto] is also passed.
VFE makes it possible to remove functions from vtables that are never
dynamically called by the rest of the code. Without this flag, LLVM makes the
really conservative assumption, that if any function in a vtable is called, no
function that is referenced by this vtable can be removed. With this flag
additional information are given to LLVM, so that it can determine which
functions are actually called and remove the unused functions.
## Limitations
At the time of writing this flag may remove vtable functions too eagerly. One
such example is in this code:
```rust
trait Foo { fn foo(&self) { println!("foo") } }
impl Foo for usize {}
pub struct FooBox(Box<dyn Foo>);
pub fn make_foo() -> FooBox { FooBox(Box::new(0)) }
#[inline]
pub fn f(a: FooBox) { a.0.foo() }
```
In the above code the `Foo` trait is private, so an assumption is made that its
functions can only be seen/called from the current crate and can therefore get
optimized out, if unused. However, with `make_foo` you can produce a wrapped
`dyn Foo` type outside of the current crate, which can then be used in `f`. Due
to inlining of `f`, `Foo::foo` can then be called from a foreign crate. This can
lead to miscompilations.
[Clto]: https://doc.rust-lang.org/rustc/codegen-options/index.html#lto

View file

@ -0,0 +1,35 @@
// compile-flags: -Zvirtual-function-elimination -Clto -O -Csymbol-mangling-version=v0
// ignore-64bit
// CHECK: @vtable.0 = {{.*}}, !type ![[TYPE0:[0-9]+]], !vcall_visibility ![[VCALL_VIS0:[0-9]+]]
#![crate_type = "lib"]
trait T {
// CHECK-LABEL: ; <virtual_function_elimination_32bit::S as virtual_function_elimination_32bit::T>::used
fn used(&self) -> i32 {
1
}
// CHECK-LABEL-NOT: {{.*}}::unused
fn unused(&self) -> i32 {
2
}
}
#[derive(Copy, Clone)]
struct S;
impl T for S {}
fn taking_t(t: &dyn T) -> i32 {
// CHECK: @llvm.type.checked.load({{.*}}, i32 12, metadata !"[[MANGLED_TYPE0:[0-9a-zA-Z_]+]]")
t.used()
}
pub fn main() {
let s = S;
taking_t(&s);
}
// CHECK: ![[TYPE0]] = !{i32 0, !"[[MANGLED_TYPE0]]"}
// CHECK: ![[VCALL_VIS0]] = !{i64 2}

View file

@ -0,0 +1,100 @@
// compile-flags: -Zvirtual-function-elimination -Clto -O -Csymbol-mangling-version=v0
// ignore-32bit
// CHECK: @vtable.0 = {{.*}}, !type ![[TYPE0:[0-9]+]], !vcall_visibility ![[VCALL_VIS0:[0-9]+]]
// CHECK: @vtable.1 = {{.*}}, !type ![[TYPE1:[0-9]+]], !vcall_visibility ![[VCALL_VIS0:[0-9]+]]
// CHECK: @vtable.2 = {{.*}}, !type ![[TYPE2:[0-9]+]], !vcall_visibility ![[VCALL_VIS2:[0-9]+]]
#![crate_type = "lib"]
#![allow(incomplete_features)]
#![feature(unsized_locals)]
use std::rc::Rc;
trait T {
// CHECK-LABEL: ; <virtual_function_elimination::S as virtual_function_elimination::T>::used
fn used(&self) -> i32 {
1
}
// CHECK-LABEL: ; <virtual_function_elimination::S as virtual_function_elimination::T>::used_through_sub_trait
fn used_through_sub_trait(&self) -> i32 {
3
}
// CHECK-LABEL: ; <virtual_function_elimination::S as virtual_function_elimination::T>::by_rc
fn by_rc(self: Rc<Self>) -> i32 {
self.used() + self.used()
}
// CHECK-LABEL-NOT: {{.*}}::unused
fn unused(&self) -> i32 {
2
}
// CHECK-LABEL-NOT: {{.*}}::by_rc_unused
fn by_rc_unused(self: Rc<Self>) -> i32 {
self.by_rc()
}
}
trait U: T {
// CHECK-LABEL: ; <virtual_function_elimination::S as virtual_function_elimination::U>::subtrait_used
fn subtrait_used(&self) -> i32 {
4
}
// CHECK-LABEL-NOT: {{.*}}::subtrait_unused
fn subtrait_unused(&self) -> i32 {
5
}
}
pub trait V {
// CHECK-LABEL: ; <virtual_function_elimination::S as virtual_function_elimination::V>::public_function
fn public_function(&self) -> i32;
}
#[derive(Copy, Clone)]
struct S;
impl T for S {}
impl U for S {}
impl V for S {
fn public_function(&self) -> i32 {
6
}
}
fn taking_t(t: &dyn T) -> i32 {
// CHECK: @llvm.type.checked.load({{.*}}, i32 24, metadata !"[[MANGLED_TYPE0:[0-9a-zA-Z_]+]]")
t.used()
}
fn taking_rc_t(t: Rc<dyn T>) -> i32 {
// CHECK: @llvm.type.checked.load({{.*}}, i32 40, metadata !"[[MANGLED_TYPE0:[0-9a-zA-Z_]+]]")
t.by_rc()
}
fn taking_u(u: &dyn U) -> i32 {
// CHECK: @llvm.type.checked.load({{.*}}, i32 64, metadata !"[[MANGLED_TYPE1:[0-9a-zA-Z_]+]]")
// CHECK: @llvm.type.checked.load({{.*}}, i32 24, metadata !"[[MANGLED_TYPE1:[0-9a-zA-Z_]+]]")
// CHECK: @llvm.type.checked.load({{.*}}, i32 32, metadata !"[[MANGLED_TYPE1:[0-9a-zA-Z_]+]]")
u.subtrait_used() + u.used() + u.used_through_sub_trait()
}
pub fn taking_v(v: &dyn V) -> i32 {
// CHECK: @llvm.type.checked.load({{.*}}, i32 24, metadata !"NtCsfRpWlKdQPZn_28virtual_function_elimination1V")
v.public_function()
}
pub fn main() {
let s = S;
taking_t(&s);
taking_rc_t(Rc::new(s));
taking_u(&s);
taking_v(&s);
}
// CHECK: ![[TYPE0]] = !{i64 0, !"[[MANGLED_TYPE0]]"}
// CHECK: ![[VCALL_VIS0]] = !{i64 2}
// CHECK: ![[TYPE1]] = !{i64 0, !"[[MANGLED_TYPE1]]"}
// CHECK: ![[TYPE2]] = !{i64 0, !"NtCsfRpWlKdQPZn_28virtual_function_elimination1V"}
// CHECK: ![[VCALL_VIS2]] = !{i64 1}