1 //! Handling of enum discriminants
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>)
6 use rustc_target::abi::{Int, TagEncoding, Variants};
10 pub(crate) fn codegen_set_discriminant<'tcx>(
11 fx: &mut FunctionCx<'_, '_, 'tcx>,
13 variant_index: VariantIdx,
15 let layout = place.layout();
16 if layout.for_variant(fx, variant_index).abi.is_uninhabited() {
19 match layout.variants {
20 Variants::Single { index } => {
21 assert_eq!(index, variant_index);
26 tag_encoding: TagEncoding::Direct,
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,
38 ty::ScalarInt::try_from_uint(to, ptr.layout().size).unwrap()
40 let discr = CValue::const_val(fx, ptr.layout(), to);
41 ptr.write_cvalue(fx, discr);
46 tag_encoding: TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start },
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 {
56 let lsb = fx.bcx.ins().iconst(types::I64, niche_value as u64 as i64);
58 fx.bcx.ins().iconst(types::I64, (niche_value >> 64) as u64 as i64);
59 fx.bcx.ins().iconcat(lsb, msb)
61 ty => fx.bcx.ins().iconst(ty, niche_value as i64),
63 let niche_llval = CValue::by_val(niche_value, niche.layout());
64 niche.write_cvalue(fx, niche_llval);
70 pub(crate) fn codegen_get_discriminant<'tcx>(
71 fx: &mut FunctionCx<'_, '_, 'tcx>,
74 dest_layout: TyAndLayout<'tcx>,
76 let layout = value.layout();
78 if layout.abi.is_uninhabited() {
82 let (tag_scalar, tag_field, tag_encoding) = match &layout.variants {
83 Variants::Single { index } => {
84 let discr_val = layout
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,
95 ty::ScalarInt::try_from_uint(discr_val, dest_layout.size).unwrap()
97 let res = CValue::const_val(fx, dest_layout, discr_val);
98 dest.write_cvalue(fx, res);
101 Variants::Multiple { tag, tag_field, tag_encoding, variants: _ } => {
102 (tag, *tag_field, tag_encoding)
106 let cast_to_size = dest_layout.layout.size();
107 let cast_to = fx.clif_type(dest_layout.ty).unwrap();
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);
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,
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);
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);
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)
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
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()
154 // However, we will likely be able to emit simpler code.
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)
163 (min_signed, max_signed)
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;
170 // In the algorithm above, we can change
171 // cast(relative_tag) + niche_variants.start()
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;
179 let data_for_boundary_niche = || -> Option<(IntCC, u128)> {
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))
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.
199 // is_niche = tag == niche_start
200 // discr = if is_niche {
205 let is_niche = codegen_icmp_imm(fx, IntCC::Equal, tag, niche_start as i128);
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
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))
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 {
230 let delta = match fx.bcx.func.dfg.value_type(tag) {
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)
236 ty => fx.bcx.ins().iconst(ty, delta as i64),
238 fx.bcx.ins().iadd(tag, delta)
241 let cast_tag = clif_intcast(fx, incr_tag, cast_to, !niches_ule);
243 (is_niche, cast_tag, 0)
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) {
249 let lsb = fx.bcx.ins().iconst(types::I64, niche_start as u64 as i64);
251 fx.bcx.ins().iconst(types::I64, (niche_start >> 64) as u64 as i64);
252 fx.bcx.ins().iconcat(lsb, msb)
254 ty => fx.bcx.ins().iconst(ty, niche_start as i64),
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(
260 IntCC::UnsignedLessThanOrEqual,
262 i128::from(relative_max),
264 (is_niche, cast_tag, niche_variants.start().as_u32() as u128)
267 let tagged_discr = if delta == 0 {
270 let delta = match cast_to {
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)
276 ty => fx.bcx.ins().iconst(ty, delta as i64),
278 fx.bcx.ins().iadd(tagged_discr, delta)
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)
287 fx.bcx.ins().iconst(cast_to, i64::from(untagged_variant.as_u32()))
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);