diff --git a/compiler/rustc_codegen_cranelift/src/codegen_i128.rs b/compiler/rustc_codegen_cranelift/src/codegen_i128.rs index b2bc289a5b6ba..4a5ef352151f3 100644 --- a/compiler/rustc_codegen_cranelift/src/codegen_i128.rs +++ b/compiler/rustc_codegen_cranelift/src/codegen_i128.rs @@ -68,7 +68,7 @@ pub(crate) fn maybe_codegen<'tcx>( Some(CValue::by_val(ret_val, lhs.layout())) } } - BinOp::Lt | BinOp::Le | BinOp::Eq | BinOp::Ge | BinOp::Gt | BinOp::Ne => None, + BinOp::Lt | BinOp::Le | BinOp::Eq | BinOp::Ge | BinOp::Gt | BinOp::Ne | BinOp::Cmp => None, BinOp::Shl | BinOp::ShlUnchecked | BinOp::Shr | BinOp::ShrUnchecked => None, } } @@ -134,6 +134,7 @@ pub(crate) fn maybe_codegen_checked<'tcx>( BinOp::AddUnchecked | BinOp::SubUnchecked | BinOp::MulUnchecked => unreachable!(), BinOp::Offset => unreachable!("offset should only be used on pointers, not 128bit ints"), BinOp::Div | BinOp::Rem => unreachable!(), + BinOp::Cmp => unreachable!(), BinOp::Lt | BinOp::Le | BinOp::Eq | BinOp::Ge | BinOp::Gt | BinOp::Ne => unreachable!(), BinOp::Shl | BinOp::ShlUnchecked | BinOp::Shr | BinOp::ShrUnchecked => unreachable!(), } diff --git a/compiler/rustc_codegen_cranelift/src/num.rs b/compiler/rustc_codegen_cranelift/src/num.rs index 8992f40fb903a..94afa117b5436 100644 --- a/compiler/rustc_codegen_cranelift/src/num.rs +++ b/compiler/rustc_codegen_cranelift/src/num.rs @@ -40,6 +40,23 @@ pub(crate) fn bin_op_to_intcc(bin_op: BinOp, signed: bool) -> Option { }) } +fn codegen_three_way_compare<'tcx>( + fx: &mut FunctionCx<'_, '_, 'tcx>, + signed: bool, + lhs: Value, + rhs: Value, +) -> CValue<'tcx> { + let gt_cc = crate::num::bin_op_to_intcc(BinOp::Gt, signed).unwrap(); + let lt_cc = crate::num::bin_op_to_intcc(BinOp::Lt, signed).unwrap(); + let gt = fx.bcx.ins().icmp(gt_cc, lhs, rhs); + let lt = fx.bcx.ins().icmp(lt_cc, lhs, rhs); + // Cranelift no longer has a single-bit type, so the comparison results + // are already `I8`s, which we can subtract to get the -1/0/+1 we want. + // See . + let val = fx.bcx.ins().isub(gt, lt); + CValue::by_val(val, fx.layout_of(fx.tcx.ty_ordering_enum(Some(fx.mir.span)))) +} + fn codegen_compare_bin_op<'tcx>( fx: &mut FunctionCx<'_, '_, 'tcx>, bin_op: BinOp, @@ -47,6 +64,10 @@ fn codegen_compare_bin_op<'tcx>( lhs: Value, rhs: Value, ) -> CValue<'tcx> { + if bin_op == BinOp::Cmp { + return codegen_three_way_compare(fx, signed, lhs, rhs); + } + let intcc = crate::num::bin_op_to_intcc(bin_op, signed).unwrap(); let val = fx.bcx.ins().icmp(intcc, lhs, rhs); CValue::by_val(val, fx.layout_of(fx.tcx.types.bool)) @@ -59,7 +80,7 @@ pub(crate) fn codegen_binop<'tcx>( in_rhs: CValue<'tcx>, ) -> CValue<'tcx> { match bin_op { - BinOp::Eq | BinOp::Lt | BinOp::Le | BinOp::Ne | BinOp::Ge | BinOp::Gt => { + BinOp::Eq | BinOp::Lt | BinOp::Le | BinOp::Ne | BinOp::Ge | BinOp::Gt | BinOp::Cmp => { match in_lhs.layout().ty.kind() { ty::Bool | ty::Uint(_) | ty::Int(_) | ty::Char => { let signed = type_sign(in_lhs.layout().ty); @@ -160,7 +181,7 @@ pub(crate) fn codegen_int_binop<'tcx>( } BinOp::Offset => unreachable!("Offset is not an integer operation"), // Compare binops handles by `codegen_binop`. - BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => { + BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge | BinOp::Cmp => { unreachable!("{:?}({:?}, {:?})", bin_op, in_lhs.layout().ty, in_rhs.layout().ty); } }; diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 93fe27e547aef..cab1f8201feea 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -104,6 +104,10 @@ impl<'gcc, 'tcx> ConstMethods<'tcx> for CodegenCx<'gcc, 'tcx> { self.const_int(self.type_i32(), i as i64) } + fn const_i8(&self, i: i8) -> RValue<'gcc> { + self.const_int(self.type_i8(), i as i64) + } + fn const_u32(&self, i: u32) -> RValue<'gcc> { self.const_uint(self.type_u32(), i as u64) } diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index d1b643f49677a..42c2cb99cf7d9 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -158,6 +158,10 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> { self.const_int(self.type_i32(), i as i64) } + fn const_i8(&self, i: i8) -> &'ll Value { + self.const_int(self.type_i8(), i as i64) + } + fn const_u32(&self, i: u32) -> &'ll Value { self.const_uint(self.type_i32(), i as u64) } diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 02b51dfe5bf7f..7071091befb6b 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -7,6 +7,7 @@ use crate::common::{self, IntPredicate}; use crate::traits::*; use crate::MemFlags; +use rustc_hir as hir; use rustc_middle::mir; use rustc_middle::mir::Operand; use rustc_middle::ty::cast::{CastTy, IntTy}; @@ -881,6 +882,27 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx.icmp(base::bin_op_to_icmp_predicate(op.to_hir_binop(), is_signed), lhs, rhs) } } + mir::BinOp::Cmp => { + use std::cmp::Ordering; + debug_assert!(!is_float); + // FIXME: To avoid this PR changing behaviour, the operations used + // here are those from , + // as tested by `tests/codegen/integer-cmp.rs`. + // Something in future might want to pick different ones. For example, + // maybe the ones from Clang's `<=>` operator in C++20 (see + // ) or once we + // update to new LLVM, something to take advantage of the new folds in + // . + let pred = |op| base::bin_op_to_icmp_predicate(op, is_signed); + let is_lt = bx.icmp(pred(hir::BinOpKind::Lt), lhs, rhs); + let is_ne = bx.icmp(pred(hir::BinOpKind::Ne), lhs, rhs); + let ge = bx.select( + is_ne, + bx.cx().const_i8(Ordering::Greater as i8), + bx.cx().const_i8(Ordering::Equal as i8), + ); + bx.select(is_lt, bx.cx().const_i8(Ordering::Less as i8), ge) + } } } diff --git a/compiler/rustc_codegen_ssa/src/traits/consts.rs b/compiler/rustc_codegen_ssa/src/traits/consts.rs index 4dff9c7684f18..8cb17a5b37a89 100644 --- a/compiler/rustc_codegen_ssa/src/traits/consts.rs +++ b/compiler/rustc_codegen_ssa/src/traits/consts.rs @@ -19,6 +19,7 @@ pub trait ConstMethods<'tcx>: BackendTypes { fn const_bool(&self, val: bool) -> Self::Value; fn const_i16(&self, i: i16) -> Self::Value; fn const_i32(&self, i: i32) -> Self::Value; + fn const_i8(&self, i: i8) -> Self::Value; fn const_u32(&self, i: u32) -> Self::Value; fn const_u64(&self, i: u64) -> Self::Value; fn const_u128(&self, i: u128) -> Self::Value; diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index 255dd1eba97f2..48461c9d5d402 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -224,6 +224,13 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> { Self::from_scalar(Scalar::from_bool(b), layout) } + #[inline] + pub fn from_ordering(c: std::cmp::Ordering, tcx: TyCtxt<'tcx>) -> Self { + let ty = tcx.ty_ordering_enum(None); + let layout = tcx.layout_of(ty::ParamEnv::reveal_all().and(ty)).unwrap(); + Self::from_scalar(Scalar::from_i8(c as i8), layout) + } + #[inline] pub fn to_const_int(self) -> ConstInt { assert!(self.layout.ty.is_integral()); diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs index eef1542576466..d0f95d84f64b0 100644 --- a/compiler/rustc_const_eval/src/interpret/operator.rs +++ b/compiler/rustc_const_eval/src/interpret/operator.rs @@ -61,6 +61,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { + fn three_way_compare(&self, lhs: T, rhs: T) -> (ImmTy<'tcx, M::Provenance>, bool) { + let res = Ord::cmp(&lhs, &rhs); + return (ImmTy::from_ordering(res, *self.tcx), false); + } + fn binary_char_op( &self, bin_op: mir::BinOp, @@ -69,6 +74,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ) -> (ImmTy<'tcx, M::Provenance>, bool) { use rustc_middle::mir::BinOp::*; + if bin_op == Cmp { + return self.three_way_compare(l, r); + } + let res = match bin_op { Eq => l == r, Ne => l != r, @@ -231,6 +240,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let r = self.sign_extend(r, right_layout) as i128; return Ok((ImmTy::from_bool(op(&l, &r), *self.tcx), false)); } + if bin_op == Cmp { + let l = self.sign_extend(l, left_layout) as i128; + let r = self.sign_extend(r, right_layout) as i128; + return Ok(self.three_way_compare(l, r)); + } let op: Option (i128, bool)> = match bin_op { Div if r == 0 => throw_ub!(DivisionByZero), Rem if r == 0 => throw_ub!(RemainderByZero), @@ -270,6 +284,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } + if bin_op == Cmp { + return Ok(self.three_way_compare(l, r)); + } + let val = match bin_op { Eq => ImmTy::from_bool(l == r, *self.tcx), Ne => ImmTy::from_bool(l != r, *self.tcx), diff --git a/compiler/rustc_const_eval/src/transform/promote_consts.rs b/compiler/rustc_const_eval/src/transform/promote_consts.rs index 8b2ea2dc21dd1..1f8956895e4f2 100644 --- a/compiler/rustc_const_eval/src/transform/promote_consts.rs +++ b/compiler/rustc_const_eval/src/transform/promote_consts.rs @@ -573,6 +573,7 @@ impl<'tcx> Validator<'_, 'tcx> { | BinOp::Lt | BinOp::Ge | BinOp::Gt + | BinOp::Cmp | BinOp::Offset | BinOp::Add | BinOp::AddUnchecked diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs index 3e8a0a2b7df08..9ba2f317791db 100644 --- a/compiler/rustc_const_eval/src/transform/validate.rs +++ b/compiler/rustc_const_eval/src/transform/validate.rs @@ -932,6 +932,15 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ) } } + Cmp => { + for x in [a, b] { + check_kinds!( + x, + "Cannot three-way compare non-integer type {:?}", + ty::Char | ty::Uint(..) | ty::Int(..) + ) + } + } AddUnchecked | SubUnchecked | MulUnchecked | Shl | ShlUnchecked | Shr | ShrUnchecked => { for x in [a, b] { diff --git a/compiler/rustc_const_eval/src/util/mod.rs b/compiler/rustc_const_eval/src/util/mod.rs index 1e58bd645cdde..475dfad61e4df 100644 --- a/compiler/rustc_const_eval/src/util/mod.rs +++ b/compiler/rustc_const_eval/src/util/mod.rs @@ -19,7 +19,7 @@ pub(crate) fn binop_left_homogeneous(op: mir::BinOp) -> bool { match op { Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor | BitAnd | BitOr | Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => true, - Eq | Ne | Lt | Le | Gt | Ge => false, + Eq | Ne | Lt | Le | Gt | Ge | Cmp => false, } } @@ -30,7 +30,7 @@ pub(crate) fn binop_right_homogeneous(op: mir::BinOp) -> bool { use rustc_middle::mir::BinOp::*; match op { Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor - | BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge => true, + | BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge | Cmp => true, Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => false, } } diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 60f1449c177cb..ae293869320a5 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -217,6 +217,7 @@ language_item_table! { Unpin, sym::unpin, unpin_trait, Target::Trait, GenericRequirement::None; Pin, sym::pin, pin_type, Target::Struct, GenericRequirement::None; + OrderingEnum, sym::Ordering, ordering_enum, Target::Enum, GenericRequirement::Exact(0); PartialEq, sym::eq, eq_trait, Target::Trait, GenericRequirement::Exact(1); PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1); CVoid, sym::c_void, c_void, Target::Enum, GenericRequirement::None; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 7ea21b24fc821..cfd825186300d 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -98,6 +98,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: DefId) -> hir | sym::cttz | sym::bswap | sym::bitreverse + | sym::three_way_compare | sym::discriminant_value | sym::type_id | sym::likely @@ -325,6 +326,10 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { | sym::bswap | sym::bitreverse => (1, vec![param(0)], param(0)), + sym::three_way_compare => { + (1, vec![param(0), param(0)], tcx.ty_ordering_enum(Some(it.span))) + } + sym::add_with_overflow | sym::sub_with_overflow | sym::mul_with_overflow => { (1, vec![param(0), param(0)], Ty::new_tup(tcx, &[param(0), tcx.types.bool])) } diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 8cf9e55f0b603..e2941fb31ab6d 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1429,6 +1429,8 @@ pub enum BinOp { Ge, /// The `>` operator (greater than) Gt, + /// The `<=>` operator (three-way comparison, like `Ord::cmp`) + Cmp, /// The `ptr.offset` operator Offset, } diff --git a/compiler/rustc_middle/src/mir/tcx.rs b/compiler/rustc_middle/src/mir/tcx.rs index 0fc12586948d8..a5c5a74a27baf 100644 --- a/compiler/rustc_middle/src/mir/tcx.rs +++ b/compiler/rustc_middle/src/mir/tcx.rs @@ -264,6 +264,11 @@ impl<'tcx> BinOp { &BinOp::Eq | &BinOp::Lt | &BinOp::Le | &BinOp::Ne | &BinOp::Ge | &BinOp::Gt => { tcx.types.bool } + &BinOp::Cmp => { + // these should be integer-like types of the same size. + assert_eq!(lhs_ty, rhs_ty); + tcx.ty_ordering_enum(None) + } } } } @@ -300,7 +305,8 @@ impl BinOp { BinOp::Gt => hir::BinOpKind::Gt, BinOp::Le => hir::BinOpKind::Le, BinOp::Ge => hir::BinOpKind::Ge, - BinOp::AddUnchecked + BinOp::Cmp + | BinOp::AddUnchecked | BinOp::SubUnchecked | BinOp::MulUnchecked | BinOp::ShlUnchecked diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index df5ac28130a71..7475c6da1de4c 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -784,6 +784,13 @@ impl<'tcx> TyCtxt<'tcx> { self.get_lang_items(()) } + /// Gets a `Ty` representing the [`LangItem::OrderingEnum`] + #[track_caller] + pub fn ty_ordering_enum(self, span: Option) -> Ty<'tcx> { + let ordering_enum = self.require_lang_item(hir::LangItem::OrderingEnum, span); + self.type_of(ordering_enum).no_bound_vars().unwrap() + } + /// Obtain the given diagnostic item's `DefId`. Use `is_diagnostic_item` if you just want to /// compare against another `DefId`, since `is_diagnostic_item` is cheaper. pub fn get_diagnostic_item(self, name: Symbol) -> Option { diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index 249e0fc633e88..d7375e25e3b67 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -81,6 +81,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { sym::wrapping_add | sym::wrapping_sub | sym::wrapping_mul + | sym::three_way_compare | sym::unchecked_add | sym::unchecked_sub | sym::unchecked_mul @@ -100,6 +101,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { sym::wrapping_add => BinOp::Add, sym::wrapping_sub => BinOp::Sub, sym::wrapping_mul => BinOp::Mul, + sym::three_way_compare => BinOp::Cmp, sym::unchecked_add => BinOp::AddUnchecked, sym::unchecked_sub => BinOp::SubUnchecked, sym::unchecked_mul => BinOp::MulUnchecked, diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs index 62a26bc089ad8..9f84caea0cd75 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -490,6 +490,7 @@ impl<'tcx> Stable<'tcx> for mir::BinOp { BinOp::Ne => stable_mir::mir::BinOp::Ne, BinOp::Ge => stable_mir::mir::BinOp::Ge, BinOp::Gt => stable_mir::mir::BinOp::Gt, + BinOp::Cmp => stable_mir::mir::BinOp::Cmp, BinOp::Offset => stable_mir::mir::BinOp::Offset, } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 40b0387424221..25a96d7d86a6b 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1658,6 +1658,7 @@ symbols! { thread, thread_local, thread_local_macro, + three_way_compare, thumb2, thumb_mode: "thumb-mode", tmm_reg, diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs index b521a5c1145f6..6a5474a3c7252 100644 --- a/compiler/rustc_ty_utils/src/consts.rs +++ b/compiler/rustc_ty_utils/src/consts.rs @@ -82,7 +82,7 @@ fn check_binop(op: mir::BinOp) -> bool { match op { Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor | BitAnd | BitOr | Shl | ShlUnchecked | Shr | ShrUnchecked | Eq | Lt | Le | Ne | Ge - | Gt => true, + | Gt | Cmp => true, Offset => false, } } diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs index 8d237fc9f1d95..0be661ab2ab4e 100644 --- a/compiler/stable_mir/src/mir/body.rs +++ b/compiler/stable_mir/src/mir/body.rs @@ -208,6 +208,7 @@ pub enum BinOp { Ne, Ge, Gt, + Cmp, Offset, } diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs index fadf2fcc9fce6..a97a71097c0ee 100644 --- a/library/core/src/cmp.rs +++ b/library/core/src/cmp.rs @@ -352,6 +352,7 @@ pub struct AssertParamIsEq { /// ``` #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] #[stable(feature = "rust1", since = "1.0.0")] +#[cfg_attr(not(bootstrap), lang = "Ordering")] #[repr(i8)] pub enum Ordering { /// An ordering where a compared value is less than another. @@ -1513,12 +1514,19 @@ mod impls { impl Ord for $t { #[inline] fn cmp(&self, other: &$t) -> Ordering { + #[cfg(bootstrap)] + { // The order here is important to generate more optimal assembly. // See for more info. if *self < *other { Less } else if *self == *other { Equal } else { Greater } } + #[cfg(not(bootstrap))] + { + crate::intrinsics::three_way_compare(*self, *other) + } + } } )*) } diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index f25ca9e2b1866..24c4fddeb1665 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2033,6 +2033,18 @@ extern "rust-intrinsic" { #[rustc_nounwind] pub fn bitreverse(x: T) -> T; + /// Does a three-way comparison between the two integer arguments. + /// + /// This is included as an intrinsic as it's useful to let it be one thing + /// in MIR, rather than the multiple checks and switches that make its IR + /// large and difficult to optimize. + /// + /// The stabilized version of this intrinsic is [`Ord::cmp`]. + #[cfg(not(bootstrap))] + #[rustc_const_unstable(feature = "const_three_way_compare", issue = "none")] + #[rustc_safe_intrinsic] + pub fn three_way_compare(lhs: T, rhs: T) -> crate::cmp::Ordering; + /// Performs checked integer addition. /// /// Note that, unlike most intrinsics, this is safe to call; diff --git a/library/core/tests/intrinsics.rs b/library/core/tests/intrinsics.rs index 06870c6d06cbe..b0e88a6ef0fa3 100644 --- a/library/core/tests/intrinsics.rs +++ b/library/core/tests/intrinsics.rs @@ -99,3 +99,22 @@ fn test_const_deallocate_at_runtime() { const_deallocate(core::ptr::null_mut(), 1, 1); // nop } } + +#[cfg(not(bootstrap))] +#[test] +fn test_three_way_compare_in_const_contexts() { + use core::cmp::Ordering::*; + use core::intrinsics::three_way_compare; + + const { + assert!(Less as i8 == three_way_compare(123_u16, 456) as _); + assert!(Equal as i8 == three_way_compare(456_u16, 456) as _); + assert!(Greater as i8 == three_way_compare(789_u16, 456) as _); + assert!(Less as i8 == three_way_compare('A', 'B') as _); + assert!(Equal as i8 == three_way_compare('B', 'B') as _); + assert!(Greater as i8 == three_way_compare('C', 'B') as _); + assert!(Less as i8 == three_way_compare(-123_i16, 456) as _); + assert!(Equal as i8 == three_way_compare(456_i16, 456) as _); + assert!(Greater as i8 == three_way_compare(123_i16, -456) as _); + } +} diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 3fbb058b09ba0..83929361d4371 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -19,6 +19,7 @@ #![feature(const_pointer_is_aligned)] #![feature(const_ptr_as_ref)] #![feature(const_ptr_write)] +#![cfg_attr(not(bootstrap), feature(const_three_way_compare))] #![feature(const_trait_impl)] #![feature(const_likely)] #![feature(const_location_fields)] diff --git a/tests/codegen/integer-cmp-raw.rs b/tests/codegen/integer-cmp-raw.rs new file mode 100644 index 0000000000000..00fa037e5a07e --- /dev/null +++ b/tests/codegen/integer-cmp-raw.rs @@ -0,0 +1,30 @@ +// This is test for more optimal Ord implementation for integers. +// See for more info. + +// compile-flags: -C opt-level=3 -C no-prepopulate-passes + +#![crate_type = "lib"] + +use std::cmp::Ordering; + +// CHECK-LABEL: @cmp_signed +#[no_mangle] +pub fn cmp_signed(a: i64, b: i64) -> Ordering { +// CHECK: %[[LT:.+]] = icmp slt i64 +// CHECK: %[[NE:.+]] = icmp ne i64 +// CHECK: %[[CGE:.+]] = select i1 %[[NE]], i8 1, i8 0 +// CHECK: %[[CGEL:.+]] = select i1 %[[LT]], i8 -1, i8 %[[CGE]] +// CHECK: ret i8 %[[CGEL]] + a.cmp(&b) +} + +// CHECK-LABEL: @cmp_unsigned +#[no_mangle] +pub fn cmp_unsigned(a: u32, b: u32) -> Ordering { +// CHECK: %[[LT:.+]] = icmp ult i32 +// CHECK: %[[NE:.+]] = icmp ne i32 +// CHECK: %[[CGE:.+]] = select i1 %[[NE]], i8 1, i8 0 +// CHECK: %[[CGEL:.+]] = select i1 %[[LT]], i8 -1, i8 %[[CGE]] +// CHECK: ret i8 %[[CGEL]] + a.cmp(&b) +} diff --git a/tests/mir-opt/lower_intrinsics.rs b/tests/mir-opt/lower_intrinsics.rs index cba2bc18d867b..e3c136f48546b 100644 --- a/tests/mir-opt/lower_intrinsics.rs +++ b/tests/mir-opt/lower_intrinsics.rs @@ -229,3 +229,18 @@ pub unsafe fn ptr_offset(p: *const i32, d: isize) -> *const i32 { core::intrinsics::offset(p, d) } + +// EMIT_MIR lower_intrinsics.three_way_compare_char.LowerIntrinsics.diff +pub fn three_way_compare_char(a: char, b: char) { + let _x = core::intrinsics::three_way_compare(a, b); +} + +// EMIT_MIR lower_intrinsics.three_way_compare_signed.LowerIntrinsics.diff +pub fn three_way_compare_signed(a: i16, b: i16) { + core::intrinsics::three_way_compare(a, b); +} + +// EMIT_MIR lower_intrinsics.three_way_compare_unsigned.LowerIntrinsics.diff +pub fn three_way_compare_unsigned(a: u32, b: u32) { + let _x = core::intrinsics::three_way_compare(a, b); +} diff --git a/tests/mir-opt/lower_intrinsics.three_way_compare_char.LowerIntrinsics.panic-abort.diff b/tests/mir-opt/lower_intrinsics.three_way_compare_char.LowerIntrinsics.panic-abort.diff new file mode 100644 index 0000000000000..816d620971588 --- /dev/null +++ b/tests/mir-opt/lower_intrinsics.three_way_compare_char.LowerIntrinsics.panic-abort.diff @@ -0,0 +1,34 @@ +- // MIR for `three_way_compare_char` before LowerIntrinsics ++ // MIR for `three_way_compare_char` after LowerIntrinsics + + fn three_way_compare_char(_1: char, _2: char) -> () { + debug a => _1; + debug b => _2; + let mut _0: (); + let _3: std::cmp::Ordering; + let mut _4: char; + let mut _5: char; + scope 1 { + debug _x => _3; + } + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = _1; + StorageLive(_5); + _5 = _2; +- _3 = three_way_compare::(move _4, move _5) -> [return: bb1, unwind unreachable]; ++ _3 = Cmp(move _4, move _5); ++ goto -> bb1; + } + + bb1: { + StorageDead(_5); + StorageDead(_4); + _0 = const (); + StorageDead(_3); + return; + } + } + diff --git a/tests/mir-opt/lower_intrinsics.three_way_compare_char.LowerIntrinsics.panic-unwind.diff b/tests/mir-opt/lower_intrinsics.three_way_compare_char.LowerIntrinsics.panic-unwind.diff new file mode 100644 index 0000000000000..80b4bd7a2be75 --- /dev/null +++ b/tests/mir-opt/lower_intrinsics.three_way_compare_char.LowerIntrinsics.panic-unwind.diff @@ -0,0 +1,34 @@ +- // MIR for `three_way_compare_char` before LowerIntrinsics ++ // MIR for `three_way_compare_char` after LowerIntrinsics + + fn three_way_compare_char(_1: char, _2: char) -> () { + debug a => _1; + debug b => _2; + let mut _0: (); + let _3: std::cmp::Ordering; + let mut _4: char; + let mut _5: char; + scope 1 { + debug _x => _3; + } + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = _1; + StorageLive(_5); + _5 = _2; +- _3 = three_way_compare::(move _4, move _5) -> [return: bb1, unwind continue]; ++ _3 = Cmp(move _4, move _5); ++ goto -> bb1; + } + + bb1: { + StorageDead(_5); + StorageDead(_4); + _0 = const (); + StorageDead(_3); + return; + } + } + diff --git a/tests/mir-opt/lower_intrinsics.three_way_compare_signed.LowerIntrinsics.panic-abort.diff b/tests/mir-opt/lower_intrinsics.three_way_compare_signed.LowerIntrinsics.panic-abort.diff new file mode 100644 index 0000000000000..05c20aaa09a27 --- /dev/null +++ b/tests/mir-opt/lower_intrinsics.three_way_compare_signed.LowerIntrinsics.panic-abort.diff @@ -0,0 +1,31 @@ +- // MIR for `three_way_compare_signed` before LowerIntrinsics ++ // MIR for `three_way_compare_signed` after LowerIntrinsics + + fn three_way_compare_signed(_1: i16, _2: i16) -> () { + debug a => _1; + debug b => _2; + let mut _0: (); + let _3: std::cmp::Ordering; + let mut _4: i16; + let mut _5: i16; + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = _1; + StorageLive(_5); + _5 = _2; +- _3 = three_way_compare::(move _4, move _5) -> [return: bb1, unwind unreachable]; ++ _3 = Cmp(move _4, move _5); ++ goto -> bb1; + } + + bb1: { + StorageDead(_5); + StorageDead(_4); + StorageDead(_3); + _0 = const (); + return; + } + } + diff --git a/tests/mir-opt/lower_intrinsics.three_way_compare_signed.LowerIntrinsics.panic-unwind.diff b/tests/mir-opt/lower_intrinsics.three_way_compare_signed.LowerIntrinsics.panic-unwind.diff new file mode 100644 index 0000000000000..8a254d02a47b2 --- /dev/null +++ b/tests/mir-opt/lower_intrinsics.three_way_compare_signed.LowerIntrinsics.panic-unwind.diff @@ -0,0 +1,31 @@ +- // MIR for `three_way_compare_signed` before LowerIntrinsics ++ // MIR for `three_way_compare_signed` after LowerIntrinsics + + fn three_way_compare_signed(_1: i16, _2: i16) -> () { + debug a => _1; + debug b => _2; + let mut _0: (); + let _3: std::cmp::Ordering; + let mut _4: i16; + let mut _5: i16; + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = _1; + StorageLive(_5); + _5 = _2; +- _3 = three_way_compare::(move _4, move _5) -> [return: bb1, unwind continue]; ++ _3 = Cmp(move _4, move _5); ++ goto -> bb1; + } + + bb1: { + StorageDead(_5); + StorageDead(_4); + StorageDead(_3); + _0 = const (); + return; + } + } + diff --git a/tests/mir-opt/lower_intrinsics.three_way_compare_unsigned.LowerIntrinsics.panic-abort.diff b/tests/mir-opt/lower_intrinsics.three_way_compare_unsigned.LowerIntrinsics.panic-abort.diff new file mode 100644 index 0000000000000..437614ec6738d --- /dev/null +++ b/tests/mir-opt/lower_intrinsics.three_way_compare_unsigned.LowerIntrinsics.panic-abort.diff @@ -0,0 +1,34 @@ +- // MIR for `three_way_compare_unsigned` before LowerIntrinsics ++ // MIR for `three_way_compare_unsigned` after LowerIntrinsics + + fn three_way_compare_unsigned(_1: u32, _2: u32) -> () { + debug a => _1; + debug b => _2; + let mut _0: (); + let _3: std::cmp::Ordering; + let mut _4: u32; + let mut _5: u32; + scope 1 { + debug _x => _3; + } + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = _1; + StorageLive(_5); + _5 = _2; +- _3 = three_way_compare::(move _4, move _5) -> [return: bb1, unwind unreachable]; ++ _3 = Cmp(move _4, move _5); ++ goto -> bb1; + } + + bb1: { + StorageDead(_5); + StorageDead(_4); + _0 = const (); + StorageDead(_3); + return; + } + } + diff --git a/tests/mir-opt/lower_intrinsics.three_way_compare_unsigned.LowerIntrinsics.panic-unwind.diff b/tests/mir-opt/lower_intrinsics.three_way_compare_unsigned.LowerIntrinsics.panic-unwind.diff new file mode 100644 index 0000000000000..7d6137979c81b --- /dev/null +++ b/tests/mir-opt/lower_intrinsics.three_way_compare_unsigned.LowerIntrinsics.panic-unwind.diff @@ -0,0 +1,34 @@ +- // MIR for `three_way_compare_unsigned` before LowerIntrinsics ++ // MIR for `three_way_compare_unsigned` after LowerIntrinsics + + fn three_way_compare_unsigned(_1: u32, _2: u32) -> () { + debug a => _1; + debug b => _2; + let mut _0: (); + let _3: std::cmp::Ordering; + let mut _4: u32; + let mut _5: u32; + scope 1 { + debug _x => _3; + } + + bb0: { + StorageLive(_3); + StorageLive(_4); + _4 = _1; + StorageLive(_5); + _5 = _2; +- _3 = three_way_compare::(move _4, move _5) -> [return: bb1, unwind continue]; ++ _3 = Cmp(move _4, move _5); ++ goto -> bb1; + } + + bb1: { + StorageDead(_5); + StorageDead(_4); + _0 = const (); + StorageDead(_3); + return; + } + } + diff --git a/tests/mir-opt/pre-codegen/derived_ord.rs b/tests/mir-opt/pre-codegen/derived_ord.rs new file mode 100644 index 0000000000000..63b479cf20b51 --- /dev/null +++ b/tests/mir-opt/pre-codegen/derived_ord.rs @@ -0,0 +1,9 @@ +// skip-filecheck +// compile-flags: -O -Zmir-opt-level=2 -Cdebuginfo=2 + +#![crate_type = "lib"] + +#[derive(PartialOrd, PartialEq)] +pub struct MultiField(u64, char, i16); + +// EMIT_MIR derived_ord.{impl#0}-partial_cmp.PreCodegen.after.mir diff --git a/tests/mir-opt/pre-codegen/derived_ord.{impl#0}-partial_cmp.PreCodegen.after.mir b/tests/mir-opt/pre-codegen/derived_ord.{impl#0}-partial_cmp.PreCodegen.after.mir new file mode 100644 index 0000000000000..1136d2bd8889e --- /dev/null +++ b/tests/mir-opt/pre-codegen/derived_ord.{impl#0}-partial_cmp.PreCodegen.after.mir @@ -0,0 +1,144 @@ +// MIR for `::partial_cmp` after PreCodegen + +fn ::partial_cmp(_1: &MultiField, _2: &MultiField) -> Option { + debug self => _1; + debug other => _2; + let mut _0: std::option::Option; + let mut _3: &u64; + let mut _4: &u64; + let mut _8: std::option::Option; + let mut _9: isize; + let mut _10: i8; + let mut _11: &char; + let mut _12: &char; + let mut _16: std::option::Option; + let mut _17: isize; + let mut _18: i8; + let mut _19: &i16; + let mut _20: &i16; + scope 1 { + debug cmp => _16; + } + scope 2 { + debug cmp => _8; + } + scope 3 (inlined cmp::impls::::partial_cmp) { + debug self => _3; + debug other => _4; + let mut _7: std::cmp::Ordering; + scope 4 (inlined cmp::impls::::cmp) { + debug self => _3; + debug other => _4; + let mut _5: u64; + let mut _6: u64; + } + } + scope 5 (inlined cmp::impls::::partial_cmp) { + debug self => _11; + debug other => _12; + let mut _15: std::cmp::Ordering; + scope 6 (inlined cmp::impls::::cmp) { + debug self => _11; + debug other => _12; + let mut _13: char; + let mut _14: char; + } + } + scope 7 (inlined cmp::impls::::partial_cmp) { + debug self => _19; + debug other => _20; + let mut _23: std::cmp::Ordering; + scope 8 (inlined cmp::impls::::cmp) { + debug self => _19; + debug other => _20; + let mut _21: i16; + let mut _22: i16; + } + } + + bb0: { + StorageLive(_3); + _3 = &((*_1).0: u64); + StorageLive(_4); + _4 = &((*_2).0: u64); + StorageLive(_7); + StorageLive(_5); + _5 = ((*_1).0: u64); + StorageLive(_6); + _6 = ((*_2).0: u64); + _7 = Cmp(move _5, move _6); + StorageDead(_6); + StorageDead(_5); + _8 = Option::::Some(move _7); + StorageDead(_7); + StorageDead(_4); + StorageDead(_3); + _9 = discriminant(_8); + switchInt(move _9) -> [1: bb1, otherwise: bb6]; + } + + bb1: { + _10 = discriminant(((_8 as Some).0: std::cmp::Ordering)); + switchInt(move _10) -> [0: bb2, otherwise: bb6]; + } + + bb2: { + StorageLive(_11); + _11 = &((*_1).1: char); + StorageLive(_12); + _12 = &((*_2).1: char); + StorageLive(_15); + StorageLive(_13); + _13 = ((*_1).1: char); + StorageLive(_14); + _14 = ((*_2).1: char); + _15 = Cmp(move _13, move _14); + StorageDead(_14); + StorageDead(_13); + _16 = Option::::Some(move _15); + StorageDead(_15); + StorageDead(_12); + StorageDead(_11); + _17 = discriminant(_16); + switchInt(move _17) -> [1: bb3, otherwise: bb5]; + } + + bb3: { + _18 = discriminant(((_16 as Some).0: std::cmp::Ordering)); + switchInt(move _18) -> [0: bb4, otherwise: bb5]; + } + + bb4: { + StorageLive(_19); + _19 = &((*_1).2: i16); + StorageLive(_20); + _20 = &((*_2).2: i16); + StorageLive(_23); + StorageLive(_21); + _21 = ((*_1).2: i16); + StorageLive(_22); + _22 = ((*_2).2: i16); + _23 = Cmp(move _21, move _22); + StorageDead(_22); + StorageDead(_21); + _0 = Option::::Some(move _23); + StorageDead(_23); + StorageDead(_20); + StorageDead(_19); + goto -> bb7; + } + + bb5: { + _0 = _16; + goto -> bb7; + } + + bb6: { + _0 = _8; + goto -> bb7; + } + + bb7: { + return; + } +}