• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Compute the binary representation of structs, unions and enums
2 
3 use std::{cmp, ops::Bound};
4 
5 use base_db::CrateId;
6 use hir_def::{
7     data::adt::VariantData,
8     layout::{Integer, LayoutCalculator, ReprOptions, TargetDataLayout},
9     AdtId, EnumVariantId, LocalEnumVariantId, VariantId,
10 };
11 use la_arena::RawIdx;
12 use smallvec::SmallVec;
13 use triomphe::Arc;
14 
15 use crate::{
16     db::HirDatabase,
17     lang_items::is_unsafe_cell,
18     layout::{field_ty, Layout, LayoutError, RustcEnumVariantIdx},
19     Substitution,
20 };
21 
22 use super::LayoutCx;
23 
struct_variant_idx() -> RustcEnumVariantIdx24 pub(crate) fn struct_variant_idx() -> RustcEnumVariantIdx {
25     RustcEnumVariantIdx(LocalEnumVariantId::from_raw(RawIdx::from(0)))
26 }
27 
layout_of_adt_query( db: &dyn HirDatabase, def: AdtId, subst: Substitution, krate: CrateId, ) -> Result<Arc<Layout>, LayoutError>28 pub fn layout_of_adt_query(
29     db: &dyn HirDatabase,
30     def: AdtId,
31     subst: Substitution,
32     krate: CrateId,
33 ) -> Result<Arc<Layout>, LayoutError> {
34     let Some(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable) };
35     let cx = LayoutCx { krate, target: &target };
36     let dl = cx.current_data_layout();
37     let handle_variant = |def: VariantId, var: &VariantData| {
38         var.fields()
39             .iter()
40             .map(|(fd, _)| db.layout_of_ty(field_ty(db, def, fd, &subst), cx.krate))
41             .collect::<Result<Vec<_>, _>>()
42     };
43     let (variants, repr) = match def {
44         AdtId::StructId(s) => {
45             let data = db.struct_data(s);
46             let mut r = SmallVec::<[_; 1]>::new();
47             r.push(handle_variant(s.into(), &data.variant_data)?);
48             (r, data.repr.unwrap_or_default())
49         }
50         AdtId::UnionId(id) => {
51             let data = db.union_data(id);
52             let mut r = SmallVec::new();
53             r.push(handle_variant(id.into(), &data.variant_data)?);
54             (r, data.repr.unwrap_or_default())
55         }
56         AdtId::EnumId(e) => {
57             let data = db.enum_data(e);
58             let r = data
59                 .variants
60                 .iter()
61                 .map(|(idx, v)| {
62                     handle_variant(
63                         EnumVariantId { parent: e, local_id: idx }.into(),
64                         &v.variant_data,
65                     )
66                 })
67                 .collect::<Result<SmallVec<_>, _>>()?;
68             (r, data.repr.unwrap_or_default())
69         }
70     };
71     let variants = variants
72         .iter()
73         .map(|x| x.iter().map(|x| &**x).collect::<Vec<_>>())
74         .collect::<SmallVec<[_; 1]>>();
75     let variants = variants.iter().map(|x| x.iter().collect()).collect();
76     let result = if matches!(def, AdtId::UnionId(..)) {
77         cx.layout_of_union(&repr, &variants).ok_or(LayoutError::Unknown)?
78     } else {
79         cx.layout_of_struct_or_enum(
80             &repr,
81             &variants,
82             matches!(def, AdtId::EnumId(..)),
83             is_unsafe_cell(db, def),
84             layout_scalar_valid_range(db, def),
85             |min, max| repr_discr(&dl, &repr, min, max).unwrap_or((Integer::I8, false)),
86             variants.iter_enumerated().filter_map(|(id, _)| {
87                 let AdtId::EnumId(e) = def else { return None };
88                 let d =
89                     db.const_eval_discriminant(EnumVariantId { parent: e, local_id: id.0 }).ok()?;
90                 Some((id, d))
91             }),
92             // FIXME: The current code for niche-filling relies on variant indices
93             // instead of actual discriminants, so enums with
94             // explicit discriminants (RFC #2363) would misbehave and we should disable
95             // niche optimization for them.
96             // The code that do it in rustc:
97             // repr.inhibit_enum_layout_opt() || def
98             //     .variants()
99             //     .iter_enumerated()
100             //     .any(|(i, v)| v.discr != ty::VariantDiscr::Relative(i.as_u32()))
101             repr.inhibit_enum_layout_opt(),
102             !matches!(def, AdtId::EnumId(..))
103                 && variants
104                     .iter()
105                     .next()
106                     .and_then(|x| x.last().map(|x| x.is_unsized()))
107                     .unwrap_or(true),
108         )
109         .ok_or(LayoutError::SizeOverflow)?
110     };
111     Ok(Arc::new(result))
112 }
113 
layout_scalar_valid_range(db: &dyn HirDatabase, def: AdtId) -> (Bound<u128>, Bound<u128>)114 fn layout_scalar_valid_range(db: &dyn HirDatabase, def: AdtId) -> (Bound<u128>, Bound<u128>) {
115     let attrs = db.attrs(def.into());
116     let get = |name| {
117         let attr = attrs.by_key(name).tt_values();
118         for tree in attr {
119             if let Some(x) = tree.token_trees.first() {
120                 if let Ok(x) = x.to_string().parse() {
121                     return Bound::Included(x);
122                 }
123             }
124         }
125         Bound::Unbounded
126     };
127     (get("rustc_layout_scalar_valid_range_start"), get("rustc_layout_scalar_valid_range_end"))
128 }
129 
layout_of_adt_recover( _: &dyn HirDatabase, _: &[String], _: &AdtId, _: &Substitution, _: &CrateId, ) -> Result<Arc<Layout>, LayoutError>130 pub fn layout_of_adt_recover(
131     _: &dyn HirDatabase,
132     _: &[String],
133     _: &AdtId,
134     _: &Substitution,
135     _: &CrateId,
136 ) -> Result<Arc<Layout>, LayoutError> {
137     user_error!("infinite sized recursive type");
138 }
139 
140 /// Finds the appropriate Integer type and signedness for the given
141 /// signed discriminant range and `#[repr]` attribute.
142 /// N.B.: `u128` values above `i128::MAX` will be treated as signed, but
143 /// that shouldn't affect anything, other than maybe debuginfo.
repr_discr( dl: &TargetDataLayout, repr: &ReprOptions, min: i128, max: i128, ) -> Result<(Integer, bool), LayoutError>144 fn repr_discr(
145     dl: &TargetDataLayout,
146     repr: &ReprOptions,
147     min: i128,
148     max: i128,
149 ) -> Result<(Integer, bool), LayoutError> {
150     // Theoretically, negative values could be larger in unsigned representation
151     // than the unsigned representation of the signed minimum. However, if there
152     // are any negative values, the only valid unsigned representation is u128
153     // which can fit all i128 values, so the result remains unaffected.
154     let unsigned_fit = Integer::fit_unsigned(cmp::max(min as u128, max as u128));
155     let signed_fit = cmp::max(Integer::fit_signed(min), Integer::fit_signed(max));
156 
157     if let Some(ity) = repr.int {
158         let discr = Integer::from_attr(dl, ity);
159         let fit = if ity.is_signed() { signed_fit } else { unsigned_fit };
160         if discr < fit {
161             return Err(LayoutError::UserError(
162                 "Integer::repr_discr: `#[repr]` hint too small for \
163                       discriminant range of enum "
164                     .to_string(),
165             ));
166         }
167         return Ok((discr, ity.is_signed()));
168     }
169 
170     let at_least = if repr.c() {
171         // This is usually I32, however it can be different on some platforms,
172         // notably hexagon and arm-none/thumb-none
173         dl.c_enum_min_size
174     } else {
175         // repr(Rust) enums try to be as small as possible
176         Integer::I8
177     };
178 
179     // If there are no negative values, we can use the unsigned fit.
180     Ok(if min >= 0 {
181         (cmp::max(unsigned_fit, at_least), false)
182     } else {
183         (cmp::max(signed_fit, at_least), true)
184     })
185 }
186