]> git.lizzy.rs Git - rust.git/blob - src/discriminant.rs
Move cached_context out of CodegenCx
[rust.git] / src / discriminant.rs
1 //! Handling of enum discriminants
2 //!
3 //! Adapted from <https://github.com/rust-lang/rust/blob/d760df5aea483aae041c9a241e7acacf48f75035/src/librustc_codegen_ssa/mir/place.rs>
4
5 use rustc_target::abi::{Int, TagEncoding, Variants};
6
7 use crate::prelude::*;
8
9 pub(crate) fn codegen_set_discriminant<'tcx>(
10     fx: &mut FunctionCx<'_, '_, 'tcx>,
11     place: CPlace<'tcx>,
12     variant_index: VariantIdx,
13 ) {
14     let layout = place.layout();
15     if layout.for_variant(fx, variant_index).abi.is_uninhabited() {
16         return;
17     }
18     match layout.variants {
19         Variants::Single { index } => {
20             assert_eq!(index, variant_index);
21         }
22         Variants::Multiple {
23             tag: _,
24             tag_field,
25             tag_encoding: TagEncoding::Direct,
26             variants: _,
27         } => {
28             let ptr = place.place_field(fx, mir::Field::new(tag_field));
29             let to = layout.ty.discriminant_for_variant(fx.tcx, variant_index).unwrap().val;
30             let to = if ptr.layout().abi.is_signed() {
31                 ty::ScalarInt::try_from_int(
32                     ptr.layout().size.sign_extend(to) as i128,
33                     ptr.layout().size,
34                 )
35                 .unwrap()
36             } else {
37                 ty::ScalarInt::try_from_uint(to, ptr.layout().size).unwrap()
38             };
39             let discr = CValue::const_val(fx, ptr.layout(), to);
40             ptr.write_cvalue(fx, discr);
41         }
42         Variants::Multiple {
43             tag: _,
44             tag_field,
45             tag_encoding: TagEncoding::Niche { dataful_variant, ref niche_variants, niche_start },
46             variants: _,
47         } => {
48             if variant_index != dataful_variant {
49                 let niche = place.place_field(fx, mir::Field::new(tag_field));
50                 let niche_value = variant_index.as_u32() - niche_variants.start().as_u32();
51                 let niche_value = ty::ScalarInt::try_from_uint(
52                     u128::from(niche_value).wrapping_add(niche_start),
53                     niche.layout().size,
54                 )
55                 .unwrap();
56                 let niche_llval = CValue::const_val(fx, niche.layout(), niche_value);
57                 niche.write_cvalue(fx, niche_llval);
58             }
59         }
60     }
61 }
62
63 pub(crate) fn codegen_get_discriminant<'tcx>(
64     fx: &mut FunctionCx<'_, '_, 'tcx>,
65     dest: CPlace<'tcx>,
66     value: CValue<'tcx>,
67     dest_layout: TyAndLayout<'tcx>,
68 ) {
69     let layout = value.layout();
70
71     if layout.abi.is_uninhabited() {
72         return;
73     }
74
75     let (tag_scalar, tag_field, tag_encoding) = match &layout.variants {
76         Variants::Single { index } => {
77             let discr_val = layout
78                 .ty
79                 .discriminant_for_variant(fx.tcx, *index)
80                 .map_or(u128::from(index.as_u32()), |discr| discr.val);
81             let discr_val = if dest_layout.abi.is_signed() {
82                 ty::ScalarInt::try_from_int(
83                     dest_layout.size.sign_extend(discr_val) as i128,
84                     dest_layout.size,
85                 )
86                 .unwrap()
87             } else {
88                 ty::ScalarInt::try_from_uint(discr_val, dest_layout.size).unwrap()
89             };
90             let res = CValue::const_val(fx, dest_layout, discr_val);
91             dest.write_cvalue(fx, res);
92             return;
93         }
94         Variants::Multiple { tag, tag_field, tag_encoding, variants: _ } => {
95             (tag, *tag_field, tag_encoding)
96         }
97     };
98
99     let cast_to = fx.clif_type(dest_layout.ty).unwrap();
100
101     // Read the tag/niche-encoded discriminant from memory.
102     let tag = value.value_field(fx, mir::Field::new(tag_field));
103     let tag = tag.load_scalar(fx);
104
105     // Decode the discriminant (specifically if it's niche-encoded).
106     match *tag_encoding {
107         TagEncoding::Direct => {
108             let signed = match tag_scalar.primitive() {
109                 Int(_, signed) => signed,
110                 _ => false,
111             };
112             let val = clif_intcast(fx, tag, cast_to, signed);
113             let res = CValue::by_val(val, dest_layout);
114             dest.write_cvalue(fx, res);
115         }
116         TagEncoding::Niche { dataful_variant, ref niche_variants, niche_start } => {
117             // Rebase from niche values to discriminants, and check
118             // whether the result is in range for the niche variants.
119
120             // We first compute the "relative discriminant" (wrt `niche_variants`),
121             // that is, if `n = niche_variants.end() - niche_variants.start()`,
122             // we remap `niche_start..=niche_start + n` (which may wrap around)
123             // to (non-wrap-around) `0..=n`, to be able to check whether the
124             // discriminant corresponds to a niche variant with one comparison.
125             // We also can't go directly to the (variant index) discriminant
126             // and check that it is in the range `niche_variants`, because
127             // that might not fit in the same type, on top of needing an extra
128             // comparison (see also the comment on `let niche_discr`).
129             let relative_discr = if niche_start == 0 {
130                 tag
131             } else {
132                 let niche_start = match fx.bcx.func.dfg.value_type(tag) {
133                     types::I128 => {
134                         let lsb = fx.bcx.ins().iconst(types::I64, niche_start as u64 as i64);
135                         let msb =
136                             fx.bcx.ins().iconst(types::I64, (niche_start >> 64) as u64 as i64);
137                         fx.bcx.ins().iconcat(lsb, msb)
138                     }
139                     ty => fx.bcx.ins().iconst(ty, niche_start as i64),
140                 };
141                 fx.bcx.ins().isub(tag, niche_start)
142             };
143             let relative_max = niche_variants.end().as_u32() - niche_variants.start().as_u32();
144             let is_niche = {
145                 codegen_icmp_imm(
146                     fx,
147                     IntCC::UnsignedLessThanOrEqual,
148                     relative_discr,
149                     i128::from(relative_max),
150                 )
151             };
152
153             // NOTE(eddyb) this addition needs to be performed on the final
154             // type, in case the niche itself can't represent all variant
155             // indices (e.g. `u8` niche with more than `256` variants,
156             // but enough uninhabited variants so that the remaining variants
157             // fit in the niche).
158             // In other words, `niche_variants.end - niche_variants.start`
159             // is representable in the niche, but `niche_variants.end`
160             // might not be, in extreme cases.
161             let niche_discr = {
162                 let relative_discr = if relative_max == 0 {
163                     // HACK(eddyb) since we have only one niche, we know which
164                     // one it is, and we can avoid having a dynamic value here.
165                     fx.bcx.ins().iconst(cast_to, 0)
166                 } else {
167                     clif_intcast(fx, relative_discr, cast_to, false)
168                 };
169                 fx.bcx.ins().iadd_imm(relative_discr, i64::from(niche_variants.start().as_u32()))
170             };
171
172             let dataful_variant = fx.bcx.ins().iconst(cast_to, i64::from(dataful_variant.as_u32()));
173             let discr = fx.bcx.ins().select(is_niche, niche_discr, dataful_variant);
174             let res = CValue::by_val(discr, dest_layout);
175             dest.write_cvalue(fx, res);
176         }
177     }
178 }