Skip to content

Commit e96e6e2

Browse files
committed
Add metadata generation for vtables when using VFE
This adds the typeid and `vcall_visibility` metadata to vtables when the -Cvirtual-function-elimination flag is set. The typeid is generated in the same way as for the `llvm.type.checked.load` intrinsic from the trait_ref. The offset that is added to the typeid is always 0. This is because LLVM assumes that vtables are constructed according to the definition in the Itanium ABI. This includes an "address point" of the vtable. In C++ this is the offset in the vtable where information for RTTI is placed. Since there is no RTTI information in Rust's vtables, this "address point" is always 0. This "address point" in combination with the offset passed to the `llvm.type.checked.load` intrinsic determines the final function that should be loaded from the vtable in the `WholeProgramDevirtualization` pass in LLVM. That's why the `llvm.type.checked.load` intrinsics are generated with the typeid of the trait, rather than with that of the function that is called. This matches what `clang` does for C++. The vcall_visibility metadata depends on three factors: 1. LTO level: Currently this is always fat LTO, because LLVM only supports this optimization with fat LTO. 2. Visibility of the trait: If the trait is publicly visible, VFE can only act on its vtables after linking. 3. Number of CGUs: if there is more than one CGU, also vtables with restricted visibility could be seen outside of the CGU, so VFE can only act on them after linking. To reflect this, there are three visibility levels: Public, LinkageUnit, and TranslationUnit.
1 parent e1c1d0f commit e96e6e2

File tree

5 files changed

+106
-7
lines changed

5 files changed

+106
-7
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3695,6 +3695,7 @@ dependencies = [
36953695
"rustc_serialize",
36963696
"rustc_session",
36973697
"rustc_span",
3698+
"rustc_symbol_mangling",
36983699
"rustc_target",
36993700
"smallvec",
37003701
"tracing",

compiler/rustc_codegen_llvm/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ rustc-demangle = "0.1.21"
1919
rustc_arena = { path = "../rustc_arena" }
2020
rustc_attr = { path = "../rustc_attr" }
2121
rustc_codegen_ssa = { path = "../rustc_codegen_ssa" }
22+
rustc_symbol_mangling = { path = "../rustc_symbol_mangling" }
2223
rustc_data_structures = { path = "../rustc_data_structures" }
2324
rustc_errors = { path = "../rustc_errors" }
2425
rustc_fs_util = { path = "../rustc_fs_util" }

compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs

+92-7
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,21 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE};
3030
use rustc_index::vec::{Idx, IndexVec};
3131
use rustc_middle::bug;
3232
use rustc_middle::mir::{self, GeneratorLayout};
33-
use rustc_middle::ty::layout::LayoutOf;
34-
use rustc_middle::ty::layout::TyAndLayout;
33+
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
3534
use rustc_middle::ty::subst::GenericArgKind;
36-
use rustc_middle::ty::{self, AdtKind, Instance, ParamEnv, Ty, TyCtxt};
37-
use rustc_session::config::{self, DebugInfo};
35+
use rustc_middle::ty::{
36+
self, AdtKind, Instance, ParamEnv, PolyExistentialTraitRef, Ty, TyCtxt, Visibility,
37+
};
38+
use rustc_session::config::{self, DebugInfo, Lto};
3839
use rustc_span::symbol::Symbol;
3940
use rustc_span::FileName;
40-
use rustc_span::FileNameDisplayPreference;
41-
use rustc_span::{self, SourceFile};
41+
use rustc_span::{self, FileNameDisplayPreference, SourceFile};
42+
use rustc_symbol_mangling::typeid_for_trait_ref;
4243
use rustc_target::abi::{Align, Size};
4344
use smallvec::smallvec;
4445
use tracing::debug;
4546

46-
use libc::{c_longlong, c_uint};
47+
use libc::{c_char, c_longlong, c_uint};
4748
use std::borrow::Cow;
4849
use std::fmt::{self, Write};
4950
use std::hash::{Hash, Hasher};
@@ -1468,6 +1469,84 @@ fn build_vtable_type_di_node<'ll, 'tcx>(
14681469
.di_node
14691470
}
14701471

1472+
fn vcall_visibility_metadata<'ll, 'tcx>(
1473+
cx: &CodegenCx<'ll, 'tcx>,
1474+
ty: Ty<'tcx>,
1475+
trait_ref: Option<PolyExistentialTraitRef<'tcx>>,
1476+
vtable: &'ll Value,
1477+
) {
1478+
enum VCallVisibility {
1479+
Public = 0,
1480+
LinkageUnit = 1,
1481+
TranslationUnit = 2,
1482+
}
1483+
1484+
let Some(trait_ref) = trait_ref else { return };
1485+
1486+
let trait_ref_self = trait_ref.with_self_ty(cx.tcx, ty);
1487+
let trait_ref_self = cx.tcx.erase_regions(trait_ref_self);
1488+
let trait_def_id = trait_ref_self.def_id();
1489+
let trait_vis = cx.tcx.visibility(trait_def_id);
1490+
1491+
let cgus = cx.sess().codegen_units();
1492+
let single_cgu = cgus == 1;
1493+
1494+
let lto = cx.sess().lto();
1495+
1496+
// Since LLVM requires full LTO for the virtual function elimination optimization to apply,
1497+
// only the `Lto::Fat` cases are relevant currently.
1498+
let vcall_visibility = match (lto, trait_vis, single_cgu) {
1499+
// If there is not LTO and the visibility in public, we have to assume that the vtable can
1500+
// be seen from anywhere. With multiple CGUs, the vtable is quasi-public.
1501+
(Lto::No | Lto::ThinLocal, Visibility::Public, _)
1502+
| (Lto::No, Visibility::Restricted(_) | Visibility::Invisible, false) => {
1503+
VCallVisibility::Public
1504+
}
1505+
// With LTO and a quasi-public visibility, the usages of the functions of the vtable are
1506+
// all known by the `LinkageUnit`.
1507+
// FIXME: LLVM only supports this optimization for `Lto::Fat` currently. Once it also
1508+
// supports `Lto::Thin` the `VCallVisibility` may have to be adjusted for those.
1509+
(Lto::Fat | Lto::Thin, Visibility::Public, _)
1510+
| (
1511+
Lto::ThinLocal | Lto::Thin | Lto::Fat,
1512+
Visibility::Restricted(_) | Visibility::Invisible,
1513+
false,
1514+
) => VCallVisibility::LinkageUnit,
1515+
// If there is only one CGU, private vtables can only be seen by that CGU/translation unit
1516+
// and therefore we know of all usages of functions in the vtable.
1517+
(_, Visibility::Restricted(_) | Visibility::Invisible, true) => {
1518+
VCallVisibility::TranslationUnit
1519+
}
1520+
};
1521+
1522+
let trait_ref_typeid = typeid_for_trait_ref(cx.tcx, trait_ref);
1523+
1524+
unsafe {
1525+
let typeid = llvm::LLVMMDStringInContext(
1526+
cx.llcx,
1527+
trait_ref_typeid.as_ptr() as *const c_char,
1528+
trait_ref_typeid.as_bytes().len() as c_uint,
1529+
);
1530+
let v = [cx.const_usize(0), typeid];
1531+
llvm::LLVMRustGlobalAddMetadata(
1532+
vtable,
1533+
llvm::MD_type as c_uint,
1534+
llvm::LLVMValueAsMetadata(llvm::LLVMMDNodeInContext(
1535+
cx.llcx,
1536+
v.as_ptr(),
1537+
v.len() as c_uint,
1538+
)),
1539+
);
1540+
let vcall_visibility = llvm::LLVMValueAsMetadata(cx.const_u64(vcall_visibility as u64));
1541+
let vcall_visibility_metadata = llvm::LLVMMDNodeInContext2(cx.llcx, &vcall_visibility, 1);
1542+
llvm::LLVMGlobalSetMetadata(
1543+
vtable,
1544+
llvm::MetadataType::MD_vcall_visibility as c_uint,
1545+
vcall_visibility_metadata,
1546+
);
1547+
}
1548+
}
1549+
14711550
/// Creates debug information for the given vtable, which is for the
14721551
/// given type.
14731552
///
@@ -1478,6 +1557,12 @@ pub fn create_vtable_di_node<'ll, 'tcx>(
14781557
poly_trait_ref: Option<ty::PolyExistentialTraitRef<'tcx>>,
14791558
vtable: &'ll Value,
14801559
) {
1560+
// FIXME(flip1995): The virtual function elimination optimization only works with full LTO in
1561+
// LLVM at the moment.
1562+
if cx.sess().opts.debugging_opts.virtual_function_elimination && cx.sess().lto() == Lto::Fat {
1563+
vcall_visibility_metadata(cx, ty, poly_trait_ref, vtable);
1564+
}
1565+
14811566
if cx.dbg_cx.is_none() {
14821567
return;
14831568
}

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

+7
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ pub enum MetadataType {
442442
MD_nonnull = 11,
443443
MD_align = 17,
444444
MD_type = 19,
445+
MD_vcall_visibility = 28,
445446
MD_noundef = 29,
446447
}
447448

@@ -1067,6 +1068,7 @@ extern "C" {
10671068
pub fn LLVMReplaceAllUsesWith<'a>(OldVal: &'a Value, NewVal: &'a Value);
10681069
pub fn LLVMSetMetadata<'a>(Val: &'a Value, KindID: c_uint, Node: &'a Value);
10691070
pub fn LLVMGlobalSetMetadata<'a>(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata);
1071+
pub fn LLVMRustGlobalAddMetadata<'a>(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata);
10701072
pub fn LLVMValueAsMetadata(Node: &Value) -> &Metadata;
10711073

10721074
// Operations on constants of any type
@@ -1080,6 +1082,11 @@ extern "C" {
10801082
Vals: *const &'a Value,
10811083
Count: c_uint,
10821084
) -> &'a Value;
1085+
pub fn LLVMMDNodeInContext2<'a>(
1086+
C: &'a Context,
1087+
Vals: *const &'a Metadata,
1088+
Count: size_t,
1089+
) -> &'a Metadata;
10831090
pub fn LLVMAddNamedMetadataOperand<'a>(M: &'a Module, Name: *const c_char, Val: &'a Value);
10841091

10851092
// Operations on scalar constants

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,11 @@ extern "C" LLVMValueRef LLVMRustMetadataAsValue(LLVMContextRef C, LLVMMetadataRe
681681
return wrap(MetadataAsValue::get(*unwrap(C), unwrap(MD)));
682682
}
683683

684+
extern "C" void LLVMRustGlobalAddMetadata(
685+
LLVMValueRef Global, unsigned Kind, LLVMMetadataRef MD) {
686+
unwrap<GlobalObject>(Global)->addMetadata(Kind, *unwrap<MDNode>(MD));
687+
}
688+
684689
extern "C" LLVMRustDIBuilderRef LLVMRustDIBuilderCreate(LLVMModuleRef M) {
685690
return new DIBuilder(*unwrap(M));
686691
}

0 commit comments

Comments
 (0)