blob: 5ce188488ce59fcf1a3c6b1c9166b148e4d8c86b [file] [log] [blame]
/// Type metadata identifiers (using Itanium C++ ABI mangling for encoding) for LLVM Control Flow
/// Integrity (CFI) and cross-language LLVM CFI support.
///
/// Encodes type metadata identifiers for LLVM CFI and cross-language LLVM CFI support using Itanium
/// C++ ABI mangling for encoding with vendor extended type qualifiers and types for Rust types that
/// are not used across the FFI boundary.
///
/// For more information about LLVM CFI and cross-language LLVM CFI support for the Rust compiler,
/// see design document in the tracking issue #89653.
use rustc_data_structures::base_n;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{
self, Const, ExistentialPredicate, FloatTy, FnSig, Instance, IntTy, List, Region, RegionKind,
TermKind, Ty, TyCtxt, UintTy,
};
use rustc_middle::ty::{GenericArg, GenericArgKind, GenericArgsRef};
use rustc_span::def_id::DefId;
use rustc_span::sym;
use rustc_target::abi::call::{Conv, FnAbi};
use rustc_target::abi::Integer;
use rustc_target::spec::abi::Abi;
use std::fmt::Write as _;
use crate::typeid::TypeIdOptions;
/// Type and extended type qualifiers.
#[derive(Eq, Hash, PartialEq)]
enum TyQ {
None,
Const,
Mut,
}
/// Substitution dictionary key.
#[derive(Eq, Hash, PartialEq)]
enum DictKey<'tcx> {
Ty(Ty<'tcx>, TyQ),
Region(Region<'tcx>),
Const(Const<'tcx>),
Predicate(ExistentialPredicate<'tcx>),
}
/// Options for encode_ty.
type EncodeTyOptions = TypeIdOptions;
/// Options for transform_ty.
type TransformTyOptions = TypeIdOptions;
/// Converts a number to a disambiguator (see
/// <https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html>).
fn to_disambiguator(num: u64) -> String {
if let Some(num) = num.checked_sub(1) {
format!("s{}_", base_n::encode(num as u128, 62))
} else {
"s_".to_string()
}
}
/// Converts a number to a sequence number (see
/// <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangle.seq-id>).
fn to_seq_id(num: usize) -> String {
if let Some(num) = num.checked_sub(1) {
base_n::encode(num as u128, 36).to_uppercase()
} else {
"".to_string()
}
}
/// Substitutes a component if found in the substitution dictionary (see
/// <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-compression>).
fn compress<'tcx>(
dict: &mut FxHashMap<DictKey<'tcx>, usize>,
key: DictKey<'tcx>,
comp: &mut String,
) {
match dict.get(&key) {
Some(num) => {
comp.clear();
let _ = write!(comp, "S{}_", to_seq_id(*num));
}
None => {
dict.insert(key, dict.len());
}
}
}
/// Encodes a const using the Itanium C++ ABI as a literal argument (see
/// <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling.literal>).
fn encode_const<'tcx>(
tcx: TyCtxt<'tcx>,
c: Const<'tcx>,
dict: &mut FxHashMap<DictKey<'tcx>, usize>,
options: EncodeTyOptions,
) -> String {
// L<element-type>[n][<element-value>]E as literal argument
let mut s = String::from('L');
match c.kind() {
// Const parameters
ty::ConstKind::Param(..) => {
// L<element-type>E as literal argument
// Element type
s.push_str(&encode_ty(tcx, c.ty(), dict, options));
}
// Literal arguments
ty::ConstKind::Value(..) => {
// L<element-type>[n]<element-value>E as literal argument
// Element type
s.push_str(&encode_ty(tcx, c.ty(), dict, options));
// The only allowed types of const values are bool, u8, u16, u32,
// u64, u128, usize i8, i16, i32, i64, i128, isize, and char. The
// bool value false is encoded as 0 and true as 1.
match c.ty().kind() {
ty::Int(ity) => {
let bits = c.eval_bits(tcx, ty::ParamEnv::reveal_all());
let val = Integer::from_int_ty(&tcx, *ity).size().sign_extend(bits) as i128;
if val < 0 {
s.push('n');
}
let _ = write!(s, "{val}");
}
ty::Uint(_) => {
let val = c.eval_bits(tcx, ty::ParamEnv::reveal_all());
let _ = write!(s, "{val}");
}
ty::Bool => {
let val = c.try_eval_bool(tcx, ty::ParamEnv::reveal_all()).unwrap();
let _ = write!(s, "{val}");
}
_ => {
bug!("encode_const: unexpected type `{:?}`", c.ty());
}
}
}
_ => {
bug!("encode_const: unexpected kind `{:?}`", c.kind());
}
}
// Close the "L..E" pair
s.push('E');
compress(dict, DictKey::Const(c), &mut s);
s
}
/// Encodes a FnSig using the Itanium C++ ABI with vendor extended type qualifiers and types for
/// Rust types that are not used at the FFI boundary.
#[instrument(level = "trace", skip(tcx, dict))]
fn encode_fnsig<'tcx>(
tcx: TyCtxt<'tcx>,
fn_sig: &FnSig<'tcx>,
dict: &mut FxHashMap<DictKey<'tcx>, usize>,
options: TypeIdOptions,
) -> String {
// Function types are delimited by an "F..E" pair
let mut s = String::from("F");
let mut encode_ty_options = EncodeTyOptions::from_bits(options.bits())
.unwrap_or_else(|| bug!("encode_fnsig: invalid option(s) `{:?}`", options.bits()));
match fn_sig.abi {
Abi::C { .. } => {
encode_ty_options.insert(EncodeTyOptions::GENERALIZE_REPR_C);
}
_ => {
encode_ty_options.remove(EncodeTyOptions::GENERALIZE_REPR_C);
}
}
// Encode the return type
let transform_ty_options = TransformTyOptions::from_bits(options.bits())
.unwrap_or_else(|| bug!("encode_fnsig: invalid option(s) `{:?}`", options.bits()));
let ty = transform_ty(tcx, fn_sig.output(), transform_ty_options);
s.push_str(&encode_ty(tcx, ty, dict, encode_ty_options));
// Encode the parameter types
let tys = fn_sig.inputs();
if !tys.is_empty() {
for ty in tys {
let ty = transform_ty(tcx, *ty, transform_ty_options);
s.push_str(&encode_ty(tcx, ty, dict, encode_ty_options));
}
if fn_sig.c_variadic {
s.push('z');
}
} else {
if fn_sig.c_variadic {
s.push('z');
} else {
// Empty parameter lists, whether declared as () or conventionally as (void), are
// encoded with a void parameter specifier "v".
s.push('v')
}
}
// Close the "F..E" pair
s.push('E');
s
}
/// Encodes a predicate using the Itanium C++ ABI with vendor extended type qualifiers and types for
/// Rust types that are not used at the FFI boundary.
fn encode_predicate<'tcx>(
tcx: TyCtxt<'tcx>,
predicate: ty::PolyExistentialPredicate<'tcx>,
dict: &mut FxHashMap<DictKey<'tcx>, usize>,
options: EncodeTyOptions,
) -> String {
// u<length><name>[I<element-type1..element-typeN>E], where <element-type> is <subst>, as vendor
// extended type.
let mut s = String::new();
match predicate.as_ref().skip_binder() {
ty::ExistentialPredicate::Trait(trait_ref) => {
let name = encode_ty_name(tcx, trait_ref.def_id);
let _ = write!(s, "u{}{}", name.len(), &name);
s.push_str(&encode_args(tcx, trait_ref.args, dict, options));
}
ty::ExistentialPredicate::Projection(projection) => {
let name = encode_ty_name(tcx, projection.def_id);
let _ = write!(s, "u{}{}", name.len(), &name);
s.push_str(&encode_args(tcx, projection.args, dict, options));
match projection.term.unpack() {
TermKind::Ty(ty) => s.push_str(&encode_ty(tcx, ty, dict, options)),
TermKind::Const(c) => s.push_str(&encode_const(tcx, c, dict, options)),
}
}
ty::ExistentialPredicate::AutoTrait(def_id) => {
let name = encode_ty_name(tcx, *def_id);
let _ = write!(s, "u{}{}", name.len(), &name);
}
};
compress(dict, DictKey::Predicate(*predicate.as_ref().skip_binder()), &mut s);
s
}
/// Encodes predicates using the Itanium C++ ABI with vendor extended type qualifiers and types for
/// Rust types that are not used at the FFI boundary.
fn encode_predicates<'tcx>(
tcx: TyCtxt<'tcx>,
predicates: &List<ty::PolyExistentialPredicate<'tcx>>,
dict: &mut FxHashMap<DictKey<'tcx>, usize>,
options: EncodeTyOptions,
) -> String {
// <predicate1[..predicateN]>E as part of vendor extended type
let mut s = String::new();
let predicates: Vec<ty::PolyExistentialPredicate<'tcx>> = predicates.iter().collect();
for predicate in predicates {
s.push_str(&encode_predicate(tcx, predicate, dict, options));
}
s
}
/// Encodes a region using the Itanium C++ ABI as a vendor extended type.
fn encode_region<'tcx>(
_tcx: TyCtxt<'tcx>,
region: Region<'tcx>,
dict: &mut FxHashMap<DictKey<'tcx>, usize>,
_options: EncodeTyOptions,
) -> String {
// u6region[I[<region-disambiguator>][<region-index>]E] as vendor extended type
let mut s = String::new();
match region.kind() {
RegionKind::ReLateBound(debruijn, r) => {
s.push_str("u6regionI");
// Debruijn index, which identifies the binder, as region disambiguator
let num = debruijn.index() as u64;
if num > 0 {
s.push_str(&to_disambiguator(num));
}
// Index within the binder
let _ = write!(s, "{}", r.var.index() as u64);
s.push('E');
compress(dict, DictKey::Region(region), &mut s);
}
RegionKind::ReEarlyBound(..) | RegionKind::ReErased => {
s.push_str("u6region");
compress(dict, DictKey::Region(region), &mut s);
}
RegionKind::ReFree(..)
| RegionKind::ReStatic
| RegionKind::ReError(_)
| RegionKind::ReVar(..)
| RegionKind::RePlaceholder(..) => {
bug!("encode_region: unexpected `{:?}`", region.kind());
}
}
s
}
/// Encodes args using the Itanium C++ ABI with vendor extended type qualifiers and types for Rust
/// types that are not used at the FFI boundary.
fn encode_args<'tcx>(
tcx: TyCtxt<'tcx>,
args: GenericArgsRef<'tcx>,
dict: &mut FxHashMap<DictKey<'tcx>, usize>,
options: EncodeTyOptions,
) -> String {
// [I<subst1..substN>E] as part of vendor extended type
let mut s = String::new();
let args: Vec<GenericArg<'_>> = args.iter().collect();
if !args.is_empty() {
s.push('I');
for arg in args {
match arg.unpack() {
GenericArgKind::Lifetime(region) => {
s.push_str(&encode_region(tcx, region, dict, options));
}
GenericArgKind::Type(ty) => {
s.push_str(&encode_ty(tcx, ty, dict, options));
}
GenericArgKind::Const(c) => {
s.push_str(&encode_const(tcx, c, dict, options));
}
}
}
s.push('E');
}
s
}
/// Encodes a ty:Ty name, including its crate and path disambiguators and names.
fn encode_ty_name(tcx: TyCtxt<'_>, def_id: DefId) -> String {
// Encode <name> for use in u<length><name>[I<element-type1..element-typeN>E], where
// <element-type> is <subst>, using v0's <path> without v0's extended form of paths:
//
// N<namespace-tagN>..N<namespace-tag1>
// C<crate-disambiguator><crate-name>
// <path-disambiguator1><path-name1>..<path-disambiguatorN><path-nameN>
//
// With additional tags for DefPathData::Impl and DefPathData::ForeignMod. For instance:
//
// pub type Type1 = impl Send;
// let _: Type1 = <Struct1<i32>>::foo;
// fn foo1(_: Type1) { }
//
// pub type Type2 = impl Send;
// let _: Type2 = <Trait1<i32>>::foo;
// fn foo2(_: Type2) { }
//
// pub type Type3 = impl Send;
// let _: Type3 = <i32 as Trait1<i32>>::foo;
// fn foo3(_: Type3) { }
//
// pub type Type4 = impl Send;
// let _: Type4 = <Struct1<i32> as Trait1<i32>>::foo;
// fn foo3(_: Type4) { }
//
// Are encoded as:
//
// _ZTSFvu29NvNIC1234_5crate8{{impl}}3fooIu3i32EE
// _ZTSFvu27NvNtC1234_5crate6Trait13fooIu3dynIu21NtC1234_5crate6Trait1Iu3i32Eu6regionES_EE
// _ZTSFvu27NvNtC1234_5crate6Trait13fooIu3i32S_EE
// _ZTSFvu27NvNtC1234_5crate6Trait13fooIu22NtC1234_5crate7Struct1Iu3i32ES_EE
//
// The reason for not using v0's extended form of paths is to use a consistent and simpler
// encoding, as the reasoning for using it isn't relevand for type metadata identifiers (i.e.,
// keep symbol names close to how methods are represented in error messages). See
// https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html#methods.
let mut s = String::new();
// Start and namespace tags
let mut def_path = tcx.def_path(def_id);
def_path.data.reverse();
for disambiguated_data in &def_path.data {
s.push('N');
s.push_str(match disambiguated_data.data {
hir::definitions::DefPathData::Impl => "I", // Not specified in v0's <namespace>
hir::definitions::DefPathData::ForeignMod => "F", // Not specified in v0's <namespace>
hir::definitions::DefPathData::TypeNs(..) => "t",
hir::definitions::DefPathData::ValueNs(..) => "v",
hir::definitions::DefPathData::ClosureExpr => "C",
hir::definitions::DefPathData::Ctor => "c",
hir::definitions::DefPathData::AnonConst => "k",
hir::definitions::DefPathData::ImplTrait => "i",
hir::definitions::DefPathData::CrateRoot
| hir::definitions::DefPathData::Use
| hir::definitions::DefPathData::GlobalAsm
| hir::definitions::DefPathData::ImplTraitAssocTy
| hir::definitions::DefPathData::MacroNs(..)
| hir::definitions::DefPathData::LifetimeNs(..) => {
bug!("encode_ty_name: unexpected `{:?}`", disambiguated_data.data);
}
});
}
// Crate disambiguator and name
s.push('C');
s.push_str(&to_disambiguator(tcx.stable_crate_id(def_path.krate).as_u64()));
let crate_name = tcx.crate_name(def_path.krate).to_string();
let _ = write!(s, "{}{}", crate_name.len(), &crate_name);
// Disambiguators and names
def_path.data.reverse();
for disambiguated_data in &def_path.data {
let num = disambiguated_data.disambiguator as u64;
if num > 0 {
s.push_str(&to_disambiguator(num));
}
let name = disambiguated_data.data.to_string();
let _ = write!(s, "{}", name.len());
// Prepend a '_' if name starts with a digit or '_'
if let Some(first) = name.as_bytes().first() {
if first.is_ascii_digit() || *first == b'_' {
s.push('_');
}
} else {
bug!("encode_ty_name: invalid name `{:?}`", name);
}
s.push_str(&name);
}
s
}
/// Encodes a ty:Ty using the Itanium C++ ABI with vendor extended type qualifiers and types for
/// Rust types that are not used at the FFI boundary.
fn encode_ty<'tcx>(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
dict: &mut FxHashMap<DictKey<'tcx>, usize>,
options: EncodeTyOptions,
) -> String {
let mut typeid = String::new();
match ty.kind() {
// Primitive types
// Rust's bool has the same layout as C17's _Bool, that is, its size and alignment are
// implementation-defined. Any bool can be cast into an integer, taking on the values 1
// (true) or 0 (false).
//
// (See https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#bool.)
ty::Bool => {
typeid.push('b');
}
ty::Int(..) | ty::Uint(..) => {
// u<length><type-name> as vendor extended type
let mut s = String::from(match ty.kind() {
ty::Int(IntTy::I8) => "u2i8",
ty::Int(IntTy::I16) => "u3i16",
ty::Int(IntTy::I32) => "u3i32",
ty::Int(IntTy::I64) => "u3i64",
ty::Int(IntTy::I128) => "u4i128",
ty::Int(IntTy::Isize) => "u5isize",
ty::Uint(UintTy::U8) => "u2u8",
ty::Uint(UintTy::U16) => "u3u16",
ty::Uint(UintTy::U32) => "u3u32",
ty::Uint(UintTy::U64) => "u3u64",
ty::Uint(UintTy::U128) => "u4u128",
ty::Uint(UintTy::Usize) => "u5usize",
_ => bug!("encode_ty: unexpected `{:?}`", ty.kind()),
});
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
// Rust's f32 and f64 single (32-bit) and double (64-bit) precision floating-point types
// have IEEE-754 binary32 and binary64 floating-point layouts, respectively.
//
// (See https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#fixed-width-floating-point-types.)
ty::Float(float_ty) => {
typeid.push(match float_ty {
FloatTy::F32 => 'f',
FloatTy::F64 => 'd',
});
}
ty::Char => {
// u4char as vendor extended type
let mut s = String::from("u4char");
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
ty::Str => {
// u3str as vendor extended type
let mut s = String::from("u3str");
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
ty::Never => {
// u5never as vendor extended type
let mut s = String::from("u5never");
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
// Compound types
// () in Rust is equivalent to void return type in C
_ if ty.is_unit() => {
typeid.push('v');
}
// Sequence types
ty::Tuple(tys) => {
// u5tupleI<element-type1..element-typeN>E as vendor extended type
let mut s = String::from("u5tupleI");
for ty in tys.iter() {
s.push_str(&encode_ty(tcx, ty, dict, options));
}
s.push('E');
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
ty::Array(ty0, len) => {
// A<array-length><element-type>
let mut s = String::from("A");
let _ = write!(
s,
"{}",
&len.try_to_scalar()
.unwrap()
.to_u64()
.unwrap_or_else(|_| panic!("failed to convert length to u64"))
);
s.push_str(&encode_ty(tcx, *ty0, dict, options));
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
ty::Slice(ty0) => {
// u5sliceI<element-type>E as vendor extended type
let mut s = String::from("u5sliceI");
s.push_str(&encode_ty(tcx, *ty0, dict, options));
s.push('E');
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
// User-defined types
ty::Adt(adt_def, args) => {
let mut s = String::new();
let def_id = adt_def.did();
if let Some(cfi_encoding) = tcx.get_attr(def_id, sym::cfi_encoding) {
// Use user-defined CFI encoding for type
if let Some(value_str) = cfi_encoding.value_str() {
if !value_str.to_string().trim().is_empty() {
s.push_str(&value_str.to_string().trim());
} else {
#[allow(
rustc::diagnostic_outside_of_impl,
rustc::untranslatable_diagnostic
)]
tcx.sess
.struct_span_err(
cfi_encoding.span,
format!("invalid `cfi_encoding` for `{:?}`", ty.kind()),
)
.emit();
}
} else {
bug!("encode_ty: invalid `cfi_encoding` for `{:?}`", ty.kind());
}
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
} else if options.contains(EncodeTyOptions::GENERALIZE_REPR_C) && adt_def.repr().c() {
// For cross-language LLVM CFI support, the encoding must be compatible at the FFI
// boundary. For instance:
//
// struct type1 {};
// void foo(struct type1* bar) {}
//
// Is encoded as:
//
// _ZTSFvP5type1E
//
// So, encode any repr(C) user-defined type for extern function types with the "C"
// calling convention (or extern types [i.e., ty::Foreign]) as <length><name>, where
// <name> is <unscoped-name>.
let name = tcx.item_name(def_id).to_string();
let _ = write!(s, "{}{}", name.len(), &name);
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
} else {
// u<length><name>[I<element-type1..element-typeN>E], where <element-type> is
// <subst>, as vendor extended type.
let name = encode_ty_name(tcx, def_id);
let _ = write!(s, "u{}{}", name.len(), &name);
s.push_str(&encode_args(tcx, args, dict, options));
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
}
typeid.push_str(&s);
}
ty::Foreign(def_id) => {
// <length><name>, where <name> is <unscoped-name>
let mut s = String::new();
if let Some(cfi_encoding) = tcx.get_attr(*def_id, sym::cfi_encoding) {
// Use user-defined CFI encoding for type
if let Some(value_str) = cfi_encoding.value_str() {
if !value_str.to_string().trim().is_empty() {
s.push_str(&value_str.to_string().trim());
} else {
#[allow(
rustc::diagnostic_outside_of_impl,
rustc::untranslatable_diagnostic
)]
tcx.sess
.struct_span_err(
cfi_encoding.span,
format!("invalid `cfi_encoding` for `{:?}`", ty.kind()),
)
.emit();
}
} else {
bug!("encode_ty: invalid `cfi_encoding` for `{:?}`", ty.kind());
}
} else {
let name = tcx.item_name(*def_id).to_string();
let _ = write!(s, "{}{}", name.len(), &name);
}
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
// Function types
ty::FnDef(def_id, args) | ty::Closure(def_id, args) => {
// u<length><name>[I<element-type1..element-typeN>E], where <element-type> is <subst>,
// as vendor extended type.
let mut s = String::new();
let name = encode_ty_name(tcx, *def_id);
let _ = write!(s, "u{}{}", name.len(), &name);
s.push_str(&encode_args(tcx, args, dict, options));
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
ty::Coroutine(def_id, args, ..) => {
// u<length><name>[I<element-type1..element-typeN>E], where <element-type> is <subst>,
// as vendor extended type.
let mut s = String::new();
let name = encode_ty_name(tcx, *def_id);
let _ = write!(s, "u{}{}", name.len(), &name);
// Encode parent args only
s.push_str(&encode_args(
tcx,
tcx.mk_args(args.as_coroutine().parent_args()),
dict,
options,
));
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
// Pointer types
ty::Ref(region, ty0, ..) => {
// [U3mut]u3refI<element-type>E as vendor extended type qualifier and type
let mut s = String::new();
s.push_str("u3refI");
s.push_str(&encode_ty(tcx, *ty0, dict, options));
s.push('E');
compress(dict, DictKey::Ty(Ty::new_imm_ref(tcx, *region, *ty0), TyQ::None), &mut s);
if ty.is_mutable_ptr() {
s = format!("{}{}", "U3mut", &s);
compress(dict, DictKey::Ty(ty, TyQ::Mut), &mut s);
}
typeid.push_str(&s);
}
ty::RawPtr(tm) => {
// P[K]<element-type>
let mut s = String::new();
s.push_str(&encode_ty(tcx, tm.ty, dict, options));
if !ty.is_mutable_ptr() {
s = format!("{}{}", "K", &s);
compress(dict, DictKey::Ty(tm.ty, TyQ::Const), &mut s);
};
s = format!("{}{}", "P", &s);
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
ty::FnPtr(fn_sig) => {
// PF<return-type><parameter-type1..parameter-typeN>E
let mut s = String::from("P");
s.push_str(&encode_fnsig(tcx, &fn_sig.skip_binder(), dict, TypeIdOptions::empty()));
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
// Trait types
ty::Dynamic(predicates, region, kind) => {
// u3dynI<element-type1[..element-typeN]>E, where <element-type> is <predicate>, as
// vendor extended type.
let mut s = String::from(match kind {
ty::Dyn => "u3dynI",
ty::DynStar => "u7dynstarI",
});
s.push_str(&encode_predicates(tcx, predicates, dict, options));
s.push_str(&encode_region(tcx, *region, dict, options));
s.push('E');
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
// Type parameters
ty::Param(..) => {
// u5param as vendor extended type
let mut s = String::from("u5param");
compress(dict, DictKey::Ty(ty, TyQ::None), &mut s);
typeid.push_str(&s);
}
// Unexpected types
ty::Alias(..)
| ty::Bound(..)
| ty::Error(..)
| ty::CoroutineWitness(..)
| ty::Infer(..)
| ty::Placeholder(..) => {
bug!("encode_ty: unexpected `{:?}`", ty.kind());
}
};
typeid
}
/// Transforms predicates for being encoded and used in the substitution dictionary.
fn transform_predicates<'tcx>(
tcx: TyCtxt<'tcx>,
predicates: &List<ty::PolyExistentialPredicate<'tcx>>,
_options: EncodeTyOptions,
) -> &'tcx List<ty::PolyExistentialPredicate<'tcx>> {
let predicates: Vec<ty::PolyExistentialPredicate<'tcx>> = predicates
.iter()
.filter_map(|predicate| match predicate.skip_binder() {
ty::ExistentialPredicate::Trait(trait_ref) => {
let trait_ref = ty::TraitRef::identity(tcx, trait_ref.def_id);
Some(ty::Binder::dummy(ty::ExistentialPredicate::Trait(
ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref),
)))
}
ty::ExistentialPredicate::Projection(..) => None,
ty::ExistentialPredicate::AutoTrait(..) => Some(predicate),
})
.collect();
tcx.mk_poly_existential_predicates(&predicates)
}
/// Transforms args for being encoded and used in the substitution dictionary.
fn transform_args<'tcx>(
tcx: TyCtxt<'tcx>,
args: GenericArgsRef<'tcx>,
options: TransformTyOptions,
) -> GenericArgsRef<'tcx> {
let args = args.iter().map(|arg| match arg.unpack() {
GenericArgKind::Type(ty) if ty.is_c_void(tcx) => Ty::new_unit(tcx).into(),
GenericArgKind::Type(ty) => transform_ty(tcx, ty, options).into(),
_ => arg,
});
tcx.mk_args_from_iter(args)
}
// Transforms a ty:Ty for being encoded and used in the substitution dictionary. It transforms all
// c_void types into unit types unconditionally, generalizes pointers if
// TransformTyOptions::GENERALIZE_POINTERS option is set, and normalizes integers if
// TransformTyOptions::NORMALIZE_INTEGERS option is set.
fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptions) -> Ty<'tcx> {
let mut ty = ty;
match ty.kind() {
ty::Float(..)
| ty::Char
| ty::Str
| ty::Never
| ty::Foreign(..)
| ty::CoroutineWitness(..) => {}
ty::Bool => {
if options.contains(EncodeTyOptions::NORMALIZE_INTEGERS) {
// Note: on all platforms that Rust's currently supports, its size and alignment are
// 1, and its ABI class is INTEGER - see Rust Layout and ABIs.
//
// (See https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#bool.)
//
// Clang represents bool as an 8-bit unsigned integer.
ty = tcx.types.u8;
}
}
ty::Int(..) | ty::Uint(..) => {
if options.contains(EncodeTyOptions::NORMALIZE_INTEGERS) {
// Note: C99 7.18.2.4 requires uintptr_t and intptr_t to be at least 16-bit wide.
// All platforms we currently support have a C platform, and as a consequence,
// isize/usize are at least 16-bit wide for all of them.
//
// (See https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#isize-and-usize.)
match ty.kind() {
ty::Int(IntTy::Isize) => match tcx.sess.target.pointer_width {
16 => ty = tcx.types.i16,
32 => ty = tcx.types.i32,
64 => ty = tcx.types.i64,
128 => ty = tcx.types.i128,
_ => bug!(
"transform_ty: unexpected pointer width `{}`",
tcx.sess.target.pointer_width
),
},
ty::Uint(UintTy::Usize) => match tcx.sess.target.pointer_width {
16 => ty = tcx.types.u16,
32 => ty = tcx.types.u32,
64 => ty = tcx.types.u64,
128 => ty = tcx.types.u128,
_ => bug!(
"transform_ty: unexpected pointer width `{}`",
tcx.sess.target.pointer_width
),
},
_ => (),
}
}
}
_ if ty.is_unit() => {}
ty::Tuple(tys) => {
ty = Ty::new_tup_from_iter(tcx, tys.iter().map(|ty| transform_ty(tcx, ty, options)));
}
ty::Array(ty0, len) => {
let len = len.eval_target_usize(tcx, ty::ParamEnv::reveal_all());
ty = Ty::new_array(tcx, transform_ty(tcx, *ty0, options), len);
}
ty::Slice(ty0) => {
ty = Ty::new_slice(tcx, transform_ty(tcx, *ty0, options));
}
ty::Adt(adt_def, args) => {
if ty.is_c_void(tcx) {
ty = Ty::new_unit(tcx);
} else if options.contains(TransformTyOptions::GENERALIZE_REPR_C) && adt_def.repr().c()
{
ty = Ty::new_adt(tcx, *adt_def, ty::List::empty());
} else if adt_def.repr().transparent() && adt_def.is_struct() {
// Don't transform repr(transparent) types with an user-defined CFI encoding to
// preserve the user-defined CFI encoding.
if let Some(_) = tcx.get_attr(adt_def.did(), sym::cfi_encoding) {
return ty;
}
let variant = adt_def.non_enum_variant();
let param_env = tcx.param_env(variant.def_id);
let field = variant.fields.iter().find(|field| {
let ty = tcx.type_of(field.did).instantiate_identity();
let is_zst =
tcx.layout_of(param_env.and(ty)).is_ok_and(|layout| layout.is_zst());
!is_zst
});
if let Some(field) = field {
let ty0 = tcx.type_of(field.did).instantiate(tcx, args);
// Generalize any repr(transparent) user-defined type that is either a pointer
// or reference, and either references itself or any other type that contains or
// references itself, to avoid a reference cycle.
if ty0.is_any_ptr() && ty0.contains(ty) {
ty = transform_ty(
tcx,
ty0,
options | TransformTyOptions::GENERALIZE_POINTERS,
);
} else {
ty = transform_ty(tcx, ty0, options);
}
} else {
// Transform repr(transparent) types without non-ZST field into ()
ty = Ty::new_unit(tcx);
}
} else {
ty = Ty::new_adt(tcx, *adt_def, transform_args(tcx, args, options));
}
}
ty::FnDef(def_id, args) => {
ty = Ty::new_fn_def(tcx, *def_id, transform_args(tcx, args, options));
}
ty::Closure(def_id, args) => {
ty = Ty::new_closure(tcx, *def_id, transform_args(tcx, args, options));
}
ty::Coroutine(def_id, args, movability) => {
ty = Ty::new_coroutine(tcx, *def_id, transform_args(tcx, args, options), *movability);
}
ty::Ref(region, ty0, ..) => {
if options.contains(TransformTyOptions::GENERALIZE_POINTERS) {
if ty.is_mutable_ptr() {
ty = Ty::new_mut_ref(tcx, tcx.lifetimes.re_static, Ty::new_unit(tcx));
} else {
ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_static, Ty::new_unit(tcx));
}
} else {
if ty.is_mutable_ptr() {
ty = Ty::new_mut_ref(tcx, *region, transform_ty(tcx, *ty0, options));
} else {
ty = Ty::new_imm_ref(tcx, *region, transform_ty(tcx, *ty0, options));
}
}
}
ty::RawPtr(tm) => {
if options.contains(TransformTyOptions::GENERALIZE_POINTERS) {
if ty.is_mutable_ptr() {
ty = Ty::new_mut_ptr(tcx, Ty::new_unit(tcx));
} else {
ty = Ty::new_imm_ptr(tcx, Ty::new_unit(tcx));
}
} else {
if ty.is_mutable_ptr() {
ty = Ty::new_mut_ptr(tcx, transform_ty(tcx, tm.ty, options));
} else {
ty = Ty::new_imm_ptr(tcx, transform_ty(tcx, tm.ty, options));
}
}
}
ty::FnPtr(fn_sig) => {
if options.contains(TransformTyOptions::GENERALIZE_POINTERS) {
ty = Ty::new_imm_ptr(tcx, Ty::new_unit(tcx));
} else {
let parameters: Vec<Ty<'tcx>> = fn_sig
.skip_binder()
.inputs()
.iter()
.map(|ty| transform_ty(tcx, *ty, options))
.collect();
let output = transform_ty(tcx, fn_sig.skip_binder().output(), options);
ty = Ty::new_fn_ptr(
tcx,
ty::Binder::bind_with_vars(
tcx.mk_fn_sig(
parameters,
output,
fn_sig.c_variadic(),
fn_sig.unsafety(),
fn_sig.abi(),
),
fn_sig.bound_vars(),
),
);
}
}
ty::Dynamic(predicates, _region, kind) => {
ty = Ty::new_dynamic(
tcx,
transform_predicates(tcx, predicates, options),
tcx.lifetimes.re_erased,
*kind,
);
}
ty::Alias(..) => {
ty = transform_ty(
tcx,
tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), ty),
options,
);
}
ty::Bound(..) | ty::Error(..) | ty::Infer(..) | ty::Param(..) | ty::Placeholder(..) => {
bug!("transform_ty: unexpected `{:?}`", ty.kind());
}
}
ty
}
/// Returns a type metadata identifier for the specified FnAbi using the Itanium C++ ABI with vendor
/// extended type qualifiers and types for Rust types that are not used at the FFI boundary.
#[instrument(level = "trace", skip(tcx))]
pub fn typeid_for_fnabi<'tcx>(
tcx: TyCtxt<'tcx>,
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
options: TypeIdOptions,
) -> String {
// A name is mangled by prefixing "_Z" to an encoding of its name, and in the case of functions
// its type.
let mut typeid = String::from("_Z");
// Clang uses the Itanium C++ ABI's virtual tables and RTTI typeinfo structure name as type
// metadata identifiers for function pointers. The typeinfo name encoding is a two-character
// code (i.e., 'TS') prefixed to the type encoding for the function.
typeid.push_str("TS");
// Function types are delimited by an "F..E" pair
typeid.push('F');
// A dictionary of substitution candidates used for compression (see
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-compression).
let mut dict: FxHashMap<DictKey<'tcx>, usize> = FxHashMap::default();
let mut encode_ty_options = EncodeTyOptions::from_bits(options.bits())
.unwrap_or_else(|| bug!("typeid_for_fnabi: invalid option(s) `{:?}`", options.bits()));
match fn_abi.conv {
Conv::C => {
encode_ty_options.insert(EncodeTyOptions::GENERALIZE_REPR_C);
}
_ => {
encode_ty_options.remove(EncodeTyOptions::GENERALIZE_REPR_C);
}
}
// Encode the return type
let transform_ty_options = TransformTyOptions::from_bits(options.bits())
.unwrap_or_else(|| bug!("typeid_for_fnabi: invalid option(s) `{:?}`", options.bits()));
let ty = transform_ty(tcx, fn_abi.ret.layout.ty, transform_ty_options);
typeid.push_str(&encode_ty(tcx, ty, &mut dict, encode_ty_options));
// Encode the parameter types
if !fn_abi.c_variadic {
if !fn_abi.args.is_empty() {
for arg in fn_abi.args.iter() {
let ty = transform_ty(tcx, arg.layout.ty, transform_ty_options);
typeid.push_str(&encode_ty(tcx, ty, &mut dict, encode_ty_options));
}
} else {
// Empty parameter lists, whether declared as () or conventionally as (void), are
// encoded with a void parameter specifier "v".
typeid.push('v');
}
} else {
for n in 0..fn_abi.fixed_count as usize {
let ty = transform_ty(tcx, fn_abi.args[n].layout.ty, transform_ty_options);
typeid.push_str(&encode_ty(tcx, ty, &mut dict, encode_ty_options));
}
typeid.push('z');
}
// Close the "F..E" pair
typeid.push('E');
// Add encoding suffixes
if options.contains(EncodeTyOptions::NORMALIZE_INTEGERS) {
typeid.push_str(".normalized");
}
if options.contains(EncodeTyOptions::GENERALIZE_POINTERS) {
typeid.push_str(".generalized");
}
typeid
}
/// Returns a type metadata identifier for the specified FnSig using the Itanium C++ ABI with vendor
/// extended type qualifiers and types for Rust types that are not used at the FFI boundary.
pub fn typeid_for_fnsig<'tcx>(
tcx: TyCtxt<'tcx>,
fn_sig: &FnSig<'tcx>,
options: TypeIdOptions,
) -> String {
// A name is mangled by prefixing "_Z" to an encoding of its name, and in the case of functions
// its type.
let mut typeid = String::from("_Z");
// Clang uses the Itanium C++ ABI's virtual tables and RTTI typeinfo structure name as type
// metadata identifiers for function pointers. The typeinfo name encoding is a two-character
// code (i.e., 'TS') prefixed to the type encoding for the function.
typeid.push_str("TS");
// A dictionary of substitution candidates used for compression (see
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-compression).
let mut dict: FxHashMap<DictKey<'tcx>, usize> = FxHashMap::default();
// Encode the function signature
typeid.push_str(&encode_fnsig(tcx, fn_sig, &mut dict, options));
// Add encoding suffixes
if options.contains(EncodeTyOptions::NORMALIZE_INTEGERS) {
typeid.push_str(".normalized");
}
if options.contains(EncodeTyOptions::GENERALIZE_POINTERS) {
typeid.push_str(".generalized");
}
typeid
}
/// Returns a type metadata identifier for the specified Instance using the Itanium C++ ABI with
/// vendor extended type qualifiers and types for Rust types that are not used at the FFI boundary.
pub fn typeid_for_instance<'tcx>(
tcx: TyCtxt<'tcx>,
instance: &Instance<'tcx>,
options: TypeIdOptions,
) -> String {
let fn_abi = tcx
.fn_abi_of_instance(tcx.param_env(instance.def_id()).and((*instance, ty::List::empty())))
.unwrap_or_else(|instance| {
bug!("typeid_for_instance: couldn't get fn_abi of instance {:?}", instance)
});
// If this instance is a method and self is a reference, get the impl it belongs to
let impl_def_id = tcx.impl_of_method(instance.def_id());
if impl_def_id.is_some() && !fn_abi.args.is_empty() && fn_abi.args[0].layout.ty.is_ref() {
// If this impl is not an inherent impl, get the trait it implements
if let Some(trait_ref) = tcx.impl_trait_ref(impl_def_id.unwrap()) {
// Transform the concrete self into a reference to a trait object
let existential_predicate = trait_ref.map_bound(|trait_ref| {
ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty(
tcx, trait_ref,
))
});
let existential_predicates = tcx.mk_poly_existential_predicates(&[ty::Binder::dummy(
existential_predicate.skip_binder(),
)]);
// Is the concrete self mutable?
let self_ty = if fn_abi.args[0].layout.ty.is_mutable_ptr() {
Ty::new_mut_ref(
tcx,
tcx.lifetimes.re_erased,
Ty::new_dynamic(tcx, existential_predicates, tcx.lifetimes.re_erased, ty::Dyn),
)
} else {
Ty::new_imm_ref(
tcx,
tcx.lifetimes.re_erased,
Ty::new_dynamic(tcx, existential_predicates, tcx.lifetimes.re_erased, ty::Dyn),
)
};
// Replace the concrete self in an fn_abi clone by the reference to a trait object
let mut fn_abi = fn_abi.clone();
// HACK(rcvalle): It is okay to not replace or update the entire ArgAbi here because the
// other fields are never used.
fn_abi.args[0].layout.ty = self_ty;
return typeid_for_fnabi(tcx, &fn_abi, options);
}
}
typeid_for_fnabi(tcx, &fn_abi, options)
}