From f452f6d3db8f10cfe69f8d3b7c889ab47380ce9a Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sat, 5 Jul 2025 22:32:26 -0700 Subject: [PATCH 1/2] Allow matching enums without needing `alloca`s --- compiler/rustc_codegen_ssa/src/mir/analyze.rs | 103 ++++++------ compiler/rustc_codegen_ssa/src/mir/locals.rs | 1 + compiler/rustc_codegen_ssa/src/mir/mod.rs | 1 + compiler/rustc_codegen_ssa/src/mir/operand.rs | 63 ++++---- tests/codegen/array-cmp.rs | 10 +- tests/codegen/common_prim_int_ptr.rs | 6 +- tests/codegen/enum/enum-extract.rs | 151 ++++++++++++++++++ tests/codegen/enum/enum-match.rs | 27 ++-- tests/codegen/enum/enum-two-variants-match.rs | 16 +- tests/codegen/intrinsics/cold_path3.rs | 4 +- tests/codegen/range-loop.rs | 6 +- tests/codegen/try_question_mark_nop.rs | 49 +++--- 12 files changed, 299 insertions(+), 138 deletions(-) create mode 100644 tests/codegen/enum/enum-extract.rs diff --git a/compiler/rustc_codegen_ssa/src/mir/analyze.rs b/compiler/rustc_codegen_ssa/src/mir/analyze.rs index 99f35b7920864..14386755fa6eb 100644 --- a/compiler/rustc_codegen_ssa/src/mir/analyze.rs +++ b/compiler/rustc_codegen_ssa/src/mir/analyze.rs @@ -6,7 +6,7 @@ use rustc_index::bit_set::DenseBitSet; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::{self, DefLocation, Location, TerminatorKind, traversal}; -use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf}; +use rustc_middle::ty::layout::LayoutOf; use rustc_middle::{bug, span_bug}; use tracing::debug; @@ -99,63 +99,64 @@ impl<'a, 'b, 'tcx, Bx: BuilderMethods<'b, 'tcx>> LocalAnalyzer<'a, 'b, 'tcx, Bx> context: PlaceContext, location: Location, ) { - let cx = self.fx.cx; + const COPY_CONTEXT: PlaceContext = + PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy); + + // `PlaceElem::Index` is the only variant that can mention other `Local`s, + // so check for those up-front before any potential short-circuits. + for elem in place_ref.projection { + if let mir::PlaceElem::Index(index_local) = *elem { + self.visit_local(index_local, COPY_CONTEXT, location); + } + } - if let Some((place_base, elem)) = place_ref.last_projection() { - let mut base_context = if context.is_mutating_use() { - PlaceContext::MutatingUse(MutatingUseContext::Projection) - } else { - PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) - }; + // If our local is already memory, nothing can make it *more* memory + // so we don't need to bother checking the projections further. + if self.locals[place_ref.local] == LocalKind::Memory { + return; + } - // Allow uses of projections that are ZSTs or from scalar fields. - let is_consume = matches!( - context, - PlaceContext::NonMutatingUse( - NonMutatingUseContext::Copy | NonMutatingUseContext::Move, - ) - ); - if is_consume { - let base_ty = place_base.ty(self.fx.mir, cx.tcx()); - let base_ty = self.fx.monomorphize(base_ty); - - // ZSTs don't require any actual memory access. - let elem_ty = base_ty.projection_ty(cx.tcx(), self.fx.monomorphize(elem)).ty; - let span = self.fx.mir.local_decls[place_ref.local].source_info.span; - if cx.spanned_layout_of(elem_ty, span).is_zst() { - return; - } + if place_ref.is_indirect_first_projection() { + // If this starts with a `Deref`, we only need to record a read of the + // pointer being dereferenced, as all the subsequent projections are + // working on a place which is always supported. (And because we're + // looking at codegen MIR, it can only happen as the first projection.) + self.visit_local(place_ref.local, COPY_CONTEXT, location); + return; + } - if let mir::ProjectionElem::Field(..) = elem { - let layout = cx.spanned_layout_of(base_ty.ty, span); - if cx.is_backend_immediate(layout) || cx.is_backend_scalar_pair(layout) { - // Recurse with the same context, instead of `Projection`, - // potentially stopping at non-operand projections, - // which would trigger `not_ssa` on locals. - base_context = context; - } - } - } + if !place_ref.projection.is_empty() && context.is_mutating_use() { + // If it's a mutating use it doesn't matter what the projections are, + // if there are *any* then we need a place to write. (For example, + // `_1 = Foo()` works in SSA but `_2.0 = Foo()` does not.) + let mut_projection = PlaceContext::MutatingUse(MutatingUseContext::Projection); + self.visit_local(place_ref.local, mut_projection, location); + return; + } - if let mir::ProjectionElem::Deref = elem { - // Deref projections typically only read the pointer. - base_context = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy); - } + for elem in place_ref.projection { + // Scan through to ensure the only projections are those which + // `FunctionCx::maybe_codegen_consume_direct` can handle. + match elem { + mir::PlaceElem::Field(..) | mir::PlaceElem::Downcast(..) => {} + + mir::PlaceElem::Index(..) + | mir::PlaceElem::ConstantIndex { .. } + | mir::PlaceElem::Subslice { .. } + | mir::PlaceElem::OpaqueCast(..) + | mir::PlaceElem::UnwrapUnsafeBinder(..) + | mir::PlaceElem::Subtype(..) => { + self.locals[place_ref.local] = LocalKind::Memory; + return; + } - self.process_place(&place_base, base_context, location); - // HACK(eddyb) this emulates the old `visit_projection_elem`, this - // entire `visit_place`-like `process_place` method should be rewritten, - // now that we have moved to the "slice of projections" representation. - if let mir::ProjectionElem::Index(local) = elem { - self.visit_local( - local, - PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy), - location, - ); + mir::PlaceElem::Deref => bug!("Deref after first position"), } - } else { - self.visit_local(place_ref.local, context, location); } + + // Even with supported projections, we still need to have `visit_local` + // check for things that can't be done in SSA (like `SharedBorrow`). + self.visit_local(place_ref.local, context, location); } } diff --git a/compiler/rustc_codegen_ssa/src/mir/locals.rs b/compiler/rustc_codegen_ssa/src/mir/locals.rs index 93f0ab36f2a2f..741c1f0a3610d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/locals.rs +++ b/compiler/rustc_codegen_ssa/src/mir/locals.rs @@ -12,6 +12,7 @@ use tracing::{debug, warn}; use crate::mir::{FunctionCx, LocalRef}; use crate::traits::BuilderMethods; +#[derive(Debug)] pub(super) struct Locals<'tcx, V> { values: IndexVec>, } diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 10b44a1faf087..9b755863e77e5 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -136,6 +136,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } +#[derive(Debug)] enum LocalRef<'tcx, V> { Place(PlaceRef<'tcx, V>), /// `UnsizedPlace(p)`: `p` itself is a thin pointer (indirect place). diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs index 2896dfd5463c1..5d544d56fa343 100644 --- a/compiler/rustc_codegen_ssa/src/mir/operand.rs +++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs @@ -126,7 +126,7 @@ pub struct OperandRef<'tcx, V> { pub layout: TyAndLayout<'tcx>, } -impl fmt::Debug for OperandRef<'_, V> { +impl fmt::Debug for OperandRef<'_, V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "OperandRef({:?} @ {:?})", self.val, self.layout) } @@ -361,13 +361,17 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { let (in_scalar, imm) = match (self.val, self.layout.backend_repr) { // Extract a scalar component from a pair. (OperandValue::Pair(a_llval, b_llval), BackendRepr::ScalarPair(a, b)) => { - if offset.bytes() == 0 { + // This needs to look at `offset`, rather than `i`, because + // for a type like `Option`, the first thing in the pair + // is the tag, so `(_2 as Some).0` needs to read the *second* + // thing in the pair despite it being "field zero". + if offset == Size::ZERO { assert_eq!(field.size, a.size(bx.cx())); - (Some(a), a_llval) + (a, a_llval) } else { assert_eq!(offset, a.size(bx.cx()).align_to(b.align(bx.cx()).abi)); assert_eq!(field.size, b.size(bx.cx())); - (Some(b), b_llval) + (b, b_llval) } } @@ -378,23 +382,12 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { OperandValue::Immediate(match field.backend_repr { BackendRepr::SimdVector { .. } => imm, BackendRepr::Scalar(out_scalar) => { - let Some(in_scalar) = in_scalar else { - span_bug!( - fx.mir.span, - "OperandRef::extract_field({:?}): missing input scalar for output scalar", - self - ) - }; - if in_scalar != out_scalar { - // If the backend and backend_immediate types might differ, - // flip back to the backend type then to the new immediate. - // This avoids nop truncations, but still handles things like - // Bools in union fields needs to be truncated. - let backend = bx.from_immediate(imm); - bx.to_immediate_scalar(backend, out_scalar) - } else { - imm - } + // For a type like `Result` the layout is `Pair(i64, ptr)`. + // But if we're reading the `Ok` payload, we need to turn that `ptr` + // back into an integer. To avoid repeating logic we do that by + // calling the transmute code, which is legal thanks to the size + // assert we did when pulling it out of the pair. + transmute_scalar(bx, imm, in_scalar, out_scalar) } BackendRepr::ScalarPair(_, _) | BackendRepr::Memory { .. } => bug!(), }) @@ -852,7 +845,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { LocalRef::Operand(mut o) => { // Moves out of scalar and scalar pair fields are trivial. for elem in place_ref.projection.iter() { - match elem { + match *elem { mir::ProjectionElem::Field(f, _) => { assert!( !o.layout.ty.is_any_ptr(), @@ -861,17 +854,21 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { ); o = o.extract_field(self, bx, f.index()); } - mir::ProjectionElem::Index(_) - | mir::ProjectionElem::ConstantIndex { .. } => { - // ZSTs don't require any actual memory access. - // FIXME(eddyb) deduplicate this with the identical - // checks in `codegen_consume` and `extract_field`. - let elem = o.layout.field(bx.cx(), 0); - if elem.is_zst() { - o = OperandRef::zero_sized(elem); - } else { - return None; - } + mir::ProjectionElem::Downcast(_sym, variant_idx) => { + let layout = o.layout.for_variant(bx.cx(), variant_idx); + let val = match layout.backend_repr { + // The transmute here handles cases like `Result` + // where the immediate values need to change for + // the specific types in the cast-to variant. + BackendRepr::Scalar(..) | BackendRepr::ScalarPair(..) => { + self.codegen_transmute_operand(bx, o, layout) + } + BackendRepr::SimdVector { .. } | BackendRepr::Memory { .. } => { + o.val + } + }; + + o = OperandRef { val, layout }; } _ => return None, } diff --git a/tests/codegen/array-cmp.rs b/tests/codegen/array-cmp.rs index 0d33765540176..30bdc686a7e14 100644 --- a/tests/codegen/array-cmp.rs +++ b/tests/codegen/array-cmp.rs @@ -39,6 +39,10 @@ pub fn array_of_tuple_le(a: &[(i16, u16); 2], b: &[(i16, u16); 2]) -> bool { // CHECK: %[[EQ00:.+]] = icmp eq i16 %[[A00]], %[[B00]] // CHECK-NEXT: br i1 %[[EQ00]], label %[[L01:.+]], label %[[EXIT_S:.+]] + // CHECK: [[EXIT_S]]: + // CHECK: %[[RET_S:.+]] = icmp sle i16 + // CHECK: br label + // CHECK: [[L01]]: // CHECK: %[[PA01:.+]] = getelementptr{{.+}}i8, ptr %a, {{i32|i64}} 2 // CHECK: %[[PB01:.+]] = getelementptr{{.+}}i8, ptr %b, {{i32|i64}} 2 @@ -66,8 +70,12 @@ pub fn array_of_tuple_le(a: &[(i16, u16); 2], b: &[(i16, u16); 2]) -> bool { // CHECK: %[[EQ11:.+]] = icmp eq i16 %[[A11]], %[[B11]] // CHECK-NEXT: br i1 %[[EQ11]], label %[[DONE:.+]], label %[[EXIT_U]] + // CHECK: [[EXIT_U]]: + // CHECK: %[[RET_U:.+]] = icmp ule i16 + // CHECK: br label + // CHECK: [[DONE]]: - // CHECK: %[[RET:.+]] = phi i1 [ %{{.+}}, %[[EXIT_S]] ], [ %{{.+}}, %[[EXIT_U]] ], [ true, %[[L11]] ] + // CHECK: %[[RET:.+]] = phi i1 [ true, %[[L11]] ], [ %[[RET_S]], %[[EXIT_S]] ], [ %[[RET_U]], %[[EXIT_U]] ] // CHECK: ret i1 %[[RET]] a <= b diff --git a/tests/codegen/common_prim_int_ptr.rs b/tests/codegen/common_prim_int_ptr.rs index 53716adccbf21..5283a3e7ae1bb 100644 --- a/tests/codegen/common_prim_int_ptr.rs +++ b/tests/codegen/common_prim_int_ptr.rs @@ -40,9 +40,13 @@ pub unsafe fn extract_int(x: Result>) -> usize { } // CHECK-LABEL: @extract_box -// CHECK-SAME: (i{{[0-9]+}} {{[^%]+}} [[DISCRIMINANT:%[0-9]+]], ptr {{[^%]+}} [[PAYLOAD:%[0-9]+]]) +// CHECK-SAME: (i{{[0-9]+}} {{[^%]+}} [[DISCRIMINANT:%x.0]], ptr {{[^%]+}} [[PAYLOAD:%x.1]]) #[no_mangle] pub unsafe fn extract_box(x: Result>) -> Box { + // CHECK: [[NOT_OK:%.+]] = icmp ne i{{[0-9]+}} [[DISCRIMINANT]], 0 + // CHECK: call void @llvm.assume(i1 [[NOT_OK]]) + // CHECK: [[NOT_NULL:%.+]] = icmp ne ptr [[PAYLOAD]], null + // CHECK: call void @llvm.assume(i1 [[NOT_NULL]]) // CHECK: ret ptr [[PAYLOAD]] match x { Ok(_) => std::intrinsics::unreachable(), diff --git a/tests/codegen/enum/enum-extract.rs b/tests/codegen/enum/enum-extract.rs new file mode 100644 index 0000000000000..285953313866b --- /dev/null +++ b/tests/codegen/enum/enum-extract.rs @@ -0,0 +1,151 @@ +//@ revisions: OPT DBG +//@ compile-flags: -Cno-prepopulate-passes -Cdebuginfo=0 +//@[OPT] compile-flags: -Copt-level=1 +//@[DBG] compile-flags: -Copt-level=0 +//@ min-llvm-version: 19 +//@ only-64bit + +#![crate_type = "lib"] + +// This tests various cases around consuming enums as SSA values in what we emit. +// Importantly, it checks things like correct `i1` handling for `bool` +// and for mixed integer/pointer payloads. + +use std::cmp::Ordering; +use std::num::NonZero; +use std::ptr::NonNull; + +#[no_mangle] +fn use_option_u32(x: Option) -> u32 { + // CHECK-LABEL: @use_option_u32 + // OPT-SAME: (i32 noundef range(i32 0, 2) %x.0, i32 %x.1) + + // CHECK-NOT: alloca + // CHECK: %[[DISCR:.+]] = zext i32 %x.0 to i64 + // CHECK: %[[IS_SOME:.+]] = trunc nuw i64 %[[DISCR]] to i1 + // OPT: %[[LIKELY:.+]] = call i1 @llvm.expect.i1(i1 %[[IS_SOME]], i1 true) + // OPT: br i1 %[[LIKELY]], label %[[BLOCK:.+]], + // DBG: br i1 %[[IS_SOME]], label %[[BLOCK:.+]], + + // CHECK: [[BLOCK]]: + // CHECK: ret i32 %x.1 + + if let Some(val) = x { val } else { unreachable!() } +} + +#[no_mangle] +fn use_option_bool(x: Option) -> bool { + // CHECK-LABEL: @use_option_bool + // OPT-SAME: (i8 noundef range(i8 0, 3) %x) + + // CHECK-NOT: alloca + // CHECK: %[[IS_NONE:.+]] = icmp eq i8 %x, 2 + // CHECK: %[[DISCR:.+]] = select i1 %[[IS_NONE]], i64 0, i64 1 + // CHECK: %[[IS_SOME:.+]] = trunc nuw i64 %[[DISCR]] to i1 + // OPT: %[[LIKELY:.+]] = call i1 @llvm.expect.i1(i1 %[[IS_SOME]], i1 true) + // OPT: br i1 %[[LIKELY]], label %[[BLOCK:.+]], + // DBG: br i1 %[[IS_SOME]], label %[[BLOCK:.+]], + + // CHECK: [[BLOCK]]: + // CHECK: %val = trunc nuw i8 %x to i1 + // CHECK: ret i1 %val + + if let Some(val) = x { val } else { unreachable!() } +} + +#[no_mangle] +fn use_option_ordering(x: Option) -> Ordering { + // CHECK-LABEL: @use_option_ordering + // OPT-SAME: (i8 noundef range(i8 -1, 3) %x) + + // CHECK: %[[IS_NONE:.+]] = icmp eq i8 %x, 2 + // CHECK: %[[DISCR:.+]] = select i1 %[[IS_NONE]], i64 0, i64 1 + // CHECK: %[[IS_SOME:.+]] = trunc nuw i64 %[[DISCR]] to i1 + // OPT: %[[LIKELY:.+]] = call i1 @llvm.expect.i1(i1 %[[IS_SOME]], i1 true) + // OPT: br i1 %[[LIKELY]], label %[[BLOCK:.+]], + // DBG: br i1 %[[IS_SOME]], label %[[BLOCK:.+]], + + // CHECK: [[BLOCK]]: + // OPT: %[[SHIFTED:.+]] = sub i8 %x, -1 + // OPT: %[[IN_WIDTH:.+]] = icmp ule i8 %[[SHIFTED]], 3 + // OPT: call void @llvm.assume(i1 %[[IN_WIDTH]]) + // DBG-NOT: assume + // CHECK: ret i8 %x + + if let Some(val) = x { val } else { unreachable!() } +} + +#[no_mangle] +fn use_result_nzusize(x: Result, NonNull>) -> NonZero { + // CHECK-LABEL: @use_result_nzusize + // OPT-SAME: (i64 noundef range(i64 0, 2) %x.0, ptr noundef %x.1) + + // CHECK-NOT: alloca + // CHECK: %[[IS_ERR:.+]] = trunc nuw i64 %x.0 to i1 + // OPT: %[[UNLIKELY:.+]] = call i1 @llvm.expect.i1(i1 %[[IS_ERR]], i1 false) + // OPT: br i1 %[[UNLIKELY]], label %[[PANIC:.+]], label %[[BLOCK:.+]] + // DBG: br i1 %[[IS_ERR]], label %[[PANIC:.+]], label %[[BLOCK:.+]] + + // CHECK: [[BLOCK]]: + // CHECK: %val = ptrtoint ptr %x.1 to i64 + // CHECK: ret i64 %val + + if let Ok(val) = x { val } else { unreachable!() } +} + +#[repr(u64)] +enum BigEnum { + Foo = 100, + Bar = 200, +} + +#[no_mangle] +fn use_result_bigenum(x: Result) -> BigEnum { + // CHECK-LABEL: @use_result_bigenum + // OPT-SAME: (i64 noundef range(i64 0, 2) %x.0, i64 noundef %x.1) + + // CHECK-NOT: alloca + // CHECK: %[[IS_ERR:.+]] = trunc nuw i64 %x.0 to i1 + // OPT: %[[UNLIKELY:.+]] = call i1 @llvm.expect.i1(i1 %[[IS_ERR]], i1 false) + // OPT: br i1 %[[UNLIKELY]], label %[[PANIC:.+]], label %[[BLOCK:.+]] + // DBG: br i1 %[[IS_ERR]], label %[[PANIC:.+]], label %[[BLOCK:.+]] + + // CHECK: [[BLOCK]]: + // CHECK: ret i64 %x.1 + + if let Ok(val) = x { val } else { unreachable!() } +} + +struct WhateverError; + +#[no_mangle] +fn use_result_nonnull(x: Result, WhateverError>) -> NonNull { + // CHECK-LABEL: @use_result_nonnull + // OPT-SAME: (ptr noundef %x) + + // CHECK-NOT: alloca + // CHECK: %[[ADDR:.+]] = ptrtoint ptr %x to i64 + // CHECK: %[[IS_NULL:.+]] = icmp eq i64 %[[ADDR]], 0 + // CHECK: %[[DISCR:.+]] = select i1 %[[IS_NULL]], i64 1, i64 0 + // CHECK: %[[IS_ERR:.+]] = trunc nuw i64 %[[DISCR]] to i1 + // OPT: %[[UNLIKELY:.+]] = call i1 @llvm.expect.i1(i1 %[[IS_ERR]], i1 false) + // OPT: br i1 %[[UNLIKELY]], label %[[PANIC:.+]], label %[[BLOCK:.+]] + // DBG: br i1 %[[IS_ERR]], label %[[PANIC:.+]], label %[[BLOCK:.+]] + + // CHECK: [[BLOCK]]: + // CHECK: ret ptr %x + + if let Ok(val) = x { val } else { unreachable!() } +} + +const SOME_FOUR: Option = Some(4); + +#[no_mangle] +fn use_option_from_const() -> u8 { + // CHECK-LABEL: @use_option_from_const() + // CHECK-NEXT: start: + // OPT-NEXT: ret i8 4 + // DBG: %[[PAYLOAD:.+]] = load i8, ptr getelementptr inbounds + // DBG: ret i8 %[[PAYLOAD]] + if let Some(val) = SOME_FOUR { val } else { unreachable!() } +} diff --git a/tests/codegen/enum/enum-match.rs b/tests/codegen/enum/enum-match.rs index 6da6ad1f078d3..9a835e76ed3b8 100644 --- a/tests/codegen/enum/enum-match.rs +++ b/tests/codegen/enum/enum-match.rs @@ -15,11 +15,10 @@ pub enum Enum0 { B, } -// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match0(i8{{.+}}%0) +// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match0(i8{{.+}}%e) // CHECK-NEXT: start: -// CHECK-NEXT: %[[IS_B:.+]] = icmp eq i8 %0, 2 -// CHECK-NEXT: %[[TRUNC:.+]] = and i8 %0, 1 -// CHECK-NEXT: %[[R:.+]] = select i1 %[[IS_B]], i8 13, i8 %[[TRUNC]] +// CHECK-NEXT: %[[IS_B:.+]] = icmp eq i8 %e, 2 +// CHECK-NEXT: %[[R:.+]] = select i1 %[[IS_B]], i8 13, i8 %e // CHECK-NEXT: ret i8 %[[R]] #[no_mangle] pub fn match0(e: Enum0) -> u8 { @@ -37,9 +36,9 @@ pub enum Enum1 { C, } -// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match1(i8{{.+}}%0) +// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match1(i8{{.+}}%e) // CHECK-NEXT: start: -// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %0, -2 +// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %e, -2 // CHECK-NEXT: %[[REL_VAR_WIDE:.+]] = zext i8 %[[REL_VAR]] to i64 // CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ult i8 %[[REL_VAR]], 2 // CHECK-NEXT: %[[NICHE_DISCR:.+]] = add nuw nsw i64 %[[REL_VAR_WIDE]], 1 @@ -98,9 +97,9 @@ pub enum Enum2 { E, } -// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match2(i8{{.+}}%0) +// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match2(i8{{.+}}%e) // CHECK-NEXT: start: -// CHECK-NEXT: %[[REL_VAR:.+]] = add i8 %0, 2 +// CHECK-NEXT: %[[REL_VAR:.+]] = add i8 %e, 2 // CHECK-NEXT: %[[REL_VAR_WIDE:.+]] = zext i8 %[[REL_VAR]] to i64 // CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ult i8 %[[REL_VAR]], 4 // CHECK-NEXT: %[[NICHE_DISCR:.+]] = add nuw nsw i64 %[[REL_VAR_WIDE]], 1 @@ -121,9 +120,9 @@ pub fn match2(e: Enum2) -> u8 { // And make sure it works even if the niched scalar is a pointer. // (For example, that we don't try to `sub` on pointers.) -// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i16 -?[0-9]+, -?[0-9]+\))?}} i16 @match3(ptr{{.+}}%0) +// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i16 -?[0-9]+, -?[0-9]+\))?}} i16 @match3(ptr{{.+}}%e) // CHECK-NEXT: start: -// CHECK-NEXT: %[[IS_NULL:.+]] = icmp eq ptr %0, null +// CHECK-NEXT: %[[IS_NULL:.+]] = icmp eq ptr %e, null // CHECK-NEXT: br i1 %[[IS_NULL]] #[no_mangle] pub fn match3(e: Option<&u8>) -> i16 { @@ -145,9 +144,9 @@ pub enum MiddleNiche { E, } -// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 -?[0-9]+, -?[0-9]+\))?}} i8 @match4(i8{{.+}}%0) +// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 -?[0-9]+, -?[0-9]+\))?}} i8 @match4(i8{{.+}}%e) // CHECK-NEXT: start: -// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %0, -2 +// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %e, -2 // CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ult i8 %[[REL_VAR]], 5 // CHECK-NEXT: %[[NOT_IMPOSSIBLE:.+]] = icmp ne i8 %[[REL_VAR]], 2 // CHECK-NEXT: call void @llvm.assume(i1 %[[NOT_IMPOSSIBLE]]) @@ -449,9 +448,9 @@ pub enum HugeVariantIndex { Possible259, } -// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match5(i8{{.+}}%0) +// CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match5(i8{{.+}}%e) // CHECK-NEXT: start: -// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %0, -2 +// CHECK-NEXT: %[[REL_VAR:.+]] = add{{( nsw)?}} i8 %e, -2 // CHECK-NEXT: %[[REL_VAR_WIDE:.+]] = zext i8 %[[REL_VAR]] to i64 // CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ult i8 %[[REL_VAR]], 3 // CHECK-NEXT: %[[NOT_IMPOSSIBLE:.+]] = icmp ne i8 %[[REL_VAR]], 1 diff --git a/tests/codegen/enum/enum-two-variants-match.rs b/tests/codegen/enum/enum-two-variants-match.rs index 12d9edc4d6234..39a46f07ea29a 100644 --- a/tests/codegen/enum/enum-two-variants-match.rs +++ b/tests/codegen/enum/enum-two-variants-match.rs @@ -56,18 +56,16 @@ pub fn result_match(x: Result) -> u16 { } } -// CHECK-LABEL: @option_bool_match( +// CHECK-LABEL: @option_bool_match(i8{{.+}}%x) #[no_mangle] pub fn option_bool_match(x: Option) -> char { - // CHECK: %[[RAW:.+]] = load i8, ptr %x - // CHECK: %[[IS_NONE:.+]] = icmp eq i8 %[[RAW]], 2 + // CHECK: %[[IS_NONE:.+]] = icmp eq i8 %x, 2 // CHECK: %[[OPT_DISCR:.+]] = select i1 %[[IS_NONE]], i64 0, i64 1 // CHECK: %[[OPT_DISCR_T:.+]] = trunc nuw i64 %[[OPT_DISCR]] to i1 // CHECK: br i1 %[[OPT_DISCR_T]], label %[[BB_SOME:.+]], label %[[BB_NONE:.+]] // CHECK: [[BB_SOME]]: - // CHECK: %[[FIELD:.+]] = load i8, ptr %x - // CHECK: %[[FIELD_T:.+]] = trunc nuw i8 %[[FIELD]] to i1 + // CHECK: %[[FIELD_T:.+]] = trunc nuw i8 %x to i1 // CHECK: br i1 %[[FIELD_T]] match x { None => 'n', @@ -77,18 +75,16 @@ pub fn option_bool_match(x: Option) -> char { } use std::cmp::Ordering::{self, *}; -// CHECK-LABEL: @option_ordering_match( +// CHECK-LABEL: @option_ordering_match(i8{{.+}}%x) #[no_mangle] pub fn option_ordering_match(x: Option) -> char { - // CHECK: %[[RAW:.+]] = load i8, ptr %x - // CHECK: %[[IS_NONE:.+]] = icmp eq i8 %[[RAW]], 2 + // CHECK: %[[IS_NONE:.+]] = icmp eq i8 %x, 2 // CHECK: %[[OPT_DISCR:.+]] = select i1 %[[IS_NONE]], i64 0, i64 1 // CHECK: %[[OPT_DISCR_T:.+]] = trunc nuw i64 %[[OPT_DISCR]] to i1 // CHECK: br i1 %[[OPT_DISCR_T]], label %[[BB_SOME:.+]], label %[[BB_NONE:.+]] // CHECK: [[BB_SOME]]: - // CHECK: %[[FIELD:.+]] = load i8, ptr %x - // CHECK: switch i8 %[[FIELD]], label %[[UNREACHABLE:.+]] [ + // CHECK: switch i8 %x, label %[[UNREACHABLE:.+]] [ // CHECK-NEXT: i8 -1, label // CHECK-NEXT: i8 0, label // CHECK-NEXT: i8 1, label diff --git a/tests/codegen/intrinsics/cold_path3.rs b/tests/codegen/intrinsics/cold_path3.rs index bf3347de665db..36cb35a320277 100644 --- a/tests/codegen/intrinsics/cold_path3.rs +++ b/tests/codegen/intrinsics/cold_path3.rs @@ -45,7 +45,7 @@ pub fn test(x: Option) { } // CHECK-LABEL: @test( - // CHECK: switch i32 %1, label %bb1 [ + // CHECK: switch i32 %x.1, label %bb1 [ // CHECK: i32 0, label %bb6 // CHECK: i32 1, label %bb5 // CHECK: i32 2, label %bb4 @@ -76,7 +76,7 @@ pub fn test2(x: Option) { } // CHECK-LABEL: @test2( - // CHECK: switch i32 %1, label %bb1 [ + // CHECK: switch i32 %x.1, label %bb1 [ // CHECK: i32 10, label %bb5 // CHECK: i32 11, label %bb4 // CHECK: i32 13, label %bb3 diff --git a/tests/codegen/range-loop.rs b/tests/codegen/range-loop.rs index b131beb40dd1a..780eace10968a 100644 --- a/tests/codegen/range-loop.rs +++ b/tests/codegen/range-loop.rs @@ -14,7 +14,6 @@ pub fn call_for_zero_to_n(n: u32, f: fn(u32)) { // CHECK: start: // CHECK-NOT: alloca // CHECK: %[[IND:.+]] = alloca [4 x i8] - // CHECK-NEXT: %[[ALWAYS_SOME_OPTION:.+]] = alloca // CHECK-NOT: alloca // CHECK: store i32 0, ptr %[[IND]], // CHECK: br label %[[HEAD:.+]] @@ -31,10 +30,7 @@ pub fn call_for_zero_to_n(n: u32, f: fn(u32)) { // CHECK: %[[T2:.+]] = load i32, ptr %[[IND]], // CHECK: %[[T3:.+]] = add nuw i32 %[[T2]], 1 // CHECK: store i32 %[[T3]], ptr %[[IND]], - - // CHECK: store i32 %[[T2]] - // CHECK: %[[T4:.+]] = load i32 - // CHECK: call void %f(i32{{.+}}%[[T4]]) + // CHECK: call void %f(i32{{.+}}%[[T2]]) for i in 0..n { f(i); diff --git a/tests/codegen/try_question_mark_nop.rs b/tests/codegen/try_question_mark_nop.rs index 398c9a580bc30..a4c6906e071be 100644 --- a/tests/codegen/try_question_mark_nop.rs +++ b/tests/codegen/try_question_mark_nop.rs @@ -1,6 +1,7 @@ //@ compile-flags: -Copt-level=3 -Z merge-functions=disabled //@ edition: 2021 -//@ only-x86_64 +//@ only-64bit +//@ needs-deterministic-layouts (opposite scalar pair orders breaks it) //@ revisions: NINETEEN TWENTY //@[NINETEEN] exact-llvm-major-version: 19 //@[TWENTY] min-llvm-version: 20 @@ -11,18 +12,18 @@ use std::ops::ControlFlow::{self, Break, Continue}; use std::ptr::NonNull; -// CHECK-LABEL: @option_nop_match_32 +// CHECK-LABEL: @option_nop_match_32({{.+}} %x.0, {{.+}} %x.1) #[no_mangle] pub fn option_nop_match_32(x: Option) -> Option { // CHECK: start: - // CHECK-NEXT: [[TRUNC:%.*]] = trunc nuw i32 %0 to i1 + // CHECK-NEXT: [[TRUNC:%.*]] = trunc nuw i32 %x.0 to i1 // NINETEEN-NEXT: [[SELECT:%.*]] = select i1 [[TRUNC]], i32 %0, i32 0 // NINETEEN-NEXT: [[REG2:%.*]] = insertvalue { i32, i32 } poison, i32 [[SELECT]], 0 // NINETEEN-NEXT: [[REG3:%.*]] = insertvalue { i32, i32 } [[REG2]], i32 %1, 1 - // TWENTY-NEXT: [[SELECT:%.*]] = select i1 [[TRUNC]], i32 %1, i32 undef - // TWENTY-NEXT: [[REG2:%.*]] = insertvalue { i32, i32 } poison, i32 %0, 0 + // TWENTY-NEXT: [[SELECT:%.*]] = select i1 [[TRUNC]], i32 %x.1, i32 undef + // TWENTY-NEXT: [[REG2:%.*]] = insertvalue { i32, i32 } poison, i32 %x.0, 0 // TWENTY-NEXT: [[REG3:%.*]] = insertvalue { i32, i32 } [[REG2]], i32 [[SELECT]], 1 // CHECK-NEXT: ret { i32, i32 } [[REG3]] @@ -32,12 +33,12 @@ pub fn option_nop_match_32(x: Option) -> Option { } } -// CHECK-LABEL: @option_nop_traits_32 +// CHECK-LABEL: @option_nop_traits_32({{.+}} %x.0, {{.+}} %x.1) #[no_mangle] pub fn option_nop_traits_32(x: Option) -> Option { // CHECK: start: - // TWENTY-NEXT: %[[IS_SOME:.+]] = trunc nuw i32 %0 to i1 - // TWENTY-NEXT: select i1 %[[IS_SOME]], i32 %1, i32 undef + // TWENTY-NEXT: %[[IS_SOME:.+]] = trunc nuw i32 %x.0 to i1 + // TWENTY-NEXT: select i1 %[[IS_SOME]], i32 %x.1, i32 undef // CHECK-NEXT: insertvalue { i32, i32 } // CHECK-NEXT: insertvalue { i32, i32 } // CHECK-NEXT: ret { i32, i32 } @@ -90,18 +91,18 @@ pub fn control_flow_nop_traits_32(x: ControlFlow) -> ControlFlow) -> Option { // CHECK: start: - // CHECK-NEXT: [[TRUNC:%.*]] = trunc nuw i64 %0 to i1 + // CHECK-NEXT: [[TRUNC:%.*]] = trunc nuw i64 %x.0 to i1 // NINETEEN-NEXT: [[SELECT:%.*]] = select i1 [[TRUNC]], i64 %0, i64 0 // NINETEEN-NEXT: [[REG2:%.*]] = insertvalue { i64, i64 } poison, i64 [[SELECT]], 0 // NINETEEN-NEXT: [[REG3:%.*]] = insertvalue { i64, i64 } [[REG2]], i64 %1, 1 - // TWENTY-NEXT: [[SELECT:%.*]] = select i1 [[TRUNC]], i64 %1, i64 undef - // TWENTY-NEXT: [[REG2:%.*]] = insertvalue { i64, i64 } poison, i64 %0, 0 + // TWENTY-NEXT: [[SELECT:%.*]] = select i1 [[TRUNC]], i64 %x.1, i64 undef + // TWENTY-NEXT: [[REG2:%.*]] = insertvalue { i64, i64 } poison, i64 %x.0, 0 // TWENTY-NEXT: [[REG3:%.*]] = insertvalue { i64, i64 } [[REG2]], i64 [[SELECT]], 1 // CHECK-NEXT: ret { i64, i64 } [[REG3]] @@ -111,12 +112,12 @@ pub fn option_nop_match_64(x: Option) -> Option { } } -// CHECK-LABEL: @option_nop_traits_64 +// CHECK-LABEL: @option_nop_traits_64({{.+}} %x.0, {{.+}} %x.1) #[no_mangle] pub fn option_nop_traits_64(x: Option) -> Option { // CHECK: start: - // TWENTY-NEXT: %[[TRUNC:[0-9]+]] = trunc nuw i64 %0 to i1 - // TWENTY-NEXT: %[[SEL:\.[0-9]+]] = select i1 %[[TRUNC]], i64 %1, i64 undef + // TWENTY-NEXT: %[[TRUNC:.+]] = trunc nuw i64 %x.0 to i1 + // TWENTY-NEXT: %[[SEL:.+]] = select i1 %[[TRUNC]], i64 %x.1, i64 undef // CHECK-NEXT: insertvalue { i64, i64 } // CHECK-NEXT: insertvalue { i64, i64 } // CHECK-NEXT: ret { i64, i64 } @@ -219,13 +220,19 @@ pub fn control_flow_nop_traits_128(x: ControlFlow) -> ControlFlow>) -> Result> { + // This also has an assume that if it's `Err` the pointer is non-null + // (since it *can* be null in the `Ok` case) but for the purpose of this + // test that's ok as the returned value is just the inputs paired up. + // CHECK: start: - // CHECK-NEXT: insertvalue { i{{[0-9]+}}, ptr } - // CHECK-NEXT: insertvalue { i{{[0-9]+}}, ptr } - // CHECK-NEXT: ret + // CHECK-NOT: insertvalue + // CHECK-NOT: ret + // CHECK: %[[TEMP1:.+]] = insertvalue { i64, ptr } poison, i64 %x.0, 0 + // CHECK-NEXT: %[[TEMP2:.+]] = insertvalue { i64, ptr } %[[TEMP1]], ptr %x.1, 1 + // CHECK-NEXT: ret { i64, ptr } %[[TEMP2]] match x { Ok(x) => Ok(x), Err(x) => Err(x), @@ -236,8 +243,8 @@ pub fn result_nop_match_ptr(x: Result>) -> Result> #[no_mangle] pub fn result_nop_traits_ptr(x: Result>) -> Result> { // CHECK: start: - // CHECK-NEXT: insertvalue { i{{[0-9]+}}, ptr } - // CHECK-NEXT: insertvalue { i{{[0-9]+}}, ptr } + // CHECK-NEXT: insertvalue { i64, ptr } + // CHECK-NEXT: insertvalue { i64, ptr } // CHECK-NEXT: ret try { x? } } From bc074f1ec2aa5c82ba5a88740344612867178f66 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sun, 6 Jul 2025 18:37:46 -0700 Subject: [PATCH 2/2] PERF EXPERIMENT --- compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 26 +++++---- tests/codegen/array-cmp.rs | 10 +--- tests/codegen/enum/enum-extract.rs | 2 +- tests/codegen/enum/enum-match.rs | 3 +- tests/codegen/intrinsics/transmute-niched.rs | 58 +++++--------------- 5 files changed, 32 insertions(+), 67 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 43726e932520f..60d68d313684d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -1056,11 +1056,10 @@ pub(super) fn transmute_scalar<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( to_scalar: abi::Scalar, ) -> Bx::Value { assert_eq!(from_scalar.size(bx.cx()), to_scalar.size(bx.cx())); - let imm_ty = bx.cx().val_ty(imm); - assert_ne!( - bx.cx().type_kind(imm_ty), + debug_assert_ne!( + bx.cx().type_kind(bx.cx().val_ty(imm)), TypeKind::Vector, - "Vector type {imm_ty:?} not allowed in transmute_scalar {from_scalar:?} -> {to_scalar:?}" + "Vector type {imm:?} not allowed in transmute_scalar {from_scalar:?} -> {to_scalar:?}" ); // While optimizations will remove no-op transmutes, they might still be @@ -1086,7 +1085,9 @@ pub(super) fn transmute_scalar<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // That said, last time we tried removing this, it didn't actually help // the rustc-perf results, so might as well keep doing it // - assume_scalar_range(bx, imm, from_scalar, from_backend_ty); + // + // EXPERIMENT: What if we skip it for perf? + // assume_scalar_range(bx, imm, from_scalar, from_backend_ty); imm = match (from_scalar.primitive(), to_scalar.primitive()) { (Int(..) | Float(_), Int(..) | Float(_)) => bx.bitcast(imm, to_backend_ty), @@ -1109,12 +1110,15 @@ pub(super) fn transmute_scalar<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( debug_assert_eq!(bx.cx().val_ty(imm), to_backend_ty); - // This `assume` remains important for cases like (a conceptual) - // transmute::(x) == 0 - // since it's never passed to something with parameter metadata (especially - // after MIR inlining) so the only way to tell the backend about the - // constraint that the `transmute` introduced is to `assume` it. - assume_scalar_range(bx, imm, to_scalar, to_backend_ty); + // For bool we don't need the assert since `to_immediate_scalar` covers it. + if !to_scalar.is_bool() { + // This `assume` remains important for cases like (a conceptual) + // transmute::(x) == 0 + // since it's never passed to something with parameter metadata (especially + // after MIR inlining) so the only way to tell the backend about the + // constraint that the `transmute` introduced is to `assume` it. + assume_scalar_range(bx, imm, to_scalar, to_backend_ty); + } imm = bx.to_immediate_scalar(imm, to_scalar); imm diff --git a/tests/codegen/array-cmp.rs b/tests/codegen/array-cmp.rs index 30bdc686a7e14..0d33765540176 100644 --- a/tests/codegen/array-cmp.rs +++ b/tests/codegen/array-cmp.rs @@ -39,10 +39,6 @@ pub fn array_of_tuple_le(a: &[(i16, u16); 2], b: &[(i16, u16); 2]) -> bool { // CHECK: %[[EQ00:.+]] = icmp eq i16 %[[A00]], %[[B00]] // CHECK-NEXT: br i1 %[[EQ00]], label %[[L01:.+]], label %[[EXIT_S:.+]] - // CHECK: [[EXIT_S]]: - // CHECK: %[[RET_S:.+]] = icmp sle i16 - // CHECK: br label - // CHECK: [[L01]]: // CHECK: %[[PA01:.+]] = getelementptr{{.+}}i8, ptr %a, {{i32|i64}} 2 // CHECK: %[[PB01:.+]] = getelementptr{{.+}}i8, ptr %b, {{i32|i64}} 2 @@ -70,12 +66,8 @@ pub fn array_of_tuple_le(a: &[(i16, u16); 2], b: &[(i16, u16); 2]) -> bool { // CHECK: %[[EQ11:.+]] = icmp eq i16 %[[A11]], %[[B11]] // CHECK-NEXT: br i1 %[[EQ11]], label %[[DONE:.+]], label %[[EXIT_U]] - // CHECK: [[EXIT_U]]: - // CHECK: %[[RET_U:.+]] = icmp ule i16 - // CHECK: br label - // CHECK: [[DONE]]: - // CHECK: %[[RET:.+]] = phi i1 [ true, %[[L11]] ], [ %[[RET_S]], %[[EXIT_S]] ], [ %[[RET_U]], %[[EXIT_U]] ] + // CHECK: %[[RET:.+]] = phi i1 [ %{{.+}}, %[[EXIT_S]] ], [ %{{.+}}, %[[EXIT_U]] ], [ true, %[[L11]] ] // CHECK: ret i1 %[[RET]] a <= b diff --git a/tests/codegen/enum/enum-extract.rs b/tests/codegen/enum/enum-extract.rs index 285953313866b..3acd6f662ecaf 100644 --- a/tests/codegen/enum/enum-extract.rs +++ b/tests/codegen/enum/enum-extract.rs @@ -67,7 +67,7 @@ fn use_option_ordering(x: Option) -> Ordering { // CHECK: [[BLOCK]]: // OPT: %[[SHIFTED:.+]] = sub i8 %x, -1 - // OPT: %[[IN_WIDTH:.+]] = icmp ule i8 %[[SHIFTED]], 3 + // OPT: %[[IN_WIDTH:.+]] = icmp ule i8 %[[SHIFTED]], 2 // OPT: call void @llvm.assume(i1 %[[IN_WIDTH]]) // DBG-NOT: assume // CHECK: ret i8 %x diff --git a/tests/codegen/enum/enum-match.rs b/tests/codegen/enum/enum-match.rs index 9a835e76ed3b8..6638b023f2663 100644 --- a/tests/codegen/enum/enum-match.rs +++ b/tests/codegen/enum/enum-match.rs @@ -18,7 +18,8 @@ pub enum Enum0 { // CHECK-LABEL: define{{( dso_local)?}} noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @match0(i8{{.+}}%e) // CHECK-NEXT: start: // CHECK-NEXT: %[[IS_B:.+]] = icmp eq i8 %e, 2 -// CHECK-NEXT: %[[R:.+]] = select i1 %[[IS_B]], i8 13, i8 %e +// CHECK-NEXT: %[[TRUNC:.+]] = and i8 %e, 1 +// CHECK-NEXT: %[[R:.+]] = select i1 %[[IS_B]], i8 13, i8 %[[TRUNC]] // CHECK-NEXT: ret i8 %[[R]] #[no_mangle] pub fn match0(e: Enum0) -> u8 { diff --git a/tests/codegen/intrinsics/transmute-niched.rs b/tests/codegen/intrinsics/transmute-niched.rs index 8ff5cc8ee4f4c..59a7846126f37 100644 --- a/tests/codegen/intrinsics/transmute-niched.rs +++ b/tests/codegen/intrinsics/transmute-niched.rs @@ -33,11 +33,6 @@ pub unsafe fn check_to_enum(x: i8) -> SmallEnum { // CHECK-LABEL: @check_from_enum( #[no_mangle] pub unsafe fn check_from_enum(x: SmallEnum) -> i8 { - // CHECK-NOT: icmp - // CHECK-NOT: assume - // OPT: %0 = sub i8 %x, 10 - // OPT: %1 = icmp ule i8 %0, 2 - // OPT: call void @llvm.assume(i1 %1) // CHECK-NOT: icmp // CHECK-NOT: assume // CHECK: ret i8 %x @@ -63,11 +58,6 @@ pub unsafe fn check_to_ordering(x: u8) -> std::cmp::Ordering { // CHECK-LABEL: @check_from_ordering( #[no_mangle] pub unsafe fn check_from_ordering(x: std::cmp::Ordering) -> u8 { - // CHECK-NOT: icmp - // CHECK-NOT: assume - // OPT: %0 = sub i8 %x, -1 - // OPT: %1 = icmp ule i8 %0, 2 - // OPT: call void @llvm.assume(i1 %1) // CHECK-NOT: icmp // CHECK-NOT: assume // CHECK: ret i8 %x @@ -105,11 +95,9 @@ pub enum Minus100ToPlus100 { pub unsafe fn check_enum_from_char(x: char) -> Minus100ToPlus100 { // CHECK-NOT: icmp // CHECK-NOT: assume - // OPT: %0 = icmp ule i32 %x, 1114111 - // OPT: call void @llvm.assume(i1 %0) - // OPT: %1 = sub i32 %x, -100 - // OPT: %2 = icmp ule i32 %1, 200 - // OPT: call void @llvm.assume(i1 %2) + // OPT: %0 = sub i32 %x, -100 + // OPT: %1 = icmp ule i32 %0, 200 + // OPT: call void @llvm.assume(i1 %1) // CHECK-NOT: icmp // CHECK-NOT: assume // CHECK: ret i32 %x @@ -122,11 +110,8 @@ pub unsafe fn check_enum_from_char(x: char) -> Minus100ToPlus100 { pub unsafe fn check_enum_to_char(x: Minus100ToPlus100) -> char { // CHECK-NOT: icmp // CHECK-NOT: assume - // OPT: %0 = sub i32 %x, -100 - // OPT: %1 = icmp ule i32 %0, 200 - // OPT: call void @llvm.assume(i1 %1) - // OPT: %2 = icmp ule i32 %x, 1114111 - // OPT: call void @llvm.assume(i1 %2) + // OPT: %0 = icmp ule i32 %x, 1114111 + // OPT: call void @llvm.assume(i1 %0) // CHECK-NOT: icmp // CHECK-NOT: assume // CHECK: ret i32 %x @@ -139,16 +124,11 @@ pub unsafe fn check_enum_to_char(x: Minus100ToPlus100) -> char { pub unsafe fn check_swap_pair(x: (char, NonZero)) -> (NonZero, char) { // CHECK-NOT: icmp // CHECK-NOT: assume - // OPT: %0 = icmp ule i32 %x.0, 1114111 - // OPT: call void @llvm.assume(i1 %0) - // OPT: %1 = sub i32 %x.0, 1 - // OPT: %2 = icmp ule i32 %1, -2 + // OPT: %0 = sub i32 %x.0, 1 + // OPT: %1 = icmp ule i32 %0, -2 + // OPT: call void @llvm.assume(i1 %1) + // OPT: %2 = icmp ule i32 %x.1, 1114111 // OPT: call void @llvm.assume(i1 %2) - // OPT: %3 = sub i32 %x.1, 1 - // OPT: %4 = icmp ule i32 %3, -2 - // OPT: call void @llvm.assume(i1 %4) - // OPT: %5 = icmp ule i32 %x.1, 1114111 - // OPT: call void @llvm.assume(i1 %5) // CHECK-NOT: icmp // CHECK-NOT: assume // CHECK: %[[P1:.+]] = insertvalue { i32, i32 } poison, i32 %x.0, 0 @@ -161,13 +141,6 @@ pub unsafe fn check_swap_pair(x: (char, NonZero)) -> (NonZero, char) { // CHECK-LABEL: @check_bool_from_ordering( #[no_mangle] pub unsafe fn check_bool_from_ordering(x: std::cmp::Ordering) -> bool { - // CHECK-NOT: icmp - // CHECK-NOT: assume - // OPT: %0 = sub i8 %x, -1 - // OPT: %1 = icmp ule i8 %0, 2 - // OPT: call void @llvm.assume(i1 %1) - // OPT: %2 = icmp ule i8 %x, 1 - // OPT: call void @llvm.assume(i1 %2) // CHECK-NOT: icmp // CHECK-NOT: assume // CHECK: %[[R:.+]] = trunc{{( nuw)?}} i8 %x to i1 @@ -182,11 +155,9 @@ pub unsafe fn check_bool_to_ordering(x: bool) -> std::cmp::Ordering { // CHECK: %_0 = zext i1 %x to i8 // CHECK-NOT: icmp // CHECK-NOT: assume - // OPT: %0 = icmp ule i8 %_0, 1 - // OPT: call void @llvm.assume(i1 %0) - // OPT: %1 = sub i8 %_0, -1 - // OPT: %2 = icmp ule i8 %1, 2 - // OPT: call void @llvm.assume(i1 %2) + // OPT: %0 = sub i8 %_0, -1 + // OPT: %1 = icmp ule i8 %0, 2 + // OPT: call void @llvm.assume(i1 %1) // CHECK-NOT: icmp // CHECK-NOT: assume // CHECK: ret i8 %_0 @@ -197,10 +168,7 @@ pub unsafe fn check_bool_to_ordering(x: bool) -> std::cmp::Ordering { // CHECK-LABEL: @check_nonnull_to_ptr( #[no_mangle] pub unsafe fn check_nonnull_to_ptr(x: NonNull) -> *const u8 { - // CHECK-NOT: icmp - // CHECK-NOT: assume - // OPT: %0 = icmp ne ptr %x, null - // OPT: call void @llvm.assume(i1 %0) + // CHECK: start // CHECK-NOT: icmp // CHECK-NOT: assume // CHECK: ret ptr %x