-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Compute data layout of types #13490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,112
−164
Merged
Compute data layout of types #13490
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
//! Definitions needed for computing data layout of types. | ||
|
||
use std::cmp; | ||
|
||
use la_arena::{Idx, RawIdx}; | ||
pub use rustc_abi::{ | ||
Abi, AbiAndPrefAlign, AddressSpace, Align, Endian, FieldsShape, Integer, IntegerType, | ||
LayoutCalculator, Niche, Primitive, ReprFlags, ReprOptions, Scalar, Size, StructKind, | ||
TargetDataLayout, WrappingRange, | ||
}; | ||
|
||
use crate::LocalEnumVariantId; | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | ||
pub struct RustcEnumVariantIdx(pub LocalEnumVariantId); | ||
|
||
impl rustc_index::vec::Idx for RustcEnumVariantIdx { | ||
fn new(idx: usize) -> Self { | ||
RustcEnumVariantIdx(Idx::from_raw(RawIdx::from(idx as u32))) | ||
} | ||
|
||
fn index(self) -> usize { | ||
u32::from(self.0.into_raw()) as usize | ||
} | ||
} | ||
|
||
pub type Layout = rustc_abi::LayoutS<RustcEnumVariantIdx>; | ||
pub type TagEncoding = rustc_abi::TagEncoding<RustcEnumVariantIdx>; | ||
pub type Variants = rustc_abi::Variants<RustcEnumVariantIdx>; | ||
|
||
pub trait IntegerExt { | ||
fn repr_discr( | ||
dl: &TargetDataLayout, | ||
repr: &ReprOptions, | ||
min: i128, | ||
max: i128, | ||
) -> Result<(Integer, bool), LayoutError>; | ||
} | ||
|
||
impl IntegerExt for Integer { | ||
/// Finds the appropriate Integer type and signedness for the given | ||
/// signed discriminant range and `#[repr]` attribute. | ||
/// N.B.: `u128` values above `i128::MAX` will be treated as signed, but | ||
/// that shouldn't affect anything, other than maybe debuginfo. | ||
fn repr_discr( | ||
dl: &TargetDataLayout, | ||
repr: &ReprOptions, | ||
min: i128, | ||
max: i128, | ||
) -> Result<(Integer, bool), LayoutError> { | ||
// Theoretically, negative values could be larger in unsigned representation | ||
// than the unsigned representation of the signed minimum. However, if there | ||
// are any negative values, the only valid unsigned representation is u128 | ||
// which can fit all i128 values, so the result remains unaffected. | ||
let unsigned_fit = Integer::fit_unsigned(cmp::max(min as u128, max as u128)); | ||
let signed_fit = cmp::max(Integer::fit_signed(min), Integer::fit_signed(max)); | ||
|
||
if let Some(ity) = repr.int { | ||
let discr = Integer::from_attr(dl, ity); | ||
let fit = if ity.is_signed() { signed_fit } else { unsigned_fit }; | ||
if discr < fit { | ||
return Err(LayoutError::UserError( | ||
"Integer::repr_discr: `#[repr]` hint too small for \ | ||
discriminant range of enum " | ||
.to_string(), | ||
)); | ||
} | ||
return Ok((discr, ity.is_signed())); | ||
} | ||
|
||
let at_least = if repr.c() { | ||
// This is usually I32, however it can be different on some platforms, | ||
// notably hexagon and arm-none/thumb-none | ||
dl.c_enum_min_size | ||
} else { | ||
// repr(Rust) enums try to be as small as possible | ||
Integer::I8 | ||
}; | ||
|
||
// If there are no negative values, we can use the unsigned fit. | ||
Ok(if min >= 0 { | ||
(cmp::max(unsigned_fit, at_least), false) | ||
} else { | ||
(cmp::max(signed_fit, at_least), true) | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Debug, PartialEq, Eq, Clone)] | ||
pub enum LayoutError { | ||
UserError(String), | ||
SizeOverflow, | ||
HasPlaceholder, | ||
NotImplemented, | ||
Unknown, | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -419,6 +419,7 @@ pub mod known { | |
shr, | ||
sub_assign, | ||
sub, | ||
unsafe_cell, | ||
va_list | ||
); | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
//! Functions to detect special lang items | ||
use hir_def::{AdtId, HasModule}; | ||
use hir_expand::name; | ||
|
||
use crate::db::HirDatabase; | ||
|
||
pub fn is_box(adt: AdtId, db: &dyn HirDatabase) -> bool { | ||
let owned_box = name![owned_box].to_smol_str(); | ||
let krate = adt.module(db.upcast()).krate(); | ||
let box_adt = db.lang_item(krate, owned_box).and_then(|it| it.as_struct()).map(AdtId::from); | ||
Some(adt) == box_adt | ||
} | ||
|
||
pub fn is_unsafe_cell(adt: AdtId, db: &dyn HirDatabase) -> bool { | ||
let owned_box = name![unsafe_cell].to_smol_str(); | ||
let krate = adt.module(db.upcast()).krate(); | ||
let box_adt = db.lang_item(krate, owned_box).and_then(|it| it.as_struct()).map(AdtId::from); | ||
Some(adt) == box_adt | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
//! Compute the binary representation of a type | ||
use std::sync::Arc; | ||
|
||
use chalk_ir::{AdtId, TyKind}; | ||
pub(self) use hir_def::layout::*; | ||
use hir_def::LocalFieldId; | ||
use stdx::never; | ||
|
||
use crate::{db::HirDatabase, Interner, Substitution, Ty}; | ||
|
||
use self::adt::struct_variant_idx; | ||
pub use self::{ | ||
adt::{layout_of_adt_query, layout_of_adt_recover}, | ||
target::current_target_data_layout_query, | ||
}; | ||
|
||
macro_rules! user_error { | ||
($x: expr) => { | ||
return Err(LayoutError::UserError(format!($x))) | ||
}; | ||
} | ||
|
||
mod adt; | ||
mod target; | ||
|
||
struct LayoutCx<'a> { | ||
db: &'a dyn HirDatabase, | ||
} | ||
|
||
impl LayoutCalculator for LayoutCx<'_> { | ||
type TargetDataLayoutRef = Arc<TargetDataLayout>; | ||
|
||
fn delay_bug(&self, txt: &str) { | ||
never!("{}", txt); | ||
} | ||
|
||
fn current_data_layout(&self) -> Arc<TargetDataLayout> { | ||
self.db.current_target_data_layout() | ||
} | ||
} | ||
|
||
fn scalar_unit(dl: &TargetDataLayout, value: Primitive) -> Scalar { | ||
Scalar::Initialized { value, valid_range: WrappingRange::full(value.size(dl)) } | ||
} | ||
|
||
fn scalar(dl: &TargetDataLayout, value: Primitive) -> Layout { | ||
Layout::scalar(dl, scalar_unit(dl, value)) | ||
} | ||
|
||
pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty) -> Result<Layout, LayoutError> { | ||
let dl = &*db.current_target_data_layout(); | ||
let cx = LayoutCx { db }; | ||
Ok(match ty.kind(Interner) { | ||
TyKind::Adt(AdtId(def), subst) => db.layout_of_adt(*def, subst.clone())?, | ||
TyKind::Scalar(s) => match s { | ||
chalk_ir::Scalar::Bool => Layout::scalar( | ||
dl, | ||
Scalar::Initialized { | ||
value: Primitive::Int(Integer::I8, false), | ||
valid_range: WrappingRange { start: 0, end: 1 }, | ||
}, | ||
), | ||
chalk_ir::Scalar::Char => Layout::scalar( | ||
dl, | ||
Scalar::Initialized { | ||
value: Primitive::Int(Integer::I32, false), | ||
valid_range: WrappingRange { start: 0, end: 0x10FFFF }, | ||
}, | ||
), | ||
chalk_ir::Scalar::Int(i) => scalar( | ||
dl, | ||
Primitive::Int( | ||
match i { | ||
chalk_ir::IntTy::Isize => dl.ptr_sized_integer(), | ||
chalk_ir::IntTy::I8 => Integer::I8, | ||
chalk_ir::IntTy::I16 => Integer::I16, | ||
chalk_ir::IntTy::I32 => Integer::I32, | ||
chalk_ir::IntTy::I64 => Integer::I64, | ||
chalk_ir::IntTy::I128 => Integer::I128, | ||
}, | ||
false, | ||
), | ||
), | ||
chalk_ir::Scalar::Uint(i) => scalar( | ||
dl, | ||
Primitive::Int( | ||
match i { | ||
chalk_ir::UintTy::Usize => dl.ptr_sized_integer(), | ||
chalk_ir::UintTy::U8 => Integer::I8, | ||
chalk_ir::UintTy::U16 => Integer::I16, | ||
chalk_ir::UintTy::U32 => Integer::I32, | ||
chalk_ir::UintTy::U64 => Integer::I64, | ||
chalk_ir::UintTy::U128 => Integer::I128, | ||
}, | ||
true, | ||
), | ||
), | ||
chalk_ir::Scalar::Float(f) => scalar( | ||
dl, | ||
match f { | ||
chalk_ir::FloatTy::F32 => Primitive::F32, | ||
chalk_ir::FloatTy::F64 => Primitive::F64, | ||
}, | ||
), | ||
}, | ||
TyKind::Tuple(len, tys) => { | ||
let kind = if *len == 0 { StructKind::AlwaysSized } else { StructKind::MaybeUnsized }; | ||
|
||
let fields = tys | ||
.iter(Interner) | ||
.map(|k| layout_of_ty(db, k.assert_ty_ref(Interner))) | ||
.collect::<Result<Vec<_>, _>>()?; | ||
let fields = fields.iter().collect::<Vec<_>>(); | ||
let fields = fields.iter().collect::<Vec<_>>(); | ||
cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)? | ||
} | ||
TyKind::Array(element, count) => { | ||
let count = match count.data(Interner).value { | ||
chalk_ir::ConstValue::Concrete(c) => match c.interned { | ||
hir_def::type_ref::ConstScalar::Int(x) => x as u64, | ||
hir_def::type_ref::ConstScalar::UInt(x) => x as u64, | ||
hir_def::type_ref::ConstScalar::Unknown => { | ||
user_error!("unknown const generic parameter") | ||
} | ||
_ => user_error!("mismatched type of const generic parameter"), | ||
}, | ||
_ => return Err(LayoutError::HasPlaceholder), | ||
}; | ||
let element = layout_of_ty(db, element)?; | ||
let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?; | ||
|
||
let abi = if count != 0 && matches!(element.abi, Abi::Uninhabited) { | ||
Abi::Uninhabited | ||
} else { | ||
Abi::Aggregate { sized: true } | ||
}; | ||
|
||
let largest_niche = if count != 0 { element.largest_niche } else { None }; | ||
|
||
Layout { | ||
variants: Variants::Single { index: struct_variant_idx() }, | ||
fields: FieldsShape::Array { stride: element.size, count }, | ||
abi, | ||
largest_niche, | ||
align: element.align, | ||
size, | ||
} | ||
} | ||
TyKind::Slice(element) => { | ||
let element = layout_of_ty(db, element)?; | ||
Layout { | ||
variants: Variants::Single { index: struct_variant_idx() }, | ||
fields: FieldsShape::Array { stride: element.size, count: 0 }, | ||
abi: Abi::Aggregate { sized: false }, | ||
largest_niche: None, | ||
align: element.align, | ||
size: Size::ZERO, | ||
} | ||
} | ||
// Potentially-wide pointers. | ||
TyKind::Ref(_, _, pointee) | TyKind::Raw(_, pointee) => { | ||
let mut data_ptr = scalar_unit(dl, Primitive::Pointer); | ||
if matches!(ty.kind(Interner), TyKind::Ref(..)) { | ||
data_ptr.valid_range_mut().start = 1; | ||
} | ||
|
||
// let pointee = tcx.normalize_erasing_regions(param_env, pointee); | ||
// if pointee.is_sized(tcx.at(DUMMY_SP), param_env) { | ||
// return Ok(tcx.intern_layout(LayoutS::scalar(cx, data_ptr))); | ||
// } | ||
|
||
let unsized_part = struct_tail_erasing_lifetimes(db, pointee.clone()); | ||
let metadata = match unsized_part.kind(Interner) { | ||
TyKind::Slice(_) | TyKind::Str => { | ||
scalar_unit(dl, Primitive::Int(dl.ptr_sized_integer(), false)) | ||
} | ||
TyKind::Dyn(..) => { | ||
let mut vtable = scalar_unit(dl, Primitive::Pointer); | ||
vtable.valid_range_mut().start = 1; | ||
vtable | ||
} | ||
_ => { | ||
// pointee is sized | ||
return Ok(Layout::scalar(dl, data_ptr)); | ||
} | ||
}; | ||
|
||
// Effectively a (ptr, meta) tuple. | ||
cx.scalar_pair(data_ptr, metadata) | ||
} | ||
TyKind::FnDef(_, _) => layout_of_unit(&cx, dl)?, | ||
TyKind::Str => Layout { | ||
variants: Variants::Single { index: struct_variant_idx() }, | ||
fields: FieldsShape::Array { stride: Size::from_bytes(1), count: 0 }, | ||
abi: Abi::Aggregate { sized: false }, | ||
largest_niche: None, | ||
align: dl.i8_align, | ||
size: Size::ZERO, | ||
}, | ||
TyKind::Never => Layout { | ||
variants: Variants::Single { index: struct_variant_idx() }, | ||
fields: FieldsShape::Primitive, | ||
abi: Abi::Uninhabited, | ||
largest_niche: None, | ||
align: dl.i8_align, | ||
size: Size::ZERO, | ||
}, | ||
TyKind::Dyn(_) | TyKind::Foreign(_) => { | ||
let mut unit = layout_of_unit(&cx, dl)?; | ||
match unit.abi { | ||
Abi::Aggregate { ref mut sized } => *sized = false, | ||
_ => user_error!("bug"), | ||
} | ||
unit | ||
} | ||
TyKind::Function(_) => { | ||
let mut ptr = scalar_unit(dl, Primitive::Pointer); | ||
ptr.valid_range_mut().start = 1; | ||
Layout::scalar(dl, ptr) | ||
} | ||
TyKind::Closure(_, _) | ||
| TyKind::OpaqueType(_, _) | ||
| TyKind::Generator(_, _) | ||
| TyKind::GeneratorWitness(_, _) => return Err(LayoutError::NotImplemented), | ||
TyKind::AssociatedType(_, _) | ||
| TyKind::Error | ||
| TyKind::Alias(_) | ||
| TyKind::Placeholder(_) | ||
| TyKind::BoundVar(_) | ||
| TyKind::InferenceVar(_, _) => return Err(LayoutError::HasPlaceholder), | ||
}) | ||
} | ||
|
||
fn layout_of_unit(cx: &LayoutCx<'_>, dl: &TargetDataLayout) -> Result<Layout, LayoutError> { | ||
cx.univariant::<RustcEnumVariantIdx, &&Layout>( | ||
&dl, | ||
&[], | ||
&ReprOptions::default(), | ||
StructKind::AlwaysSized, | ||
) | ||
.ok_or(LayoutError::Unknown) | ||
} | ||
|
||
fn struct_tail_erasing_lifetimes(db: &dyn HirDatabase, pointee: Ty) -> Ty { | ||
match pointee.kind(Interner) { | ||
TyKind::Adt(AdtId(adt), subst) => match adt { | ||
&hir_def::AdtId::StructId(i) => { | ||
let data = db.struct_data(i); | ||
let mut it = data.variant_data.fields().iter().rev(); | ||
match it.next() { | ||
Some((f, _)) => field_ty(db, i.into(), f, subst), | ||
None => pointee, | ||
} | ||
} | ||
_ => pointee, | ||
}, | ||
_ => pointee, | ||
} | ||
} | ||
|
||
fn field_ty( | ||
db: &dyn HirDatabase, | ||
def: hir_def::VariantId, | ||
fd: LocalFieldId, | ||
subst: &Substitution, | ||
) -> Ty { | ||
db.field_types(def)[fd].clone().substitute(Interner, subst) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
//! Compute the binary representation of structs, unions and enums | ||
use std::ops::Bound; | ||
|
||
use hir_def::{ | ||
adt::VariantData, | ||
layout::{Integer, IntegerExt, Layout, LayoutCalculator, LayoutError, RustcEnumVariantIdx}, | ||
AdtId, EnumVariantId, LocalEnumVariantId, VariantId, | ||
}; | ||
use la_arena::RawIdx; | ||
use rustc_index::vec::IndexVec; | ||
|
||
use crate::{db::HirDatabase, lang_items::is_unsafe_cell, layout::field_ty, Substitution}; | ||
|
||
use super::{layout_of_ty, LayoutCx}; | ||
|
||
pub(crate) fn struct_variant_idx() -> RustcEnumVariantIdx { | ||
RustcEnumVariantIdx(LocalEnumVariantId::from_raw(RawIdx::from(0))) | ||
} | ||
|
||
pub fn layout_of_adt_query( | ||
db: &dyn HirDatabase, | ||
def: AdtId, | ||
subst: Substitution, | ||
) -> Result<Layout, LayoutError> { | ||
let dl = db.current_target_data_layout(); | ||
let cx = LayoutCx { db }; | ||
let handle_variant = |def: VariantId, var: &VariantData| { | ||
var.fields() | ||
.iter() | ||
.map(|(fd, _)| layout_of_ty(db, &field_ty(db, def, fd, &subst))) | ||
.collect::<Result<Vec<_>, _>>() | ||
}; | ||
let (variants, is_enum, is_union, repr) = match def { | ||
AdtId::StructId(s) => { | ||
let data = db.struct_data(s); | ||
let mut r = IndexVec::new(); | ||
r.push(handle_variant(s.into(), &data.variant_data)?); | ||
(r, false, false, data.repr.unwrap_or_default()) | ||
} | ||
AdtId::UnionId(id) => { | ||
let data = db.union_data(id); | ||
let mut r = IndexVec::new(); | ||
r.push(handle_variant(id.into(), &data.variant_data)?); | ||
(r, false, true, data.repr.unwrap_or_default()) | ||
} | ||
AdtId::EnumId(e) => { | ||
let data = db.enum_data(e); | ||
let r = data | ||
.variants | ||
.iter() | ||
.map(|(idx, v)| { | ||
handle_variant( | ||
EnumVariantId { parent: e, local_id: idx }.into(), | ||
&v.variant_data, | ||
) | ||
}) | ||
.collect::<Result<IndexVec<RustcEnumVariantIdx, _>, _>>()?; | ||
(r, true, false, data.repr.unwrap_or_default()) | ||
} | ||
}; | ||
let variants = variants.iter().map(|x| x.iter().collect::<Vec<_>>()).collect::<Vec<_>>(); | ||
let variants = variants.iter().map(|x| x.iter().collect()).collect(); | ||
if is_union { | ||
cx.layout_of_union(&repr, &variants).ok_or(LayoutError::Unknown) | ||
} else { | ||
cx.layout_of_struct_or_enum( | ||
&repr, | ||
&variants, | ||
is_enum, | ||
is_unsafe_cell(def, db), | ||
layout_scalar_valid_range(db, def), | ||
|min, max| Integer::repr_discr(&dl, &repr, min, max).unwrap_or((Integer::I8, false)), | ||
variants.iter_enumerated().filter_map(|(id, _)| { | ||
let AdtId::EnumId(e) = def else { return None }; | ||
let d = match db | ||
.const_eval_variant(EnumVariantId { parent: e, local_id: id.0 }) | ||
.ok()? | ||
{ | ||
crate::consteval::ComputedExpr::Literal(l) => match l { | ||
hir_def::expr::Literal::Int(i, _) => i, | ||
hir_def::expr::Literal::Uint(i, _) => i as i128, | ||
_ => return None, | ||
}, | ||
_ => return None, | ||
}; | ||
Some((id, d)) | ||
}), | ||
// FIXME: The current code for niche-filling relies on variant indices | ||
// instead of actual discriminants, so enums with | ||
// explicit discriminants (RFC #2363) would misbehave and we should disable | ||
// niche optimization for them. | ||
// The code that do it in rustc: | ||
// repr.inhibit_enum_layout_opt() || def | ||
// .variants() | ||
// .iter_enumerated() | ||
// .any(|(i, v)| v.discr != ty::VariantDiscr::Relative(i.as_u32())) | ||
repr.inhibit_enum_layout_opt(), | ||
!is_enum | ||
&& variants | ||
.iter() | ||
.next() | ||
.and_then(|x| x.last().map(|x| x.is_unsized())) | ||
.unwrap_or(true), | ||
) | ||
.ok_or(LayoutError::SizeOverflow) | ||
} | ||
} | ||
|
||
fn layout_scalar_valid_range(db: &dyn HirDatabase, def: AdtId) -> (Bound<u128>, Bound<u128>) { | ||
let attrs = db.attrs(def.into()); | ||
let get = |name| { | ||
let attr = attrs.by_key(name).tt_values(); | ||
for tree in attr { | ||
if let Some(x) = tree.token_trees.first() { | ||
if let Ok(x) = x.to_string().parse() { | ||
return Bound::Included(x); | ||
} | ||
} | ||
} | ||
Bound::Unbounded | ||
}; | ||
(get("rustc_layout_scalar_valid_range_start"), get("rustc_layout_scalar_valid_range_end")) | ||
} | ||
|
||
pub fn layout_of_adt_recover( | ||
_: &dyn HirDatabase, | ||
_: &[String], | ||
_: &AdtId, | ||
_: &Substitution, | ||
) -> Result<Layout, LayoutError> { | ||
user_error!("infinite sized recursive type"); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
//! Target dependent parameters needed for layouts | ||
use std::sync::Arc; | ||
|
||
use hir_def::layout::TargetDataLayout; | ||
|
||
use crate::db::HirDatabase; | ||
|
||
use super::{AbiAndPrefAlign, AddressSpace, Align, Endian, Integer, Size}; | ||
|
||
pub fn current_target_data_layout_query(db: &dyn HirDatabase) -> Arc<TargetDataLayout> { | ||
let crate_graph = db.crate_graph(); | ||
let cfg_options = &crate_graph[crate_graph.iter().next().unwrap()].cfg_options; | ||
let endian = match cfg_options.get_cfg_values("target_endian").next() { | ||
Some(x) if x.as_str() == "big" => Endian::Big, | ||
_ => Endian::Little, | ||
}; | ||
let pointer_size = | ||
Size::from_bytes(match cfg_options.get_cfg_values("target_pointer_width").next() { | ||
Some(x) => match x.as_str() { | ||
"16" => 2, | ||
"32" => 4, | ||
_ => 8, | ||
}, | ||
_ => 8, | ||
}); | ||
// FIXME: These values are incorrect for many architectures, at least for aarch64 and riscv64, | ||
// use `rustc +nightly -Z unstable-options --print target-spec-json` or something similar instead. | ||
Arc::new(TargetDataLayout { | ||
endian, | ||
i1_align: AbiAndPrefAlign::new(Align::from_bytes(1).unwrap()), | ||
i8_align: AbiAndPrefAlign::new(Align::from_bytes(1).unwrap()), | ||
i16_align: AbiAndPrefAlign::new(Align::from_bytes(2).unwrap()), | ||
i32_align: AbiAndPrefAlign::new(Align::from_bytes(4).unwrap()), | ||
i64_align: AbiAndPrefAlign::new(Align::from_bytes(8).unwrap()), | ||
i128_align: AbiAndPrefAlign::new(Align::from_bytes(8).unwrap()), | ||
f32_align: AbiAndPrefAlign::new(Align::from_bytes(4).unwrap()), | ||
f64_align: AbiAndPrefAlign::new(Align::from_bytes(8).unwrap()), | ||
pointer_size, | ||
pointer_align: AbiAndPrefAlign::new(Align::from_bytes(pointer_size.bytes()).unwrap()), | ||
aggregate_align: AbiAndPrefAlign::new(Align::from_bytes(1).unwrap()), | ||
vector_align: vec![], | ||
instruction_address_space: AddressSpace(0), | ||
c_enum_min_size: Integer::I32, | ||
}) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
use base_db::fixture::WithFixture; | ||
use chalk_ir::{AdtId, TyKind}; | ||
use hir_def::{ | ||
db::DefDatabase, | ||
layout::{Layout, LayoutError}, | ||
}; | ||
|
||
use crate::{test_db::TestDB, Interner, Substitution}; | ||
|
||
use super::layout_of_ty; | ||
|
||
fn eval_goal(ra_fixture: &str) -> Result<Layout, LayoutError> { | ||
let (db, file_id) = TestDB::with_single_file(ra_fixture); | ||
let module_id = db.module_for_file(file_id); | ||
let def_map = module_id.def_map(&db); | ||
let scope = &def_map[module_id.local_id].scope; | ||
let adt_id = scope | ||
.declarations() | ||
.into_iter() | ||
.find_map(|x| match x { | ||
hir_def::ModuleDefId::AdtId(x) => { | ||
let name = match x { | ||
hir_def::AdtId::StructId(x) => db.struct_data(x).name.to_string(), | ||
hir_def::AdtId::UnionId(x) => db.union_data(x).name.to_string(), | ||
hir_def::AdtId::EnumId(x) => db.enum_data(x).name.to_string(), | ||
}; | ||
if name == "Goal" { | ||
Some(x) | ||
} else { | ||
None | ||
} | ||
} | ||
_ => None, | ||
}) | ||
.unwrap(); | ||
let goal_ty = TyKind::Adt(AdtId(adt_id), Substitution::empty(Interner)).intern(Interner); | ||
layout_of_ty(&db, &goal_ty) | ||
} | ||
|
||
fn check_size_and_align(ra_fixture: &str, size: u64, align: u64) { | ||
let l = eval_goal(ra_fixture).unwrap(); | ||
assert_eq!(l.size.bytes(), size); | ||
assert_eq!(l.align.abi.bytes(), align); | ||
} | ||
|
||
fn check_fail(ra_fixture: &str, e: LayoutError) { | ||
let r = eval_goal(ra_fixture); | ||
assert_eq!(r, Err(e)); | ||
} | ||
|
||
macro_rules! size_and_align { | ||
(minicore: $($x:tt),*;$($t:tt)*) => { | ||
{ | ||
#[allow(dead_code)] | ||
$($t)* | ||
check_size_and_align( | ||
&format!("//- minicore: {}\n{}", stringify!($($x),*), stringify!($($t)*)), | ||
::std::mem::size_of::<Goal>() as u64, | ||
::std::mem::align_of::<Goal>() as u64, | ||
); | ||
} | ||
}; | ||
($($t:tt)*) => { | ||
{ | ||
#[allow(dead_code)] | ||
$($t)* | ||
check_size_and_align( | ||
stringify!($($t)*), | ||
::std::mem::size_of::<Goal>() as u64, | ||
::std::mem::align_of::<Goal>() as u64, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neat trick! This should even work when cross-compiling r-a. |
||
); | ||
} | ||
}; | ||
} | ||
|
||
#[test] | ||
fn hello_world() { | ||
size_and_align! { | ||
struct Goal(i32); | ||
} | ||
} | ||
|
||
#[test] | ||
fn field_order_optimization() { | ||
size_and_align! { | ||
struct Goal(u8, i32, u8); | ||
} | ||
size_and_align! { | ||
#[repr(C)] | ||
struct Goal(u8, i32, u8); | ||
} | ||
} | ||
|
||
#[test] | ||
fn recursive() { | ||
size_and_align! { | ||
struct Goal { | ||
left: &'static Goal, | ||
right: &'static Goal, | ||
} | ||
} | ||
size_and_align! { | ||
struct BoxLike<T: ?Sized>(*mut T); | ||
struct Goal(BoxLike<Goal>); | ||
} | ||
check_fail( | ||
r#"struct Goal(Goal);"#, | ||
LayoutError::UserError("infinite sized recursive type".to_string()), | ||
); | ||
check_fail( | ||
r#" | ||
struct Foo<T>(Foo<T>); | ||
struct Goal(Foo<i32>); | ||
"#, | ||
LayoutError::UserError("infinite sized recursive type".to_string()), | ||
); | ||
} | ||
|
||
#[test] | ||
fn generic() { | ||
size_and_align! { | ||
struct Pair<A, B>(A, B); | ||
struct Goal(Pair<Pair<i32, u8>, i64>); | ||
} | ||
size_and_align! { | ||
struct X<const N: usize> { | ||
field1: [i32; N], | ||
field2: [u8; N], | ||
} | ||
struct Goal(X<1000>); | ||
} | ||
} | ||
|
||
#[test] | ||
fn enums() { | ||
size_and_align! { | ||
enum Goal { | ||
Quit, | ||
Move { x: i32, y: i32 }, | ||
ChangeColor(i32, i32, i32), | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn primitives() { | ||
size_and_align! { | ||
struct Goal(i32, i128, isize, usize, f32, f64, bool, char); | ||
} | ||
} | ||
|
||
#[test] | ||
fn tuple() { | ||
size_and_align! { | ||
struct Goal((), (i32, u64, bool)); | ||
} | ||
} | ||
|
||
#[test] | ||
fn non_zero() { | ||
size_and_align! { | ||
minicore: non_zero, option; | ||
use core::num::NonZeroU8; | ||
struct Goal(Option<NonZeroU8>); | ||
} | ||
} | ||
|
||
#[test] | ||
fn niche_optimization() { | ||
size_and_align! { | ||
minicore: option; | ||
struct Goal(Option<&'static i32>); | ||
} | ||
size_and_align! { | ||
minicore: option; | ||
struct Goal(Option<Option<bool>>); | ||
} | ||
} | ||
|
||
#[test] | ||
fn enums_with_discriminants() { | ||
size_and_align! { | ||
enum Goal { | ||
A = 1000, | ||
B = 2000, | ||
C = 3000, | ||
} | ||
} | ||
size_and_align! { | ||
enum Goal { | ||
A = 254, | ||
B, | ||
C, // implicitly becomes 256, so we need two bytes | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the alignment of i64 and i128 are always 8 Bytes on all platforms. If there's no way to query the correct alignment from the cfg_options, can you add a FIXME comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a way:
rustc +nightly -Z unstable-options --print target-spec-json
. But it needs nightly. Is it possible to use it?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
rustc_cfg
code already tries to use nightly-only features, and falls back to stable-compatible behavior if those are unavailable or don't work, so this should be doable. However, I think just setting the align to 8 should be fine for now, we can follow up with a fix that asks rustc later.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least aarch64 and riscv64 use a 16 byte alignment for u/i128.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a fixme comment