Skip to content

Commit 93981be

Browse files
committed
Stop emitting Assumes into MIR when as-casting enums.
1 parent 3ea711f commit 93981be

13 files changed

+313
-167
lines changed

compiler/rustc_borrowck/src/type_check/mod.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
2323
use rustc_middle::mir::*;
2424
use rustc_middle::traits::query::NoSolution;
2525
use rustc_middle::ty::adjustment::PointerCoercion;
26-
use rustc_middle::ty::cast::CastTy;
26+
use rustc_middle::ty::cast::{CastTy, IntTy};
2727
use rustc_middle::ty::fold::fold_regions;
2828
use rustc_middle::ty::visit::TypeVisitableExt;
2929
use rustc_middle::ty::{
@@ -2183,11 +2183,24 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
21832183
}
21842184
}
21852185
CastKind::Transmute => {
2186-
span_mirbug!(
2187-
self,
2188-
rvalue,
2189-
"Unexpected CastKind::Transmute, which is not permitted in Analysis MIR",
2190-
);
2186+
let ty_from = op.ty(body, tcx);
2187+
let cast_ty_from = CastTy::from_ty(ty_from);
2188+
let cast_ty_to = CastTy::from_ty(*ty);
2189+
match (cast_ty_from, cast_ty_to) {
2190+
(
2191+
Some(CastTy::Int(IntTy::CEnum)),
2192+
Some(CastTy::Int(IntTy::U(_) | IntTy::I)),
2193+
) => (),
2194+
_ => {
2195+
span_mirbug!(
2196+
self,
2197+
rvalue,
2198+
"Invalid CastKind::Transmute cast {:?} -> {:?}",
2199+
ty_from,
2200+
ty
2201+
)
2202+
}
2203+
}
21912204
}
21922205
}
21932206
}

compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs

Lines changed: 69 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! See docs in `build/expr/mod.rs`.
22
3-
use rustc_abi::{BackendRepr, FieldIdx, Primitive};
3+
use std::assert_matches::assert_matches;
4+
5+
use rustc_abi::{BackendRepr, FieldIdx, TagEncoding, Variants};
46
use rustc_hir::lang_items::LangItem;
57
use rustc_index::{Idx, IndexVec};
68
use rustc_middle::bug;
@@ -9,8 +11,8 @@ use rustc_middle::mir::interpret::Scalar;
911
use rustc_middle::mir::*;
1012
use rustc_middle::thir::*;
1113
use rustc_middle::ty::cast::{CastTy, mir_cast_kind};
12-
use rustc_middle::ty::layout::IntegerExt;
13-
use rustc_middle::ty::util::IntTypeExt;
14+
use rustc_middle::ty::layout::PrimitiveExt;
15+
use rustc_middle::ty::util::{Discr, IntTypeExt};
1416
use rustc_middle::ty::{self, Ty, UpvarArgs};
1517
use rustc_span::source_map::Spanned;
1618
use rustc_span::{DUMMY_SP, Span};
@@ -197,97 +199,78 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
197199
ExprKind::Cast { source } => {
198200
let source_expr = &this.thir[source];
199201

200-
// Casting an enum to an integer is equivalent to computing the discriminant and casting the
201-
// discriminant. Previously every backend had to repeat the logic for this operation. Now we
202-
// create all the steps directly in MIR with operations all backends need to support anyway.
203202
let (source, ty) = if let ty::Adt(adt_def, ..) = source_expr.ty.kind()
204203
&& adt_def.is_enum()
205204
{
206-
let discr_ty = adt_def.repr().discr_type().to_ty(this.tcx);
207205
let temp = unpack!(block = this.as_temp(block, scope, source, Mutability::Not));
208-
let layout =
209-
this.tcx.layout_of(this.typing_env().as_query_input(source_expr.ty));
210-
let discr = this.temp(discr_ty, source_expr.span);
211-
this.cfg.push_assign(
212-
block,
213-
source_info,
214-
discr,
215-
Rvalue::Discriminant(temp.into()),
216-
);
217-
let (op, ty) = (Operand::Move(discr), discr_ty);
218-
219-
if let BackendRepr::Scalar(scalar) = layout.unwrap().backend_repr
220-
&& !scalar.is_always_valid(&this.tcx)
221-
&& let Primitive::Int(int_width, _signed) = scalar.primitive()
222-
{
223-
let unsigned_ty = int_width.to_ty(this.tcx, false);
224-
let unsigned_place = this.temp(unsigned_ty, expr_span);
225-
this.cfg.push_assign(
226-
block,
227-
source_info,
228-
unsigned_place,
229-
Rvalue::Cast(CastKind::IntToInt, Operand::Copy(discr), unsigned_ty),
230-
);
231-
232-
let bool_ty = this.tcx.types.bool;
233-
let range = scalar.valid_range(&this.tcx);
234-
let merge_op =
235-
if range.start <= range.end { BinOp::BitAnd } else { BinOp::BitOr };
236-
237-
let mut comparer = |range: u128, bin_op: BinOp| -> Place<'tcx> {
238-
// We can use `ty::TypingEnv::fully_monomorphized()` here
239-
// as we only need it to compute the layout of a primitive.
240-
let range_val = Const::from_bits(
241-
this.tcx,
242-
range,
243-
ty::TypingEnv::fully_monomorphized(),
244-
unsigned_ty,
245-
);
246-
let lit_op = this.literal_operand(expr.span, range_val);
247-
let is_bin_op = this.temp(bool_ty, expr_span);
248-
this.cfg.push_assign(
206+
let layout = this
207+
.tcx
208+
.layout_of(this.typing_env().as_query_input(source_expr.ty))
209+
.unwrap();
210+
match layout.variants {
211+
// Uninhabited enum, so we're in dead code.
212+
Variants::Empty => {
213+
let false_lit = this.zero_literal(expr_span, this.tcx.types.bool);
214+
this.cfg.push(
249215
block,
250-
source_info,
251-
is_bin_op,
252-
Rvalue::BinaryOp(
253-
bin_op,
254-
Box::new((Operand::Copy(unsigned_place), lit_op)),
255-
),
256-
);
257-
is_bin_op
258-
};
259-
let assert_place = if range.start == 0 {
260-
comparer(range.end, BinOp::Le)
261-
} else {
262-
let start_place = comparer(range.start, BinOp::Ge);
263-
let end_place = comparer(range.end, BinOp::Le);
264-
let merge_place = this.temp(bool_ty, expr_span);
265-
this.cfg.push_assign(
266-
block,
267-
source_info,
268-
merge_place,
269-
Rvalue::BinaryOp(
270-
merge_op,
271-
Box::new((
272-
Operand::Move(start_place),
273-
Operand::Move(end_place),
216+
Statement {
217+
source_info,
218+
kind: StatementKind::Intrinsic(Box::new(
219+
NonDivergingIntrinsic::Assume(false_lit),
274220
)),
275-
),
221+
},
276222
);
277-
merge_place
278-
};
279-
this.cfg.push(
280-
block,
281-
Statement {
282-
source_info,
283-
kind: StatementKind::Intrinsic(Box::new(
284-
NonDivergingIntrinsic::Assume(Operand::Move(assert_place)),
285-
)),
286-
},
287-
);
223+
// We still need to emit *something*, to keep the following MIR legal,
224+
// so give a zero of the type the cast was asking for.
225+
(this.zero_literal(expr_span, expr.ty), expr.ty)
226+
}
227+
// Only one legal variant, so we can just look up its
228+
// discriminant directly and return it as a constant.
229+
// (In the discriminant's type, not the cast-to type,
230+
// to avoid worrying about truncation or extension.)
231+
Variants::Single { index } => {
232+
let Discr { val, ty } =
233+
adt_def.discriminant_for_variant(this.tcx, index);
234+
let val = Const::from_bits(this.tcx, val, this.typing_env(), ty);
235+
(this.literal_operand(expr_span, val), ty)
236+
}
237+
// Casting an enum to an integer is only supported for enums which
238+
// have no fields, so we can transmute the stored tag.
239+
// This used to emit `Assume`s into the MIR, but that bloated it,
240+
// so now we re-use the `Transmute` checks that backends ought to
241+
// support anyway for polymorphic MIR cases.
242+
Variants::Multiple { tag, ref tag_encoding, .. } => {
243+
assert_matches!(tag_encoding, TagEncoding::Direct);
244+
if let BackendRepr::Scalar(repr) = layout.backend_repr {
245+
assert_eq!(repr, tag);
246+
let tag_ty = tag.primitive().to_ty(this.tcx);
247+
let tag = this.temp(tag_ty, expr_span);
248+
this.cfg.push_assign(
249+
block,
250+
source_info,
251+
tag,
252+
Rvalue::Cast(
253+
CastKind::Transmute,
254+
Operand::Move(Place::from(temp)),
255+
tag_ty,
256+
),
257+
);
258+
(Operand::Move(tag), tag_ty)
259+
} else {
260+
// FIXME: One `Transmute` works union-style (so it can truncate
261+
// the padding down to just the tag) we can remove this fallback.
262+
let discr_ty = adt_def.repr().discr_type().to_ty(this.tcx);
263+
let discr = this.temp(discr_ty, source_expr.span);
264+
this.cfg.push_assign(
265+
block,
266+
source_info,
267+
discr,
268+
Rvalue::Discriminant(temp.into()),
269+
);
270+
(Operand::Move(discr), discr_ty)
271+
}
272+
}
288273
}
289-
290-
(op, ty)
291274
} else {
292275
let ty = source_expr.ty;
293276
let source = unpack!(

compiler/rustc_mir_transform/src/validate.rs

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,37 +1303,27 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
13031303
}
13041304
}
13051305
CastKind::Transmute => {
1306-
if let MirPhase::Runtime(..) = self.body.phase {
1307-
// Unlike `mem::transmute`, a MIR `Transmute` is well-formed
1308-
// for any two `Sized` types, just potentially UB to run.
1309-
1310-
if !self
1311-
.tcx
1312-
.normalize_erasing_regions(self.typing_env, op_ty)
1313-
.is_sized(self.tcx, self.typing_env)
1314-
{
1315-
self.fail(
1316-
location,
1317-
format!("Cannot transmute from non-`Sized` type {op_ty:?}"),
1318-
);
1319-
}
1320-
if !self
1321-
.tcx
1322-
.normalize_erasing_regions(self.typing_env, *target_type)
1323-
.is_sized(self.tcx, self.typing_env)
1324-
{
1325-
self.fail(
1326-
location,
1327-
format!("Cannot transmute to non-`Sized` type {target_type:?}"),
1328-
);
1329-
}
1330-
} else {
1306+
// Unlike `mem::transmute`, a MIR `Transmute` is well-formed
1307+
// for any two `Sized` types, just potentially UB to run.
1308+
1309+
if !self
1310+
.tcx
1311+
.normalize_erasing_regions(self.typing_env, op_ty)
1312+
.is_sized(self.tcx, self.typing_env)
1313+
{
13311314
self.fail(
13321315
location,
1333-
format!(
1334-
"Transmute is not supported in non-runtime phase {:?}.",
1335-
self.body.phase
1336-
),
1316+
format!("Cannot transmute from non-`Sized` type {op_ty:?}"),
1317+
);
1318+
}
1319+
if !self
1320+
.tcx
1321+
.normalize_erasing_regions(self.typing_env, *target_type)
1322+
.is_sized(self.tcx, self.typing_env)
1323+
{
1324+
self.fail(
1325+
location,
1326+
format!("Cannot transmute to non-`Sized` type {target_type:?}"),
13371327
);
13381328
}
13391329
}

tests/mir-opt/building/enum_cast.bar.built.after.mir

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@ fn bar(_1: Bar) -> usize {
44
debug bar => _1;
55
let mut _0: usize;
66
let _2: Bar;
7-
let mut _3: isize;
8-
let mut _4: u8;
9-
let mut _5: bool;
7+
let mut _3: u8;
108

119
bb0: {
1210
StorageLive(_2);
1311
_2 = move _1;
14-
_3 = discriminant(_2);
15-
_4 = copy _3 as u8 (IntToInt);
16-
_5 = Le(copy _4, const 1_u8);
17-
assume(move _5);
12+
_3 = move _2 as u8 (Transmute);
1813
_0 = move _3 as usize (IntToInt);
1914
StorageDead(_2);
2015
return;

tests/mir-opt/building/enum_cast.boo.built.after.mir

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,11 @@ fn boo(_1: Boo) -> usize {
55
let mut _0: usize;
66
let _2: Boo;
77
let mut _3: u8;
8-
let mut _4: u8;
9-
let mut _5: bool;
108

119
bb0: {
1210
StorageLive(_2);
1311
_2 = move _1;
14-
_3 = discriminant(_2);
15-
_4 = copy _3 as u8 (IntToInt);
16-
_5 = Le(copy _4, const 1_u8);
17-
assume(move _5);
12+
_3 = move _2 as u8 (Transmute);
1813
_0 = move _3 as usize (IntToInt);
1914
StorageDead(_2);
2015
return;

0 commit comments

Comments
 (0)