blob: 05017787a44685c5a743704b25e4a825e181428b [file]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#![feature(rustc_private)]
#![deny(rustc::internal)]
//! Query the rust compiler.
extern crate rustc_abi;
extern crate rustc_ast;
extern crate rustc_hir;
extern crate rustc_infer;
extern crate rustc_middle;
extern crate rustc_span;
extern crate rustc_trait_selection;
use arc_anyhow::Result;
use error_report::anyhow;
use rustc_abi::IntegerType;
use rustc_abi::{FieldIdx, FieldsShape, Integer, Layout, Primitive, Scalar, Variants};
use rustc_ast::ast::{IntTy as IntT, UintTy as UintT};
use rustc_hir::attrs::IntType;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::ty::{
self, GenericArg, GenericArgKind, GenericParamDefKind, IntTy, Region, Ty, TyCtxt, UintTy,
};
use rustc_span::def_id::DefId;
use rustc_span::symbol::Symbol;
use rustc_trait_selection::infer::InferCtxtExt;
use std::collections::HashMap;
use std::rc::Rc;
/// Whether functions using `extern "C"` ABI can safely handle values of type
/// `ty` (e.g. when passing by value arguments or return values of such type).
pub fn is_c_abi_compatible_by_value(ty: Ty) -> bool {
match ty.kind() {
// `improper_ctypes_definitions` warning doesn't complain about the following types:
ty::TyKind::Bool |
ty::TyKind::Float{..} |
ty::TyKind::Int{..} |
ty::TyKind::Uint{..} |
ty::TyKind::Never |
ty::TyKind::RawPtr{..} |
ty::TyKind::Ref{..} |
ty::TyKind::FnPtr{..} => true,
ty::TyKind::Tuple(types) if types.is_empty() => true,
// Crubit assumes that `char` is compatible with a certain `extern "C"` ABI.
// See `rust_builtin_type_abi_assumptions.md` for more details.
ty::TyKind::Char => true,
// TODO(b/271016831): When launching `&[T]` (not just `*const T`), consider returning
// `true` for `TyKind::Ref` and document the rationale for such decision - maybe
// something like this will be sufficient:
// - In general `TyKind::Ref` should have the same ABI as `TyKind::RawPtr`
// - References to slices (`&[T]`) or strings (`&str`) rely on assumptions
// spelled out in `rust_builtin_type_abi_assumptions.md`.
ty::TyKind::Slice{..} => false,
// Crubit's C++ bindings for tuples, structs, and other ADTs may not preserve
// their ABI (even if they *do* preserve their memory layout). For example:
// - In System V ABI replacing a field with a fixed-length array of bytes may affect
// whether the whole struct is classified as an integer and passed in general purpose
// registers VS classified as SSE2 and passed in floating-point registers like xmm0).
// See also b/270454629.
// - To replicate field offsets, Crubit may insert explicit padding fields. These
// extra fields may also impact the ABI of the generated bindings.
//
// TODO(lukasza): In the future, some additional performance gains may be realized by
// returning `true` in a few limited cases (this may require additional complexity to
// ensure that `generate_adt` never injects explicit padding into such structs):
// - `#[repr(C)]` structs and unions,
// - `#[repr(transparent)]` struct that wraps an ABI-safe type,
// - Discriminant-only enums (b/259984090).
ty::TyKind::Tuple{..} | // An empty tuple (`()` - the unit type) is handled above.
ty::TyKind::Adt{..} => false,
// These kinds of reference-related types are not implemented yet - `is_c_abi_compatible_by_value`
// should never need to handle them, because `format_ty_for_cc` fails for such types.
ty::TyKind::Str |
ty::TyKind::Array{..} => unimplemented!(),
// `format_ty_for_cc` is expected to fail for other kinds of types
// and therefore `is_c_abi_compatible_by_value` should never be called for
// these other types
_ => unimplemented!(),
}
}
/// Gets the exactly one region used in this function signature.
///
/// If the function has more than one region, or no regions, returns None.
pub fn count_regions<'tcx>(sig_mid: &ty::FnSig<'tcx>) -> HashMap<Region<'tcx>, u8> {
use rustc_middle::ty::TypeVisitor;
struct RegionCounter<'tcx>(HashMap<Region<'tcx>, u8>);
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for RegionCounter<'tcx> {
fn visit_region(&mut self, region: Region<'tcx>) {
let count = self.0.entry(region).or_default();
*count = count.saturating_add(1);
}
}
let mut visitor = RegionCounter(Default::default());
for ty in sig_mid.inputs() {
visitor.visit_ty(*ty);
}
visitor.visit_ty(sig_mid.output());
visitor.0
}
/// The prefix for deanonymized region names.
pub const ANON_REGION_PREFIX: &str = "'__anon";
/// Similar to `TyCtxt::liberate_and_name_late_bound_regions` but also replaces
/// anonymous regions with new names.
pub fn liberate_and_deanonymize_late_bound_regions<'tcx>(
tcx: TyCtxt<'tcx>,
sig: ty::PolyFnSig<'tcx>,
fn_def_id: DefId,
) -> ty::FnSig<'tcx> {
let mut anon_count: u32 = 0;
let mut translated_kinds: HashMap<ty::BoundVar, ty::BoundRegionKind> = HashMap::new();
let region_f = |br: ty::BoundRegion| {
let new_kind: &ty::BoundRegionKind = translated_kinds.entry(br.var).or_insert_with(|| {
if br.kind.is_named(tcx) {
let id = br.kind.get_id().unwrap_or(fn_def_id);
ty::BoundRegionKind::Named(id)
} else {
anon_count += 1;
let name = Symbol::intern(&format!("{ANON_REGION_PREFIX}{anon_count}"));
ty::BoundRegionKind::NamedAnon(name)
}
});
ty::Region::new_late_param(
tcx,
fn_def_id,
ty::LateParamRegionKind::from_bound(br.var, *new_kind),
)
};
tcx.instantiate_bound_regions_uncached(sig, region_f)
}
pub fn has_non_lifetime_generics<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
tcx.generics_of(def_id)
.own_params
.iter()
.any(|param| !matches!(param.kind, ty::GenericParamDefKind::Lifetime))
}
pub fn post_analysis_typing_env(tcx: TyCtxt, def_id: DefId) -> ty::TypingEnv {
ty::TypingEnv { typing_mode: ty::TypingMode::PostAnalysis, param_env: tcx.param_env(def_id) }
}
/// Returns whether `ty` is copyable inside the given environment (e.g. fn or type def).
pub fn is_copy<'tcx>(tcx: TyCtxt<'tcx>, environment_id: DefId, ty: Ty<'tcx>) -> bool {
// TODO(b/259749095): Once generic ADTs are supported, `is_copy_modulo_regions`
// might need to be replaced with a more thorough check - see
// b/258249993#comment4.
tcx.type_is_copy_modulo_regions(post_analysis_typing_env(tcx, environment_id), ty)
}
/// Like `TyCtxt::is_directly_public`, but works not only with `LocalDefId`, but
/// also with `DefId`.
pub fn is_directly_public(tcx: TyCtxt, def_id: DefId) -> bool {
match def_id.as_local() {
None => {
// This mimics the checks in `try_print_visible_def_path_recur` in
// `compiler/rustc_middle/src/ty/print/pretty.rs`.
let actual_parent = tcx.opt_parent(def_id);
let visible_parent = tcx.visible_parent_map(()).get(&def_id).copied();
actual_parent == visible_parent
}
Some(local_def_id) => tcx.effective_visibilities(()).is_directly_public(local_def_id),
}
}
/// Like `TyCtxt::is_exported`, but works not only with `LocalDefId`, but
/// also with `DefId`.
pub fn is_exported(tcx: TyCtxt, def_id: DefId) -> bool {
match def_id.as_local() {
None => {
// This mimics the checks in `try_print_visible_def_path_recur` in
// `compiler/rustc_middle/src/ty/print/pretty.rs`.
let actual_parent = tcx.opt_parent(def_id);
let visible_parent = tcx.visible_parent_map(()).get(&def_id).copied();
actual_parent == visible_parent
}
Some(local_def_id) => tcx.effective_visibilities(()).is_exported(local_def_id),
}
}
pub fn get_layout<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Result<Layout<'tcx>> {
tcx.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty))
.map(|ty_and_layout| ty_and_layout.layout)
.map_err(|layout_err| {
// Have to use `.map_err`, because `LayoutError` doesn't satisfy the
// `anyhow::context::ext::StdError` trait bound.
anyhow!("Error computing the layout: {layout_err}")
})
}
fn convert_interger_type_to_int_type(input: IntegerType) -> IntType {
match input {
IntegerType::Pointer(true) => IntType::SignedInt(IntT::Isize),
IntegerType::Pointer(false) => IntType::UnsignedInt(UintT::Usize),
IntegerType::Fixed(Integer::I8, false) => IntType::UnsignedInt(UintT::U8),
IntegerType::Fixed(Integer::I16, false) => IntType::UnsignedInt(UintT::U16),
IntegerType::Fixed(Integer::I32, false) => IntType::UnsignedInt(UintT::U32),
IntegerType::Fixed(Integer::I64, false) => IntType::UnsignedInt(UintT::U64),
IntegerType::Fixed(Integer::I128, false) => IntType::UnsignedInt(UintT::U128),
IntegerType::Fixed(Integer::I8, true) => IntType::SignedInt(IntT::I8),
IntegerType::Fixed(Integer::I16, true) => IntType::SignedInt(IntT::I16),
IntegerType::Fixed(Integer::I32, true) => IntType::SignedInt(IntT::I32),
IntegerType::Fixed(Integer::I64, true) => IntType::SignedInt(IntT::I64),
IntegerType::Fixed(Integer::I128, true) => IntType::SignedInt(IntT::I128),
}
}
/// Implementation of `BindingsGenerator::repr_attrs`.
pub fn repr_attrs(tcx: TyCtxt, def_id: DefId) -> Rc<[rustc_hir::attrs::ReprAttr]> {
let mut result = Vec::new();
let ty = tcx.type_of(def_id).instantiate_identity();
match ty.kind() {
ty::TyKind::Adt(adt_def, _) => {
let repr = adt_def.repr();
if repr.transparent() {
result.push(rustc_hir::attrs::ReprAttr::ReprTransparent);
}
if repr.c() {
result.push(rustc_hir::attrs::ReprAttr::ReprC);
}
if repr.simd() {
result.push(rustc_hir::attrs::ReprAttr::ReprSimd);
}
if let Some(alignment) = repr.align {
result.push(rustc_hir::attrs::ReprAttr::ReprAlign(alignment));
}
if let Some(alignment) = repr.pack {
result.push(rustc_hir::attrs::ReprAttr::ReprPacked(alignment));
}
if let Some(integer) = repr.int {
result.push(rustc_hir::attrs::ReprAttr::ReprInt(
convert_interger_type_to_int_type(integer),
));
}
if result.is_empty() {
result.push(rustc_hir::attrs::ReprAttr::ReprRust);
}
result.into()
}
_ => result.into(),
}
}
// Converts a scalar integer to a Ty.
// We assume the scalar represents an integer, and not a float or a pointer.
// https://doc.rust-lang.org/beta/nightly-rustc/rustc_abi/enum.Primitive.html
pub fn get_scalar_int_type<'tcx>(tcx: TyCtxt<'tcx>, scalar: Scalar) -> Ty<'tcx> {
match scalar.primitive() {
Primitive::Int(scalar_int, signed) => {
// Map the corresponding primitive to rust type.
match (scalar_int, signed) {
(Integer::I8, false) => Ty::new_uint(tcx, UintTy::U8),
(Integer::I16, false) => Ty::new_uint(tcx, UintTy::U16),
(Integer::I32, false) => Ty::new_uint(tcx, UintTy::U32),
(Integer::I64, false) => Ty::new_uint(tcx, UintTy::U64),
(Integer::I128, false) => Ty::new_uint(tcx, UintTy::U128),
(Integer::I8, true) => Ty::new_int(tcx, IntTy::I8),
(Integer::I16, true) => Ty::new_int(tcx, IntTy::I16),
(Integer::I32, true) => Ty::new_int(tcx, IntTy::I32),
(Integer::I64, true) => Ty::new_int(tcx, IntTy::I64),
(Integer::I128, true) => Ty::new_int(tcx, IntTy::I128),
}
}
_ => panic!("Internal error: integer scalar is not valid."),
}
}
// Accounts for the offset in the front of a repr(C) enum with multiple
// variants. If given a layout with a single variant, returns 0.
pub fn get_tag_size_with_padding(layout: Layout<'_>) -> u64 {
match layout.variants() {
Variants::Single { .. } | Variants::Empty => 0,
Variants::Multiple { tag: _, tag_encoding: _, tag_field: _, variants } => {
let mut variant_offsets = variants.iter().map(|variant| match &variant.fields {
FieldsShape::Arbitrary { offsets, .. } => {
if offsets.is_empty() {
variant.size.bytes() // No fields => variant is just the
// tag.
} else {
offsets[FieldIdx::from_usize(0)].bytes()
}
}
_ => panic!("Internal Error - Detected an enum with non-arbitrary field"),
});
// There are two equivalent ways to express a rust enum:
// 1. A struct that contains the discriminant and a union of the variants
// 2. A union where each field begins with a discriminant.
//
// Rust interally uses the second representation, and we extract out the
// discriminant to produce the first.
//
//
// See https://doc.rust-lang.org/beta/nightly-rustc/rustc_abi/enum.FieldsShape.html#variant.Arbitrary
// and https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields
let expected_offset = variant_offsets.next().expect("At least one variant is required");
for variant_offset in variant_offsets {
if variant_offset != expected_offset {
panic!("Internal Error - Detected an enum with different tag offsets.")
}
}
expected_offset
}
}
}
pub fn does_type_implement_trait<'tcx>(
tcx: TyCtxt<'tcx>,
self_ty: Ty<'tcx>,
trait_id: DefId,
generic_args: impl IntoIterator<Item = GenericArg<'tcx>>,
) -> bool {
assert!(tcx.is_trait(trait_id));
let generics = tcx.generics_of(trait_id);
assert!(generics.has_self);
// Self type must be first in our substitution.
let substs = std::iter::once(GenericArg::from(self_ty)).chain(generic_args).collect::<Vec<_>>();
// Assert we've provided the expected kind of args for each generic param.
// For example, we haven't passed a lifetime where a type is expected.
assert!(generics.own_params.len() == substs.len());
assert!(generics.own_params.iter().zip(substs.iter()).all(|(param, arg)| matches!(
(&param.kind, arg.kind()),
(GenericParamDefKind::Type { .. }, GenericArgKind::Type(_))
| (GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_))
| (GenericParamDefKind::Const { .. }, GenericArgKind::Const(_))
)));
use rustc_middle::ty::TypingMode;
tcx.infer_ctxt()
.build(TypingMode::non_body_analysis())
.type_implements_trait(trait_id, substs, tcx.param_env(trait_id))
.must_apply_modulo_regions()
}