]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_codegen_cranelift/src/discriminant.rs
Rollup merge of #101569 - m-ou-se:alloc-no-rexport-argumentv1, r=thomcc
[rust.git] / compiler / rustc_codegen_cranelift / src / discriminant.rs
1 //! Handling of enum discriminants
2 //!
3 //! Adapted from <https://github.com/rust-lang/rust/blob/31c0645b9d2539f47eecb096142474b29dc542f7/compiler/rustc_codegen_ssa/src/mir/place.rs>
4 //! (<https://github.com/rust-lang/rust/pull/104535>)
5
6 use rustc_target::abi::{Int, TagEncoding, Variants};
7
8 use crate::prelude::*;
9
10 pub(crate) fn codegen_set_discriminant<'tcx>(
11     fx: &mut FunctionCx<'_, '_, 'tcx>,
12     place: CPlace<'tcx>,
13     variant_index: VariantIdx,
14 ) {
15     let layout = place.layout();
16     if layout.for_variant(fx, variant_index).abi.is_uninhabited() {
17         return;
18     }
19     match layout.variants {
20         Variants::Single { index } => {
21             assert_eq!(index, variant_index);
22         }
23         Variants::Multiple {
24             tag: _,
25             tag_field,
26             tag_encoding: TagEncoding::Direct,
27             variants: _,
28         } => {
29             let ptr = place.place_field(fx, mir::Field::new(tag_field));
30             let to = layout.ty.discriminant_for_variant(fx.tcx, variant_index).unwrap().val;
31             let to = if ptr.layout().abi.is_signed() {
32                 ty::ScalarInt::try_from_int(
33                     ptr.layout().size.sign_extend(to) as i128,
34                     ptr.layout().size,
35                 )
36                 .unwrap()
37             } else {
38                 ty::ScalarInt::try_from_uint(to, ptr.layout().size).unwrap()
39             };
40             let discr = CValue::const_val(fx, ptr.layout(), to);
41             ptr.write_cvalue(fx, discr);
42         }
43         Variants::Multiple {
44             tag: _,
45             tag_field,
46             tag_encoding: TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start },
47             variants: _,
48         } => {
49             if variant_index != untagged_variant {
50                 let niche = place.place_field(fx, mir::Field::new(tag_field));
51                 let niche_type = fx.clif_type(niche.layout().ty).unwrap();
52                 let niche_value = variant_index.as_u32() - niche_variants.start().as_u32();
53                 let niche_value = (niche_value as u128).wrapping_add(niche_start);
54                 let niche_value = match niche_type {
55                     types::I128 => {
56                         let lsb = fx.bcx.ins().iconst(types::I64, niche_value as u64 as i64);
57                         let msb =
58                             fx.bcx.ins().iconst(types::I64, (niche_value >> 64) as u64 as i64);
59                         fx.bcx.ins().iconcat(lsb, msb)
60                     }
61                     ty => fx.bcx.ins().iconst(ty, niche_value as i64),
62                 };
63                 let niche_llval = CValue::by_val(niche_value, niche.layout());
64                 niche.write_cvalue(fx, niche_llval);
65             }
66         }
67     }
68 }
69
70 pub(crate) fn codegen_get_discriminant<'tcx>(
71     fx: &mut FunctionCx<'_, '_, 'tcx>,
72     dest: CPlace<'tcx>,
73     value: CValue<'tcx>,
74     dest_layout: TyAndLayout<'tcx>,
75 ) {
76     let layout = value.layout();
77
78     if layout.abi.is_uninhabited() {
79         return;
80     }
81
82     let (tag_scalar, tag_field, tag_encoding) = match &layout.variants {
83         Variants::Single { index } => {
84             let discr_val = layout
85                 .ty
86                 .discriminant_for_variant(fx.tcx, *index)
87                 .map_or(u128::from(index.as_u32()), |discr| discr.val);
88             let discr_val = if dest_layout.abi.is_signed() {
89                 ty::ScalarInt::try_from_int(
90                     dest_layout.size.sign_extend(discr_val) as i128,
91                     dest_layout.size,
92                 )
93                 .unwrap()
94             } else {
95                 ty::ScalarInt::try_from_uint(discr_val, dest_layout.size).unwrap()
96             };
97             let res = CValue::const_val(fx, dest_layout, discr_val);
98             dest.write_cvalue(fx, res);
99             return;
100         }
101         Variants::Multiple { tag, tag_field, tag_encoding, variants: _ } => {
102             (tag, *tag_field, tag_encoding)
103         }
104     };
105
106     let cast_to_size = dest_layout.layout.size();
107     let cast_to = fx.clif_type(dest_layout.ty).unwrap();
108
109     // Read the tag/niche-encoded discriminant from memory.
110     let tag = value.value_field(fx, mir::Field::new(tag_field));
111     let tag = tag.load_scalar(fx);
112
113     // Decode the discriminant (specifically if it's niche-encoded).
114     match *tag_encoding {
115         TagEncoding::Direct => {
116             let signed = match tag_scalar.primitive() {
117                 Int(_, signed) => signed,
118                 _ => false,
119             };
120             let val = clif_intcast(fx, tag, cast_to, signed);
121             let res = CValue::by_val(val, dest_layout);
122             dest.write_cvalue(fx, res);
123         }
124         TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start } => {
125             let tag_size = tag_scalar.size(fx);
126             let max_unsigned = tag_size.unsigned_int_max();
127             let max_signed = tag_size.signed_int_max() as u128;
128             let min_signed = max_signed + 1;
129             let relative_max = niche_variants.end().as_u32() - niche_variants.start().as_u32();
130             let niche_end = niche_start.wrapping_add(relative_max as u128) & max_unsigned;
131             let range = tag_scalar.valid_range(fx);
132
133             let sle = |lhs: u128, rhs: u128| -> bool {
134                 // Signed and unsigned comparisons give the same results,
135                 // except that in signed comparisons an integer with the
136                 // sign bit set is less than one with the sign bit clear.
137                 // Toggle the sign bit to do a signed comparison.
138                 (lhs ^ min_signed) <= (rhs ^ min_signed)
139             };
140
141             // We have a subrange `niche_start..=niche_end` inside `range`.
142             // If the value of the tag is inside this subrange, it's a
143             // "niche value", an increment of the discriminant. Otherwise it
144             // indicates the untagged variant.
145             // A general algorithm to extract the discriminant from the tag
146             // is:
147             // relative_tag = tag - niche_start
148             // is_niche = relative_tag <= (ule) relative_max
149             // discr = if is_niche {
150             //     cast(relative_tag) + niche_variants.start()
151             // } else {
152             //     untagged_variant
153             // }
154             // However, we will likely be able to emit simpler code.
155
156             // Find the least and greatest values in `range`, considered
157             // both as signed and unsigned.
158             let (low_unsigned, high_unsigned) =
159                 if range.start <= range.end { (range.start, range.end) } else { (0, max_unsigned) };
160             let (low_signed, high_signed) = if sle(range.start, range.end) {
161                 (range.start, range.end)
162             } else {
163                 (min_signed, max_signed)
164             };
165
166             let niches_ule = niche_start <= niche_end;
167             let niches_sle = sle(niche_start, niche_end);
168             let cast_smaller = cast_to_size <= tag_size;
169
170             // In the algorithm above, we can change
171             // cast(relative_tag) + niche_variants.start()
172             // into
173             // cast(tag + (niche_variants.start() - niche_start))
174             // if either the casted type is no larger than the original
175             // type, or if the niche values are contiguous (in either the
176             // signed or unsigned sense).
177             let can_incr = cast_smaller || niches_ule || niches_sle;
178
179             let data_for_boundary_niche = || -> Option<(IntCC, u128)> {
180                 if !can_incr {
181                     None
182                 } else if niche_start == low_unsigned {
183                     Some((IntCC::UnsignedLessThanOrEqual, niche_end))
184                 } else if niche_end == high_unsigned {
185                     Some((IntCC::UnsignedGreaterThanOrEqual, niche_start))
186                 } else if niche_start == low_signed {
187                     Some((IntCC::SignedLessThanOrEqual, niche_end))
188                 } else if niche_end == high_signed {
189                     Some((IntCC::SignedGreaterThanOrEqual, niche_start))
190                 } else {
191                     None
192                 }
193             };
194
195             let (is_niche, tagged_discr, delta) = if relative_max == 0 {
196                 // Best case scenario: only one tagged variant. This will
197                 // likely become just a comparison and a jump.
198                 // The algorithm is:
199                 // is_niche = tag == niche_start
200                 // discr = if is_niche {
201                 //     niche_start
202                 // } else {
203                 //     untagged_variant
204                 // }
205                 let is_niche = codegen_icmp_imm(fx, IntCC::Equal, tag, niche_start as i128);
206                 let tagged_discr =
207                     fx.bcx.ins().iconst(cast_to, niche_variants.start().as_u32() as i64);
208                 (is_niche, tagged_discr, 0)
209             } else if let Some((predicate, constant)) = data_for_boundary_niche() {
210                 // The niche values are either the lowest or the highest in
211                 // `range`. We can avoid the first subtraction in the
212                 // algorithm.
213                 // The algorithm is now this:
214                 // is_niche = tag <= niche_end
215                 // discr = if is_niche {
216                 //     cast(tag + (niche_variants.start() - niche_start))
217                 // } else {
218                 //     untagged_variant
219                 // }
220                 // (the first line may instead be tag >= niche_start,
221                 // and may be a signed or unsigned comparison)
222                 // The arithmetic must be done before the cast, so we can
223                 // have the correct wrapping behavior. See issue #104519 for
224                 // the consequences of getting this wrong.
225                 let is_niche = codegen_icmp_imm(fx, predicate, tag, constant as i128);
226                 let delta = (niche_variants.start().as_u32() as u128).wrapping_sub(niche_start);
227                 let incr_tag = if delta == 0 {
228                     tag
229                 } else {
230                     let delta = match fx.bcx.func.dfg.value_type(tag) {
231                         types::I128 => {
232                             let lsb = fx.bcx.ins().iconst(types::I64, delta as u64 as i64);
233                             let msb = fx.bcx.ins().iconst(types::I64, (delta >> 64) as u64 as i64);
234                             fx.bcx.ins().iconcat(lsb, msb)
235                         }
236                         ty => fx.bcx.ins().iconst(ty, delta as i64),
237                     };
238                     fx.bcx.ins().iadd(tag, delta)
239                 };
240
241                 let cast_tag = clif_intcast(fx, incr_tag, cast_to, !niches_ule);
242
243                 (is_niche, cast_tag, 0)
244             } else {
245                 // The special cases don't apply, so we'll have to go with
246                 // the general algorithm.
247                 let niche_start = match fx.bcx.func.dfg.value_type(tag) {
248                     types::I128 => {
249                         let lsb = fx.bcx.ins().iconst(types::I64, niche_start as u64 as i64);
250                         let msb =
251                             fx.bcx.ins().iconst(types::I64, (niche_start >> 64) as u64 as i64);
252                         fx.bcx.ins().iconcat(lsb, msb)
253                     }
254                     ty => fx.bcx.ins().iconst(ty, niche_start as i64),
255                 };
256                 let relative_discr = fx.bcx.ins().isub(tag, niche_start);
257                 let cast_tag = clif_intcast(fx, relative_discr, cast_to, false);
258                 let is_niche = crate::common::codegen_icmp_imm(
259                     fx,
260                     IntCC::UnsignedLessThanOrEqual,
261                     relative_discr,
262                     i128::from(relative_max),
263                 );
264                 (is_niche, cast_tag, niche_variants.start().as_u32() as u128)
265             };
266
267             let tagged_discr = if delta == 0 {
268                 tagged_discr
269             } else {
270                 let delta = match cast_to {
271                     types::I128 => {
272                         let lsb = fx.bcx.ins().iconst(types::I64, delta as u64 as i64);
273                         let msb = fx.bcx.ins().iconst(types::I64, (delta >> 64) as u64 as i64);
274                         fx.bcx.ins().iconcat(lsb, msb)
275                     }
276                     ty => fx.bcx.ins().iconst(ty, delta as i64),
277                 };
278                 fx.bcx.ins().iadd(tagged_discr, delta)
279             };
280
281             let untagged_variant = if cast_to == types::I128 {
282                 let zero = fx.bcx.ins().iconst(types::I64, 0);
283                 let untagged_variant =
284                     fx.bcx.ins().iconst(types::I64, i64::from(untagged_variant.as_u32()));
285                 fx.bcx.ins().iconcat(untagged_variant, zero)
286             } else {
287                 fx.bcx.ins().iconst(cast_to, i64::from(untagged_variant.as_u32()))
288             };
289             let discr = fx.bcx.ins().select(is_niche, tagged_discr, untagged_variant);
290             let res = CValue::by_val(discr, dest_layout);
291             dest.write_cvalue(fx, res);
292         }
293     }
294 }