1
1
//! Handling of enum discriminants
2
2
//!
3
- //! Adapted from <https://github.com/rust-lang/rust/blob/d760df5aea483aae041c9a241e7acacf48f75035/src/librustc_codegen_ssa/mir/place.rs>
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>)
4
5
5
6
use rustc_target:: abi:: { Int , TagEncoding , Variants } ;
6
7
@@ -47,13 +48,18 @@ pub(crate) fn codegen_set_discriminant<'tcx>(
47
48
} => {
48
49
if variant_index != untagged_variant {
49
50
let niche = place. place_field ( fx, mir:: Field :: new ( tag_field) ) ;
51
+ let niche_type = fx. clif_type ( niche. layout ( ) . ty ) . unwrap ( ) ;
50
52
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) ;
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 = fx. bcx . ins ( ) . iconst ( types:: I64 , ( niche_value >> 64 ) as u64 as i64 ) ;
58
+ fx. bcx . ins ( ) . iconcat ( lsb, msb)
59
+ }
60
+ ty => fx. bcx . ins ( ) . iconst ( ty, niche_value as i64 ) ,
61
+ } ;
62
+ let niche_llval = CValue :: by_val ( niche_value, niche. layout ( ) ) ;
57
63
niche. write_cvalue ( fx, niche_llval) ;
58
64
}
59
65
}
@@ -96,6 +102,7 @@ pub(crate) fn codegen_get_discriminant<'tcx>(
96
102
}
97
103
} ;
98
104
105
+ let cast_to_size = dest_layout. layout . size ( ) ;
99
106
let cast_to = fx. clif_type ( dest_layout. ty ) . unwrap ( ) ;
100
107
101
108
// Read the tag/niche-encoded discriminant from memory.
@@ -114,21 +121,128 @@ pub(crate) fn codegen_get_discriminant<'tcx>(
114
121
dest. write_cvalue ( fx, res) ;
115
122
}
116
123
TagEncoding :: Niche { untagged_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
124
+ let tag_size = tag_scalar. size ( fx) ;
125
+ let max_unsigned = tag_size. unsigned_int_max ( ) ;
126
+ let max_signed = tag_size. signed_int_max ( ) as u128 ;
127
+ let min_signed = max_signed + 1 ;
128
+ let relative_max = niche_variants. end ( ) . as_u32 ( ) - niche_variants. start ( ) . as_u32 ( ) ;
129
+ let niche_end = niche_start. wrapping_add ( relative_max as u128 ) & max_unsigned;
130
+ let range = tag_scalar. valid_range ( fx) ;
131
+
132
+ let sle = |lhs : u128 , rhs : u128 | -> bool {
133
+ // Signed and unsigned comparisons give the same results,
134
+ // except that in signed comparisons an integer with the
135
+ // sign bit set is less than one with the sign bit clear.
136
+ // Toggle the sign bit to do a signed comparison.
137
+ ( lhs ^ min_signed) <= ( rhs ^ min_signed)
138
+ } ;
139
+
140
+ // We have a subrange `niche_start..=niche_end` inside `range`.
141
+ // If the value of the tag is inside this subrange, it's a
142
+ // "niche value", an increment of the discriminant. Otherwise it
143
+ // indicates the untagged variant.
144
+ // A general algorithm to extract the discriminant from the tag
145
+ // is:
146
+ // relative_tag = tag - niche_start
147
+ // is_niche = relative_tag <= (ule) relative_max
148
+ // discr = if is_niche {
149
+ // cast(relative_tag) + niche_variants.start()
150
+ // } else {
151
+ // untagged_variant
152
+ // }
153
+ // However, we will likely be able to emit simpler code.
154
+
155
+ // Find the least and greatest values in `range`, considered
156
+ // both as signed and unsigned.
157
+ let ( low_unsigned, high_unsigned) =
158
+ if range. start <= range. end { ( range. start , range. end ) } else { ( 0 , max_unsigned) } ;
159
+ let ( low_signed, high_signed) = if sle ( range. start , range. end ) {
160
+ ( range. start , range. end )
161
+ } else {
162
+ ( min_signed, max_signed)
163
+ } ;
164
+
165
+ let niches_ule = niche_start <= niche_end;
166
+ let niches_sle = sle ( niche_start, niche_end) ;
167
+ let cast_smaller = cast_to_size <= tag_size;
168
+
169
+ // In the algorithm above, we can change
170
+ // cast(relative_tag) + niche_variants.start()
171
+ // into
172
+ // cast(tag + (niche_variants.start() - niche_start))
173
+ // if either the casted type is no larger than the original
174
+ // type, or if the niche values are contiguous (in either the
175
+ // signed or unsigned sense).
176
+ let can_incr = cast_smaller || niches_ule || niches_sle;
177
+
178
+ let data_for_boundary_niche = || -> Option < ( IntCC , u128 ) > {
179
+ if !can_incr {
180
+ None
181
+ } else if niche_start == low_unsigned {
182
+ Some ( ( IntCC :: UnsignedLessThanOrEqual , niche_end) )
183
+ } else if niche_end == high_unsigned {
184
+ Some ( ( IntCC :: UnsignedGreaterThanOrEqual , niche_start) )
185
+ } else if niche_start == low_signed {
186
+ Some ( ( IntCC :: SignedLessThanOrEqual , niche_end) )
187
+ } else if niche_end == high_signed {
188
+ Some ( ( IntCC :: SignedGreaterThanOrEqual , niche_start) )
189
+ } else {
190
+ None
191
+ }
192
+ } ;
193
+
194
+ let ( is_niche, tagged_discr, delta) = if relative_max == 0 {
195
+ // Best case scenario: only one tagged variant. This will
196
+ // likely become just a comparison and a jump.
197
+ // The algorithm is:
198
+ // is_niche = tag == niche_start
199
+ // discr = if is_niche {
200
+ // niche_start
201
+ // } else {
202
+ // untagged_variant
203
+ // }
204
+ let is_niche = codegen_icmp_imm ( fx, IntCC :: Equal , tag, niche_start as i128 ) ;
205
+ let tagged_discr =
206
+ fx. bcx . ins ( ) . iconst ( cast_to, niche_variants. start ( ) . as_u32 ( ) as i64 ) ;
207
+ ( is_niche, tagged_discr, 0 )
208
+ } else if let Some ( ( predicate, constant) ) = data_for_boundary_niche ( ) {
209
+ // The niche values are either the lowest or the highest in
210
+ // `range`. We can avoid the first subtraction in the
211
+ // algorithm.
212
+ // The algorithm is now this:
213
+ // is_niche = tag <= niche_end
214
+ // discr = if is_niche {
215
+ // cast(tag + (niche_variants.start() - niche_start))
216
+ // } else {
217
+ // untagged_variant
218
+ // }
219
+ // (the first line may instead be tag >= niche_start,
220
+ // and may be a signed or unsigned comparison)
221
+ // The arithmetic must be done before the cast, so we can
222
+ // have the correct wrapping behavior. See issue #104519 for
223
+ // the consequences of getting this wrong.
224
+ let is_niche = codegen_icmp_imm ( fx, predicate, tag, constant as i128 ) ;
225
+ let delta = ( niche_variants. start ( ) . as_u32 ( ) as u128 ) . wrapping_sub ( niche_start) ;
226
+ let incr_tag = if delta == 0 {
227
+ tag
228
+ } else {
229
+ let delta = match fx. bcx . func . dfg . value_type ( tag) {
230
+ types:: I128 => {
231
+ let lsb = fx. bcx . ins ( ) . iconst ( types:: I64 , delta as u64 as i64 ) ;
232
+ let msb = fx. bcx . ins ( ) . iconst ( types:: I64 , ( delta >> 64 ) as u64 as i64 ) ;
233
+ fx. bcx . ins ( ) . iconcat ( lsb, msb)
234
+ }
235
+ ty => fx. bcx . ins ( ) . iconst ( ty, delta as i64 ) ,
236
+ } ;
237
+ fx. bcx . ins ( ) . iadd ( tag, delta)
238
+ } ;
239
+
240
+ let cast_tag = clif_intcast ( fx, incr_tag, cast_to, !niches_ule) ;
241
+
242
+ ( is_niche, cast_tag, 0 )
131
243
} else {
244
+ // The special cases don't apply, so we'll have to go with
245
+ // the general algorithm.
132
246
let niche_start = match fx. bcx . func . dfg . value_type ( tag) {
133
247
types:: I128 => {
134
248
let lsb = fx. bcx . ins ( ) . iconst ( types:: I64 , niche_start as u64 as i64 ) ;
@@ -138,40 +252,34 @@ pub(crate) fn codegen_get_discriminant<'tcx>(
138
252
}
139
253
ty => fx. bcx . ins ( ) . iconst ( ty, niche_start as i64 ) ,
140
254
} ;
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 (
255
+ let relative_discr = fx. bcx . ins ( ) . isub ( tag, niche_start) ;
256
+ let cast_tag = clif_intcast ( fx, relative_discr, cast_to, false ) ;
257
+ let is_niche = crate :: common:: codegen_icmp_imm (
146
258
fx,
147
259
IntCC :: UnsignedLessThanOrEqual ,
148
260
relative_discr,
149
261
i128:: from ( relative_max) ,
150
- )
262
+ ) ;
263
+ ( is_niche, cast_tag, niche_variants. start ( ) . as_u32 ( ) as u128 )
151
264
} ;
152
265
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 )
266
+ let tagged_discr = if delta == 0 {
267
+ tagged_discr
268
+ } else {
269
+ let delta = match cast_to {
270
+ types:: I128 => {
271
+ let lsb = fx. bcx . ins ( ) . iconst ( types:: I64 , delta as u64 as i64 ) ;
272
+ let msb = fx. bcx . ins ( ) . iconst ( types:: I64 , ( delta >> 64 ) as u64 as i64 ) ;
273
+ fx. bcx . ins ( ) . iconcat ( lsb, msb)
274
+ }
275
+ ty => fx. bcx . ins ( ) . iconst ( ty, delta as i64 ) ,
168
276
} ;
169
- fx. bcx . ins ( ) . iadd_imm ( relative_discr , i64 :: from ( niche_variants . start ( ) . as_u32 ( ) ) )
277
+ fx. bcx . ins ( ) . iadd ( tagged_discr , delta )
170
278
} ;
171
279
172
280
let untagged_variant =
173
281
fx. bcx . ins ( ) . iconst ( cast_to, i64:: from ( untagged_variant. as_u32 ( ) ) ) ;
174
- let discr = fx. bcx . ins ( ) . select ( is_niche, niche_discr , untagged_variant) ;
282
+ let discr = fx. bcx . ins ( ) . select ( is_niche, tagged_discr , untagged_variant) ;
175
283
let res = CValue :: by_val ( discr, dest_layout) ;
176
284
dest. write_cvalue ( fx, res) ;
177
285
}
0 commit comments