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_session",
"rustc_span",
"rustc_symbol_mangling",
"rustc_target",
"smallvec",
"tracing",

View file

@ -356,6 +356,16 @@ fn type_test(&mut self, _pointer: Self::Value, _typeid: Self::Value) -> Self::Va
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> {
unimplemented!();
}

View file

@ -19,6 +19,7 @@ rustc-demangle = "0.1.21"
rustc_arena = { path = "../rustc_arena" }
rustc_attr = { path = "../rustc_attr" }
rustc_codegen_ssa = { path = "../rustc_codegen_ssa" }
rustc_symbol_mangling = { path = "../rustc_symbol_mangling" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
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.
//
// 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");
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(
&config.new_llvm_pass_manager,
&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
}
@ -656,6 +665,7 @@ macro_rules! mk_struct {
let t_isize = self.type_isize();
let t_f32 = self.type_f32();
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.f64", fn(t_f64) -> t_i32);
@ -881,11 +891,12 @@ macro_rules! mk_struct {
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 {
ifn!("llvm.dbg.declare", fn(self.type_metadata(), self.type_metadata()) -> void);
ifn!("llvm.dbg.value", fn(self.type_metadata(), t_i64, self.type_metadata()) -> void);
ifn!("llvm.dbg.declare", fn(t_metadata, t_metadata) -> void);
ifn!("llvm.dbg.value", fn(t_metadata, t_i64, t_metadata) -> void);
}
None
}

View file

@ -30,20 +30,21 @@
use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::bug;
use rustc_middle::mir::{self, GeneratorLayout};
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, AdtKind, Instance, ParamEnv, Ty, TyCtxt};
use rustc_session::config::{self, DebugInfo};
use rustc_middle::ty::{
self, AdtKind, Instance, ParamEnv, PolyExistentialTraitRef, Ty, TyCtxt, Visibility,
};
use rustc_session::config::{self, DebugInfo, Lto};
use rustc_span::symbol::Symbol;
use rustc_span::FileName;
use rustc_span::FileNameDisplayPreference;
use rustc_span::{self, SourceFile};
use rustc_span::{self, FileNameDisplayPreference, SourceFile};
use rustc_symbol_mangling::typeid_for_trait_ref;
use rustc_target::abi::{Align, Size};
use smallvec::smallvec;
use tracing::debug;
use libc::{c_longlong, c_uint};
use libc::{c_char, c_longlong, c_uint};
use std::borrow::Cow;
use std::fmt::{self, Write};
use std::hash::{Hash, Hasher};
@ -1468,6 +1469,84 @@ fn build_vtable_type_di_node<'ll, 'tcx>(
.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
/// given type.
///
@ -1478,6 +1557,12 @@ pub fn create_vtable_di_node<'ll, 'tcx>(
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
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() {
return;
}

View file

@ -406,6 +406,16 @@ fn type_test(&mut self, pointer: Self::Value, typeid: Self::Value) -> Self::Valu
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 {
self.call_intrinsic("llvm.va_start", &[va_list])
}

View file

@ -442,6 +442,7 @@ pub enum MetadataType {
MD_nonnull = 11,
MD_align = 17,
MD_type = 19,
MD_vcall_visibility = 28,
MD_noundef = 29,
}
@ -1067,6 +1068,7 @@ pub fn LLVMStructTypeInContext<'a>(
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 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;
// Operations on constants of any type
@ -1080,6 +1082,11 @@ pub fn LLVMMDNodeInContext<'a>(
Vals: *const &'a Value,
Count: c_uint,
) -> &'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);
// Operations on scalar constants
@ -1936,6 +1943,7 @@ pub fn LLVMRustAddModuleFlag(
name: *const c_char,
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;

View file

@ -1,6 +1,8 @@
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;
#[derive(Copy, Clone, Debug)]
@ -15,20 +17,32 @@ pub fn get_fn<Bx: BuilderMethods<'a, 'tcx>>(
self,
bx: &mut Bx,
llvtable: Bx::Value,
ty: Ty<'tcx>,
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
) -> Bx::Value {
// 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 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)]);
let ptr = bx.load(llty, gep, ptr_align);
bx.nonnull_metadata(ptr);
// Vtable loads are invariant.
bx.set_invariant_load(ptr);
ptr
if bx.cx().sess().opts.debugging_opts.virtual_function_elimination
&& bx.cx().sess().lto() == Lto::Fat
{
let typeid =
bx.typeid_metadata(typeid_for_trait_ref(bx.tcx(), get_trait_ref(bx.tcx(), ty)));
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>>(
@ -50,6 +64,24 @@ pub fn get_usize<Bx: BuilderMethods<'a, 'tcx>>(
}
}
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.
/// This is used only for objects.
///

View file

@ -401,7 +401,7 @@ fn codegen_drop_terminator(
args = &args[..1];
(
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,
)
}
@ -819,9 +819,12 @@ fn codegen_call_terminator(
// the data pointer as the first argument
match op.val {
Pair(data_ptr, meta) => {
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);
continue 'make_args;
}
@ -829,7 +832,12 @@ fn codegen_call_terminator(
}
} else if let Ref(data_ptr, Some(meta), _) = op.val {
// 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);
continue;
} else {

View file

@ -22,6 +22,14 @@ fn codegen_intrinsic_call(
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.
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
/// Rust defined C-variadic functions.
fn va_start(&mut self, val: Self::Value) -> Self::Value;

View file

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

View file

@ -672,10 +672,20 @@ extern "C" void LLVMRustAddModuleFlag(
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) {
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) {
return new DIBuilder(*unwrap(M));
}

View file

@ -1585,6 +1585,9 @@ pub(crate) fn parse_branch_protection(
"in general, enable more debug printouts (default: no)"),
verify_llvm_ir: bool = (false, parse_bool, [TRACKED],
"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],
"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.
if sess.is_sanitizer_cfi_enabled() {
if sess.opts.cg.lto == config::LtoCli::Unspecified
|| sess.opts.cg.lto == config::LtoCli::No
|| sess.opts.cg.lto == config::LtoCli::Thin
{
// LLVM CFI and VFE both require LTO.
if sess.lto() != config::Lto::Fat {
if sess.is_sanitizer_cfi_enabled() {
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 {

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)
}
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
/// `compute_instantiating_crate` if it needs to factor the instantiating crate
/// into the symbol name.

View file

@ -94,6 +94,24 @@ pub(super) fn mangle_typeid_for_fnabi<'tcx>(
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 {
/// The range of distances from the root of what's
/// 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}