Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2353a92

Browse files
committedNov 26, 2023
Add mir::BinOp::Cmp
1 parent 7b56ce0 commit 2353a92

34 files changed

+480
-221
lines changed
 

‎compiler/rustc_codegen_cranelift/src/codegen_i128.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub(crate) fn maybe_codegen<'tcx>(
6868
Some(CValue::by_val(ret_val, lhs.layout()))
6969
}
7070
}
71-
BinOp::Lt | BinOp::Le | BinOp::Eq | BinOp::Ge | BinOp::Gt | BinOp::Ne => None,
71+
BinOp::Lt | BinOp::Le | BinOp::Eq | BinOp::Ge | BinOp::Gt | BinOp::Ne | BinOp::Cmp => None,
7272
BinOp::Shl | BinOp::ShlUnchecked | BinOp::Shr | BinOp::ShrUnchecked => None,
7373
}
7474
}
@@ -134,6 +134,7 @@ pub(crate) fn maybe_codegen_checked<'tcx>(
134134
BinOp::AddUnchecked | BinOp::SubUnchecked | BinOp::MulUnchecked => unreachable!(),
135135
BinOp::Offset => unreachable!("offset should only be used on pointers, not 128bit ints"),
136136
BinOp::Div | BinOp::Rem => unreachable!(),
137+
BinOp::Cmp => unreachable!(),
137138
BinOp::Lt | BinOp::Le | BinOp::Eq | BinOp::Ge | BinOp::Gt | BinOp::Ne => unreachable!(),
138139
BinOp::Shl | BinOp::ShlUnchecked | BinOp::Shr | BinOp::ShrUnchecked => unreachable!(),
139140
}

‎compiler/rustc_codegen_cranelift/src/num.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,34 @@ pub(crate) fn bin_op_to_intcc(bin_op: BinOp, signed: bool) -> Option<IntCC> {
4040
})
4141
}
4242

43+
fn codegen_three_way_compare<'tcx>(
44+
fx: &mut FunctionCx<'_, '_, 'tcx>,
45+
signed: bool,
46+
lhs: Value,
47+
rhs: Value,
48+
) -> CValue<'tcx> {
49+
let gt_cc = crate::num::bin_op_to_intcc(BinOp::Gt, signed).unwrap();
50+
let lt_cc = crate::num::bin_op_to_intcc(BinOp::Lt, signed).unwrap();
51+
let gt = fx.bcx.ins().icmp(gt_cc, lhs, rhs);
52+
let lt = fx.bcx.ins().icmp(lt_cc, lhs, rhs);
53+
// Cranelift no longer has a single-bit type, so the comparison results
54+
// are already `I8`s, which we can subtract to get the -1/0/+1 we want.
55+
// See <https://github.com/bytecodealliance/wasmtime/pull/5031>.
56+
let val = fx.bcx.ins().isub(gt, lt);
57+
CValue::by_val(val, fx.layout_of(fx.tcx.types.i8))
58+
}
59+
4360
fn codegen_compare_bin_op<'tcx>(
4461
fx: &mut FunctionCx<'_, '_, 'tcx>,
4562
bin_op: BinOp,
4663
signed: bool,
4764
lhs: Value,
4865
rhs: Value,
4966
) -> CValue<'tcx> {
67+
if bin_op == BinOp::Cmp {
68+
return codegen_three_way_compare(fx, signed, lhs, rhs);
69+
}
70+
5071
let intcc = crate::num::bin_op_to_intcc(bin_op, signed).unwrap();
5172
let val = fx.bcx.ins().icmp(intcc, lhs, rhs);
5273
CValue::by_val(val, fx.layout_of(fx.tcx.types.bool))
@@ -59,7 +80,7 @@ pub(crate) fn codegen_binop<'tcx>(
5980
in_rhs: CValue<'tcx>,
6081
) -> CValue<'tcx> {
6182
match bin_op {
62-
BinOp::Eq | BinOp::Lt | BinOp::Le | BinOp::Ne | BinOp::Ge | BinOp::Gt => {
83+
BinOp::Eq | BinOp::Lt | BinOp::Le | BinOp::Ne | BinOp::Ge | BinOp::Gt | BinOp::Cmp => {
6384
match in_lhs.layout().ty.kind() {
6485
ty::Bool | ty::Uint(_) | ty::Int(_) | ty::Char => {
6586
let signed = type_sign(in_lhs.layout().ty);
@@ -160,7 +181,7 @@ pub(crate) fn codegen_int_binop<'tcx>(
160181
}
161182
BinOp::Offset => unreachable!("Offset is not an integer operation"),
162183
// Compare binops handles by `codegen_binop`.
163-
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => {
184+
BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge | BinOp::Cmp => {
164185
unreachable!("{:?}({:?}, {:?})", bin_op, in_lhs.layout().ty, in_rhs.layout().ty);
165186
}
166187
};

‎compiler/rustc_codegen_gcc/src/common.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ impl<'gcc, 'tcx> ConstMethods<'tcx> for CodegenCx<'gcc, 'tcx> {
104104
self.const_int(self.type_i32(), i as i64)
105105
}
106106

107+
fn const_i8(&self, i: i8) -> RValue<'gcc> {
108+
self.const_int(self.type_i8(), i as i64)
109+
}
110+
107111
fn const_u32(&self, i: u32) -> RValue<'gcc> {
108112
self.const_uint(self.type_u32(), i as u64)
109113
}

‎compiler/rustc_codegen_llvm/src/common.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> {
158158
self.const_int(self.type_i32(), i as i64)
159159
}
160160

161+
fn const_i8(&self, i: i8) -> &'ll Value {
162+
self.const_int(self.type_i8(), i as i64)
163+
}
164+
161165
fn const_u32(&self, i: u32) -> &'ll Value {
162166
self.const_uint(self.type_i32(), i as u64)
163167
}

‎compiler/rustc_codegen_ssa/src/mir/rvalue.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::common::{self, IntPredicate};
77
use crate::traits::*;
88
use crate::MemFlags;
99

10+
use rustc_hir as hir;
1011
use rustc_middle::mir;
1112
use rustc_middle::mir::Operand;
1213
use rustc_middle::ty::cast::{CastTy, IntTy};
@@ -881,6 +882,27 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
881882
bx.icmp(base::bin_op_to_icmp_predicate(op.to_hir_binop(), is_signed), lhs, rhs)
882883
}
883884
}
885+
mir::BinOp::Cmp => {
886+
use std::cmp::Ordering;
887+
debug_assert!(!is_float);
888+
// FIXME: To avoid this PR changing behaviour, the operations used
889+
// here are those from <https://github.com/rust-lang/rust/pull/63767>,
890+
// as tested by `tests/codegen/integer-cmp.rs`.
891+
// Something in future might want to pick different ones. For example,
892+
// maybe the ones from Clang's `<=>` operator in C++20 (see
893+
// <https://github.com/llvm/llvm-project/issues/60012>) or once we
894+
// update to new LLVM, something to take advantage of the new folds in
895+
// <https://github.com/llvm/llvm-project/issues/59666>.
896+
let pred = |op| base::bin_op_to_icmp_predicate(op, is_signed);
897+
let is_lt = bx.icmp(pred(hir::BinOpKind::Lt), lhs, rhs);
898+
let is_ne = bx.icmp(pred(hir::BinOpKind::Ne), lhs, rhs);
899+
let ge = bx.select(
900+
is_ne,
901+
bx.cx().const_i8(Ordering::Greater as i8),
902+
bx.cx().const_i8(Ordering::Equal as i8),
903+
);
904+
bx.select(is_lt, bx.cx().const_i8(Ordering::Less as i8), ge)
905+
}
884906
}
885907
}
886908

‎compiler/rustc_codegen_ssa/src/traits/consts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub trait ConstMethods<'tcx>: BackendTypes {
1919
fn const_bool(&self, val: bool) -> Self::Value;
2020
fn const_i16(&self, i: i16) -> Self::Value;
2121
fn const_i32(&self, i: i32) -> Self::Value;
22+
fn const_i8(&self, i: i8) -> Self::Value;
2223
fn const_u32(&self, i: u32) -> Self::Value;
2324
fn const_u64(&self, i: u64) -> Self::Value;
2425
fn const_u128(&self, i: u128) -> Self::Value;

‎compiler/rustc_const_eval/src/interpret/operand.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,13 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
224224
Self::from_scalar(Scalar::from_bool(b), layout)
225225
}
226226

227+
#[inline]
228+
pub fn from_ordering(c: std::cmp::Ordering, tcx: TyCtxt<'tcx>) -> Self {
229+
let ty = tcx.ty_ordering_enum(None);
230+
let layout = tcx.layout_of(ty::ParamEnv::reveal_all().and(ty)).unwrap();
231+
Self::from_scalar(Scalar::from_i8(c as i8), layout)
232+
}
233+
227234
#[inline]
228235
pub fn to_const_int(self) -> ConstInt {
229236
assert!(self.layout.ty.is_integral());

‎compiler/rustc_const_eval/src/interpret/operator.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
6161
}
6262

6363
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
64+
fn three_way_compare<T: Ord>(&self, lhs: T, rhs: T) -> (ImmTy<'tcx, M::Provenance>, bool) {
65+
let res = Ord::cmp(&lhs, &rhs);
66+
return (ImmTy::from_ordering(res, *self.tcx), false);
67+
}
68+
6469
fn binary_char_op(
6570
&self,
6671
bin_op: mir::BinOp,
@@ -69,6 +74,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
6974
) -> (ImmTy<'tcx, M::Provenance>, bool) {
7075
use rustc_middle::mir::BinOp::*;
7176

77+
if bin_op == Cmp {
78+
return self.three_way_compare(l, r);
79+
}
80+
7281
let res = match bin_op {
7382
Eq => l == r,
7483
Ne => l != r,
@@ -231,6 +240,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
231240
let r = self.sign_extend(r, right_layout) as i128;
232241
return Ok((ImmTy::from_bool(op(&l, &r), *self.tcx), false));
233242
}
243+
if bin_op == Cmp {
244+
let l = self.sign_extend(l, left_layout) as i128;
245+
let r = self.sign_extend(r, right_layout) as i128;
246+
return Ok(self.three_way_compare(l, r));
247+
}
234248
let op: Option<fn(i128, i128) -> (i128, bool)> = match bin_op {
235249
Div if r == 0 => throw_ub!(DivisionByZero),
236250
Rem if r == 0 => throw_ub!(RemainderByZero),
@@ -270,6 +284,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
270284
}
271285
}
272286

287+
if bin_op == Cmp {
288+
return Ok(self.three_way_compare(l, r));
289+
}
290+
273291
let val = match bin_op {
274292
Eq => ImmTy::from_bool(l == r, *self.tcx),
275293
Ne => ImmTy::from_bool(l != r, *self.tcx),

‎compiler/rustc_const_eval/src/transform/promote_consts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ impl<'tcx> Validator<'_, 'tcx> {
573573
| BinOp::Lt
574574
| BinOp::Ge
575575
| BinOp::Gt
576+
| BinOp::Cmp
576577
| BinOp::Offset
577578
| BinOp::Add
578579
| BinOp::AddUnchecked

‎compiler/rustc_const_eval/src/transform/validate.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,15 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
932932
)
933933
}
934934
}
935+
Cmp => {
936+
for x in [a, b] {
937+
check_kinds!(
938+
x,
939+
"Cannot three-way compare non-integer type {:?}",
940+
ty::Char | ty::Uint(..) | ty::Int(..)
941+
)
942+
}
943+
}
935944
AddUnchecked | SubUnchecked | MulUnchecked | Shl | ShlUnchecked | Shr
936945
| ShrUnchecked => {
937946
for x in [a, b] {

‎compiler/rustc_const_eval/src/util/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub(crate) fn binop_left_homogeneous(op: mir::BinOp) -> bool {
1919
match op {
2020
Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor
2121
| BitAnd | BitOr | Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => true,
22-
Eq | Ne | Lt | Le | Gt | Ge => false,
22+
Eq | Ne | Lt | Le | Gt | Ge | Cmp => false,
2323
}
2424
}
2525

@@ -30,7 +30,7 @@ pub(crate) fn binop_right_homogeneous(op: mir::BinOp) -> bool {
3030
use rustc_middle::mir::BinOp::*;
3131
match op {
3232
Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor
33-
| BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge => true,
33+
| BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge | Cmp => true,
3434
Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => false,
3535
}
3636
}

‎compiler/rustc_hir/src/lang_items.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ language_item_table! {
217217
Unpin, sym::unpin, unpin_trait, Target::Trait, GenericRequirement::None;
218218
Pin, sym::pin, pin_type, Target::Struct, GenericRequirement::None;
219219

220+
OrderingEnum, sym::Ordering, ordering_enum, Target::Enum, GenericRequirement::Exact(0);
220221
PartialEq, sym::eq, eq_trait, Target::Trait, GenericRequirement::Exact(1);
221222
PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1);
222223
CVoid, sym::c_void, c_void, Target::Enum, GenericRequirement::None;

‎compiler/rustc_hir_analysis/src/check/intrinsic.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: DefId) -> hir
9898
| sym::cttz
9999
| sym::bswap
100100
| sym::bitreverse
101+
| sym::three_way_compare
101102
| sym::discriminant_value
102103
| sym::type_id
103104
| sym::likely
@@ -325,6 +326,10 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
325326
| sym::bswap
326327
| sym::bitreverse => (1, vec![param(0)], param(0)),
327328

329+
sym::three_way_compare => {
330+
(1, vec![param(0), param(0)], tcx.ty_ordering_enum(Some(it.span)))
331+
}
332+
328333
sym::add_with_overflow | sym::sub_with_overflow | sym::mul_with_overflow => {
329334
(1, vec![param(0), param(0)], Ty::new_tup(tcx, &[param(0), tcx.types.bool]))
330335
}

‎compiler/rustc_middle/src/mir/syntax.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,8 @@ pub enum BinOp {
14291429
Ge,
14301430
/// The `>` operator (greater than)
14311431
Gt,
1432+
/// The `<=>` operator (three-way comparison, like `Ord::cmp`)
1433+
Cmp,
14321434
/// The `ptr.offset` operator
14331435
Offset,
14341436
}

‎compiler/rustc_middle/src/mir/tcx.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ impl<'tcx> BinOp {
264264
&BinOp::Eq | &BinOp::Lt | &BinOp::Le | &BinOp::Ne | &BinOp::Ge | &BinOp::Gt => {
265265
tcx.types.bool
266266
}
267+
&BinOp::Cmp => {
268+
// these should be integer-like types of the same size.
269+
assert_eq!(lhs_ty, rhs_ty);
270+
tcx.ty_ordering_enum(None)
271+
}
267272
}
268273
}
269274
}
@@ -300,7 +305,8 @@ impl BinOp {
300305
BinOp::Gt => hir::BinOpKind::Gt,
301306
BinOp::Le => hir::BinOpKind::Le,
302307
BinOp::Ge => hir::BinOpKind::Ge,
303-
BinOp::AddUnchecked
308+
BinOp::Cmp
309+
| BinOp::AddUnchecked
304310
| BinOp::SubUnchecked
305311
| BinOp::MulUnchecked
306312
| BinOp::ShlUnchecked

‎compiler/rustc_middle/src/ty/context.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,13 @@ impl<'tcx> TyCtxt<'tcx> {
784784
self.get_lang_items(())
785785
}
786786

787+
/// Gets a `Ty` representing the [`LangItem::OrderingEnum`]
788+
#[track_caller]
789+
pub fn ty_ordering_enum(self, span: Option<Span>) -> Ty<'tcx> {
790+
let ordering_enum = self.require_lang_item(hir::LangItem::OrderingEnum, span);
791+
self.type_of(ordering_enum).no_bound_vars().unwrap()
792+
}
793+
787794
/// Obtain the given diagnostic item's `DefId`. Use `is_diagnostic_item` if you just want to
788795
/// compare against another `DefId`, since `is_diagnostic_item` is cheaper.
789796
pub fn get_diagnostic_item(self, name: Symbol) -> Option<DefId> {

‎compiler/rustc_mir_transform/src/lower_intrinsics.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
8181
sym::wrapping_add
8282
| sym::wrapping_sub
8383
| sym::wrapping_mul
84+
| sym::three_way_compare
8485
| sym::unchecked_add
8586
| sym::unchecked_sub
8687
| sym::unchecked_mul
@@ -100,6 +101,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics {
100101
sym::wrapping_add => BinOp::Add,
101102
sym::wrapping_sub => BinOp::Sub,
102103
sym::wrapping_mul => BinOp::Mul,
104+
sym::three_way_compare => BinOp::Cmp,
103105
sym::unchecked_add => BinOp::AddUnchecked,
104106
sym::unchecked_sub => BinOp::SubUnchecked,
105107
sym::unchecked_mul => BinOp::MulUnchecked,

‎compiler/rustc_smir/src/rustc_smir/convert/mir.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ impl<'tcx> Stable<'tcx> for mir::BinOp {
490490
BinOp::Ne => stable_mir::mir::BinOp::Ne,
491491
BinOp::Ge => stable_mir::mir::BinOp::Ge,
492492
BinOp::Gt => stable_mir::mir::BinOp::Gt,
493+
BinOp::Cmp => stable_mir::mir::BinOp::Cmp,
493494
BinOp::Offset => stable_mir::mir::BinOp::Offset,
494495
}
495496
}

‎compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,6 +1658,7 @@ symbols! {
16581658
thread,
16591659
thread_local,
16601660
thread_local_macro,
1661+
three_way_compare,
16611662
thumb2,
16621663
thumb_mode: "thumb-mode",
16631664
tmm_reg,

‎compiler/rustc_ty_utils/src/consts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ fn check_binop(op: mir::BinOp) -> bool {
8282
match op {
8383
Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor
8484
| BitAnd | BitOr | Shl | ShlUnchecked | Shr | ShrUnchecked | Eq | Lt | Le | Ne | Ge
85-
| Gt => true,
85+
| Gt | Cmp => true,
8686
Offset => false,
8787
}
8888
}

‎compiler/stable_mir/src/mir/body.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ pub enum BinOp {
208208
Ne,
209209
Ge,
210210
Gt,
211+
Cmp,
211212
Offset,
212213
}
213214

‎library/core/src/cmp.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ pub struct AssertParamIsEq<T: Eq + ?Sized> {
352352
/// ```
353353
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
354354
#[stable(feature = "rust1", since = "1.0.0")]
355+
#[cfg_attr(not(bootstrap), lang = "Ordering")]
355356
#[repr(i8)]
356357
pub enum Ordering {
357358
/// An ordering where a compared value is less than another.
@@ -1513,12 +1514,19 @@ mod impls {
15131514
impl Ord for $t {
15141515
#[inline]
15151516
fn cmp(&self, other: &$t) -> Ordering {
1517+
#[cfg(bootstrap)]
1518+
{
15161519
// The order here is important to generate more optimal assembly.
15171520
// See <https://github.com/rust-lang/rust/issues/63758> for more info.
15181521
if *self < *other { Less }
15191522
else if *self == *other { Equal }
15201523
else { Greater }
15211524
}
1525+
#[cfg(not(bootstrap))]
1526+
{
1527+
crate::intrinsics::three_way_compare(*self, *other)
1528+
}
1529+
}
15221530
}
15231531
)*)
15241532
}

‎library/core/src/intrinsics.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2033,6 +2033,18 @@ extern "rust-intrinsic" {
20332033
#[rustc_nounwind]
20342034
pub fn bitreverse<T: Copy>(x: T) -> T;
20352035

2036+
/// Does a three-way comparison between the two integer arguments.
2037+
///
2038+
/// This is included as an intrinsic as it's useful to let it be one thing
2039+
/// in MIR, rather than the multiple checks and switches that make its IR
2040+
/// large and difficult to optimize.
2041+
///
2042+
/// The stabilized version of this intrinsic is [`Ord::cmp`].
2043+
#[cfg(not(bootstrap))]
2044+
#[rustc_const_unstable(feature = "const_three_way_compare", issue = "none")]
2045+
#[rustc_safe_intrinsic]
2046+
pub fn three_way_compare<T: Copy>(lhs: T, rhs: T) -> crate::cmp::Ordering;
2047+
20362048
/// Performs checked integer addition.
20372049
///
20382050
/// Note that, unlike most intrinsics, this is safe to call;

‎library/core/tests/intrinsics.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,22 @@ fn test_const_deallocate_at_runtime() {
9999
const_deallocate(core::ptr::null_mut(), 1, 1); // nop
100100
}
101101
}
102+
103+
#[cfg(not(bootstrap))]
104+
#[test]
105+
fn test_three_way_compare_in_const_contexts() {
106+
use core::cmp::Ordering::*;
107+
use core::intrinsics::three_way_compare;
108+
109+
const {
110+
assert!(Less as i8 == three_way_compare(123_u16, 456) as _);
111+
assert!(Equal as i8 == three_way_compare(456_u16, 456) as _);
112+
assert!(Greater as i8 == three_way_compare(789_u16, 456) as _);
113+
assert!(Less as i8 == three_way_compare('A', 'B') as _);
114+
assert!(Equal as i8 == three_way_compare('B', 'B') as _);
115+
assert!(Greater as i8 == three_way_compare('C', 'B') as _);
116+
assert!(Less as i8 == three_way_compare(-123_i16, 456) as _);
117+
assert!(Equal as i8 == three_way_compare(456_i16, 456) as _);
118+
assert!(Greater as i8 == three_way_compare(123_i16, -456) as _);
119+
}
120+
}

‎library/core/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#![feature(const_pointer_is_aligned)]
2020
#![feature(const_ptr_as_ref)]
2121
#![feature(const_ptr_write)]
22+
#![cfg_attr(not(bootstrap), feature(const_three_way_compare))]
2223
#![feature(const_trait_impl)]
2324
#![feature(const_likely)]
2425
#![feature(const_location_fields)]

‎tests/codegen/integer-cmp-raw.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This is test for more optimal Ord implementation for integers.
2+
// See <https://github.com/rust-lang/rust/issues/63758> for more info.
3+
4+
// compile-flags: -C opt-level=3 -C no-prepopulate-passes
5+
6+
#![crate_type = "lib"]
7+
8+
use std::cmp::Ordering;
9+
10+
// CHECK-LABEL: @cmp_signed
11+
#[no_mangle]
12+
pub fn cmp_signed(a: i64, b: i64) -> Ordering {
13+
// CHECK: %[[LT:.+]] = icmp slt i64
14+
// CHECK: %[[NE:.+]] = icmp ne i64
15+
// CHECK: %[[CGE:.+]] = select i1 %[[NE]], i8 1, i8 0
16+
// CHECK: %[[CGEL:.+]] = select i1 %[[LT]], i8 -1, i8 %[[CGE]]
17+
// CHECK: ret i8 %[[CGEL]]
18+
a.cmp(&b)
19+
}
20+
21+
// CHECK-LABEL: @cmp_unsigned
22+
#[no_mangle]
23+
pub fn cmp_unsigned(a: u32, b: u32) -> Ordering {
24+
// CHECK: %[[LT:.+]] = icmp ult i32
25+
// CHECK: %[[NE:.+]] = icmp ne i32
26+
// CHECK: %[[CGE:.+]] = select i1 %[[NE]], i8 1, i8 0
27+
// CHECK: %[[CGEL:.+]] = select i1 %[[LT]], i8 -1, i8 %[[CGE]]
28+
// CHECK: ret i8 %[[CGEL]]
29+
a.cmp(&b)
30+
}

‎tests/mir-opt/lower_intrinsics.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,18 @@ pub unsafe fn ptr_offset(p: *const i32, d: isize) -> *const i32 {
229229

230230
core::intrinsics::offset(p, d)
231231
}
232+
233+
// EMIT_MIR lower_intrinsics.three_way_compare_char.LowerIntrinsics.diff
234+
pub fn three_way_compare_char(a: char, b: char) {
235+
let _x = core::intrinsics::three_way_compare(a, b);
236+
}
237+
238+
// EMIT_MIR lower_intrinsics.three_way_compare_signed.LowerIntrinsics.diff
239+
pub fn three_way_compare_signed(a: i16, b: i16) {
240+
core::intrinsics::three_way_compare(a, b);
241+
}
242+
243+
// EMIT_MIR lower_intrinsics.three_way_compare_unsigned.LowerIntrinsics.diff
244+
pub fn three_way_compare_unsigned(a: u32, b: u32) {
245+
let _x = core::intrinsics::three_way_compare(a, b);
246+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
- // MIR for `three_way_compare_char` before LowerIntrinsics
2+
+ // MIR for `three_way_compare_char` after LowerIntrinsics
3+
4+
fn three_way_compare_char(_1: char, _2: char) -> () {
5+
debug a => _1;
6+
debug b => _2;
7+
let mut _0: ();
8+
let _3: std::cmp::Ordering;
9+
let mut _4: char;
10+
let mut _5: char;
11+
scope 1 {
12+
debug _x => _3;
13+
}
14+
15+
bb0: {
16+
StorageLive(_3);
17+
StorageLive(_4);
18+
_4 = _1;
19+
StorageLive(_5);
20+
_5 = _2;
21+
- _3 = three_way_compare::<char>(move _4, move _5) -> [return: bb1, unwind unreachable];
22+
+ _3 = Cmp(move _4, move _5);
23+
+ goto -> bb1;
24+
}
25+
26+
bb1: {
27+
StorageDead(_5);
28+
StorageDead(_4);
29+
_0 = const ();
30+
StorageDead(_3);
31+
return;
32+
}
33+
}
34+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
- // MIR for `three_way_compare_char` before LowerIntrinsics
2+
+ // MIR for `three_way_compare_char` after LowerIntrinsics
3+
4+
fn three_way_compare_char(_1: char, _2: char) -> () {
5+
debug a => _1;
6+
debug b => _2;
7+
let mut _0: ();
8+
let _3: std::cmp::Ordering;
9+
let mut _4: char;
10+
let mut _5: char;
11+
scope 1 {
12+
debug _x => _3;
13+
}
14+
15+
bb0: {
16+
StorageLive(_3);
17+
StorageLive(_4);
18+
_4 = _1;
19+
StorageLive(_5);
20+
_5 = _2;
21+
- _3 = three_way_compare::<char>(move _4, move _5) -> [return: bb1, unwind continue];
22+
+ _3 = Cmp(move _4, move _5);
23+
+ goto -> bb1;
24+
}
25+
26+
bb1: {
27+
StorageDead(_5);
28+
StorageDead(_4);
29+
_0 = const ();
30+
StorageDead(_3);
31+
return;
32+
}
33+
}
34+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
- // MIR for `three_way_compare_signed` before LowerIntrinsics
2+
+ // MIR for `three_way_compare_signed` after LowerIntrinsics
3+
4+
fn three_way_compare_signed(_1: i16, _2: i16) -> () {
5+
debug a => _1;
6+
debug b => _2;
7+
let mut _0: ();
8+
let _3: std::cmp::Ordering;
9+
let mut _4: i16;
10+
let mut _5: i16;
11+
12+
bb0: {
13+
StorageLive(_3);
14+
StorageLive(_4);
15+
_4 = _1;
16+
StorageLive(_5);
17+
_5 = _2;
18+
- _3 = three_way_compare::<i16>(move _4, move _5) -> [return: bb1, unwind unreachable];
19+
+ _3 = Cmp(move _4, move _5);
20+
+ goto -> bb1;
21+
}
22+
23+
bb1: {
24+
StorageDead(_5);
25+
StorageDead(_4);
26+
StorageDead(_3);
27+
_0 = const ();
28+
return;
29+
}
30+
}
31+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
- // MIR for `three_way_compare_signed` before LowerIntrinsics
2+
+ // MIR for `three_way_compare_signed` after LowerIntrinsics
3+
4+
fn three_way_compare_signed(_1: i16, _2: i16) -> () {
5+
debug a => _1;
6+
debug b => _2;
7+
let mut _0: ();
8+
let _3: std::cmp::Ordering;
9+
let mut _4: i16;
10+
let mut _5: i16;
11+
12+
bb0: {
13+
StorageLive(_3);
14+
StorageLive(_4);
15+
_4 = _1;
16+
StorageLive(_5);
17+
_5 = _2;
18+
- _3 = three_way_compare::<i16>(move _4, move _5) -> [return: bb1, unwind continue];
19+
+ _3 = Cmp(move _4, move _5);
20+
+ goto -> bb1;
21+
}
22+
23+
bb1: {
24+
StorageDead(_5);
25+
StorageDead(_4);
26+
StorageDead(_3);
27+
_0 = const ();
28+
return;
29+
}
30+
}
31+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
- // MIR for `three_way_compare_unsigned` before LowerIntrinsics
2+
+ // MIR for `three_way_compare_unsigned` after LowerIntrinsics
3+
4+
fn three_way_compare_unsigned(_1: u32, _2: u32) -> () {
5+
debug a => _1;
6+
debug b => _2;
7+
let mut _0: ();
8+
let _3: std::cmp::Ordering;
9+
let mut _4: u32;
10+
let mut _5: u32;
11+
scope 1 {
12+
debug _x => _3;
13+
}
14+
15+
bb0: {
16+
StorageLive(_3);
17+
StorageLive(_4);
18+
_4 = _1;
19+
StorageLive(_5);
20+
_5 = _2;
21+
- _3 = three_way_compare::<u32>(move _4, move _5) -> [return: bb1, unwind unreachable];
22+
+ _3 = Cmp(move _4, move _5);
23+
+ goto -> bb1;
24+
}
25+
26+
bb1: {
27+
StorageDead(_5);
28+
StorageDead(_4);
29+
_0 = const ();
30+
StorageDead(_3);
31+
return;
32+
}
33+
}
34+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
- // MIR for `three_way_compare_unsigned` before LowerIntrinsics
2+
+ // MIR for `three_way_compare_unsigned` after LowerIntrinsics
3+
4+
fn three_way_compare_unsigned(_1: u32, _2: u32) -> () {
5+
debug a => _1;
6+
debug b => _2;
7+
let mut _0: ();
8+
let _3: std::cmp::Ordering;
9+
let mut _4: u32;
10+
let mut _5: u32;
11+
scope 1 {
12+
debug _x => _3;
13+
}
14+
15+
bb0: {
16+
StorageLive(_3);
17+
StorageLive(_4);
18+
_4 = _1;
19+
StorageLive(_5);
20+
_5 = _2;
21+
- _3 = three_way_compare::<u32>(move _4, move _5) -> [return: bb1, unwind continue];
22+
+ _3 = Cmp(move _4, move _5);
23+
+ goto -> bb1;
24+
}
25+
26+
bb1: {
27+
StorageDead(_5);
28+
StorageDead(_4);
29+
_0 = const ();
30+
StorageDead(_3);
31+
return;
32+
}
33+
}
34+

‎tests/mir-opt/pre-codegen/derived_ord.{impl#0}-partial_cmp.PreCodegen.after.mir

Lines changed: 76 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -6,65 +6,53 @@ fn <impl at $DIR/derived_ord.rs:6:10: 6:20>::partial_cmp(_1: &MultiField, _2: &M
66
let mut _0: std::option::Option<std::cmp::Ordering>;
77
let mut _3: &u64;
88
let mut _4: &u64;
9-
let mut _12: std::option::Option<std::cmp::Ordering>;
10-
let mut _13: isize;
11-
let mut _14: i8;
12-
let mut _15: &char;
13-
let mut _16: &char;
14-
let mut _24: std::option::Option<std::cmp::Ordering>;
15-
let mut _25: isize;
16-
let mut _26: i8;
17-
let mut _27: &i16;
18-
let mut _28: &i16;
9+
let mut _8: std::option::Option<std::cmp::Ordering>;
10+
let mut _9: isize;
11+
let mut _10: i8;
12+
let mut _11: &char;
13+
let mut _12: &char;
14+
let mut _16: std::option::Option<std::cmp::Ordering>;
15+
let mut _17: isize;
16+
let mut _18: i8;
17+
let mut _19: &i16;
18+
let mut _20: &i16;
1919
scope 1 {
20-
debug cmp => _24;
20+
debug cmp => _16;
2121
}
2222
scope 2 {
23-
debug cmp => _12;
23+
debug cmp => _8;
2424
}
2525
scope 3 (inlined cmp::impls::<impl PartialOrd for u64>::partial_cmp) {
2626
debug self => _3;
2727
debug other => _4;
28-
let mut _11: std::cmp::Ordering;
28+
let mut _7: std::cmp::Ordering;
2929
scope 4 (inlined cmp::impls::<impl Ord for u64>::cmp) {
3030
debug self => _3;
3131
debug other => _4;
3232
let mut _5: u64;
3333
let mut _6: u64;
34-
let mut _7: bool;
35-
let mut _8: u64;
36-
let mut _9: u64;
37-
let mut _10: bool;
3834
}
3935
}
4036
scope 5 (inlined cmp::impls::<impl PartialOrd for char>::partial_cmp) {
41-
debug self => _15;
42-
debug other => _16;
43-
let mut _23: std::cmp::Ordering;
37+
debug self => _11;
38+
debug other => _12;
39+
let mut _15: std::cmp::Ordering;
4440
scope 6 (inlined cmp::impls::<impl Ord for char>::cmp) {
45-
debug self => _15;
46-
debug other => _16;
47-
let mut _17: char;
48-
let mut _18: char;
49-
let mut _19: bool;
50-
let mut _20: char;
51-
let mut _21: char;
52-
let mut _22: bool;
41+
debug self => _11;
42+
debug other => _12;
43+
let mut _13: char;
44+
let mut _14: char;
5345
}
5446
}
5547
scope 7 (inlined cmp::impls::<impl PartialOrd for i16>::partial_cmp) {
56-
debug self => _27;
57-
debug other => _28;
58-
let mut _35: std::cmp::Ordering;
48+
debug self => _19;
49+
debug other => _20;
50+
let mut _23: std::cmp::Ordering;
5951
scope 8 (inlined cmp::impls::<impl Ord for i16>::cmp) {
60-
debug self => _27;
61-
debug other => _28;
62-
let mut _29: i16;
63-
let mut _30: i16;
64-
let mut _31: bool;
65-
let mut _32: i16;
66-
let mut _33: i16;
67-
let mut _34: bool;
52+
debug self => _19;
53+
debug other => _20;
54+
let mut _21: i16;
55+
let mut _22: i16;
6856
}
6957
}
7058

@@ -73,210 +61,84 @@ fn <impl at $DIR/derived_ord.rs:6:10: 6:20>::partial_cmp(_1: &MultiField, _2: &M
7361
_3 = &((*_1).0: u64);
7462
StorageLive(_4);
7563
_4 = &((*_2).0: u64);
76-
StorageLive(_11);
7764
StorageLive(_7);
7865
StorageLive(_5);
7966
_5 = ((*_1).0: u64);
8067
StorageLive(_6);
8168
_6 = ((*_2).0: u64);
82-
_7 = Lt(move _5, move _6);
83-
switchInt(move _7) -> [0: bb1, otherwise: bb5];
69+
_7 = Cmp(move _5, move _6);
70+
StorageDead(_6);
71+
StorageDead(_5);
72+
_8 = Option::<std::cmp::Ordering>::Some(move _7);
73+
StorageDead(_7);
74+
StorageDead(_4);
75+
StorageDead(_3);
76+
_9 = discriminant(_8);
77+
switchInt(move _9) -> [1: bb1, otherwise: bb6];
8478
}
8579

8680
bb1: {
87-
StorageDead(_6);
88-
StorageDead(_5);
89-
StorageLive(_10);
90-
StorageLive(_8);
91-
_8 = ((*_1).0: u64);
92-
StorageLive(_9);
93-
_9 = ((*_2).0: u64);
94-
_10 = Eq(move _8, move _9);
95-
switchInt(move _10) -> [0: bb2, otherwise: bb3];
81+
_10 = discriminant(((_8 as Some).0: std::cmp::Ordering));
82+
switchInt(move _10) -> [0: bb2, otherwise: bb6];
9683
}
9784

9885
bb2: {
99-
StorageDead(_9);
100-
StorageDead(_8);
101-
_11 = const Greater;
102-
goto -> bb4;
86+
StorageLive(_11);
87+
_11 = &((*_1).1: char);
88+
StorageLive(_12);
89+
_12 = &((*_2).1: char);
90+
StorageLive(_15);
91+
StorageLive(_13);
92+
_13 = ((*_1).1: char);
93+
StorageLive(_14);
94+
_14 = ((*_2).1: char);
95+
_15 = Cmp(move _13, move _14);
96+
StorageDead(_14);
97+
StorageDead(_13);
98+
_16 = Option::<std::cmp::Ordering>::Some(move _15);
99+
StorageDead(_15);
100+
StorageDead(_12);
101+
StorageDead(_11);
102+
_17 = discriminant(_16);
103+
switchInt(move _17) -> [1: bb3, otherwise: bb5];
103104
}
104105

105106
bb3: {
106-
StorageDead(_9);
107-
StorageDead(_8);
108-
_11 = const Equal;
109-
goto -> bb4;
107+
_18 = discriminant(((_16 as Some).0: std::cmp::Ordering));
108+
switchInt(move _18) -> [0: bb4, otherwise: bb5];
110109
}
111110

112111
bb4: {
113-
StorageDead(_10);
114-
goto -> bb6;
115-
}
116-
117-
bb5: {
118-
StorageDead(_6);
119-
StorageDead(_5);
120-
_11 = const Less;
121-
goto -> bb6;
122-
}
123-
124-
bb6: {
125-
StorageDead(_7);
126-
_12 = Option::<std::cmp::Ordering>::Some(move _11);
127-
StorageDead(_11);
128-
StorageDead(_4);
129-
StorageDead(_3);
130-
_13 = discriminant(_12);
131-
switchInt(move _13) -> [1: bb7, otherwise: bb24];
132-
}
133-
134-
bb7: {
135-
_14 = discriminant(((_12 as Some).0: std::cmp::Ordering));
136-
switchInt(move _14) -> [0: bb8, otherwise: bb24];
137-
}
138-
139-
bb8: {
140-
StorageLive(_15);
141-
_15 = &((*_1).1: char);
142-
StorageLive(_16);
143-
_16 = &((*_2).1: char);
144-
StorageLive(_23);
145112
StorageLive(_19);
146-
StorageLive(_17);
147-
_17 = ((*_1).1: char);
148-
StorageLive(_18);
149-
_18 = ((*_2).1: char);
150-
_19 = Lt(move _17, move _18);
151-
switchInt(move _19) -> [0: bb9, otherwise: bb13];
152-
}
153-
154-
bb9: {
155-
StorageDead(_18);
156-
StorageDead(_17);
157-
StorageLive(_22);
113+
_19 = &((*_1).2: i16);
158114
StorageLive(_20);
159-
_20 = ((*_1).1: char);
115+
_20 = &((*_2).2: i16);
116+
StorageLive(_23);
160117
StorageLive(_21);
161-
_21 = ((*_2).1: char);
162-
_22 = Eq(move _20, move _21);
163-
switchInt(move _22) -> [0: bb10, otherwise: bb11];
164-
}
165-
166-
bb10: {
167-
StorageDead(_21);
168-
StorageDead(_20);
169-
_23 = const Greater;
170-
goto -> bb12;
171-
}
172-
173-
bb11: {
118+
_21 = ((*_1).2: i16);
119+
StorageLive(_22);
120+
_22 = ((*_2).2: i16);
121+
_23 = Cmp(move _21, move _22);
122+
StorageDead(_22);
174123
StorageDead(_21);
124+
_0 = Option::<std::cmp::Ordering>::Some(move _23);
125+
StorageDead(_23);
175126
StorageDead(_20);
176-
_23 = const Equal;
177-
goto -> bb12;
178-
}
179-
180-
bb12: {
181-
StorageDead(_22);
182-
goto -> bb14;
183-
}
184-
185-
bb13: {
186-
StorageDead(_18);
187-
StorageDead(_17);
188-
_23 = const Less;
189-
goto -> bb14;
190-
}
191-
192-
bb14: {
193127
StorageDead(_19);
194-
_24 = Option::<std::cmp::Ordering>::Some(move _23);
195-
StorageDead(_23);
196-
StorageDead(_16);
197-
StorageDead(_15);
198-
_25 = discriminant(_24);
199-
switchInt(move _25) -> [1: bb15, otherwise: bb23];
128+
goto -> bb7;
200129
}
201130

202-
bb15: {
203-
_26 = discriminant(((_24 as Some).0: std::cmp::Ordering));
204-
switchInt(move _26) -> [0: bb16, otherwise: bb23];
205-
}
206-
207-
bb16: {
208-
StorageLive(_27);
209-
_27 = &((*_1).2: i16);
210-
StorageLive(_28);
211-
_28 = &((*_2).2: i16);
212-
StorageLive(_35);
213-
StorageLive(_31);
214-
StorageLive(_29);
215-
_29 = ((*_1).2: i16);
216-
StorageLive(_30);
217-
_30 = ((*_2).2: i16);
218-
_31 = Lt(move _29, move _30);
219-
switchInt(move _31) -> [0: bb17, otherwise: bb21];
220-
}
221-
222-
bb17: {
223-
StorageDead(_30);
224-
StorageDead(_29);
225-
StorageLive(_34);
226-
StorageLive(_32);
227-
_32 = ((*_1).2: i16);
228-
StorageLive(_33);
229-
_33 = ((*_2).2: i16);
230-
_34 = Eq(move _32, move _33);
231-
switchInt(move _34) -> [0: bb18, otherwise: bb19];
232-
}
233-
234-
bb18: {
235-
StorageDead(_33);
236-
StorageDead(_32);
237-
_35 = const Greater;
238-
goto -> bb20;
239-
}
240-
241-
bb19: {
242-
StorageDead(_33);
243-
StorageDead(_32);
244-
_35 = const Equal;
245-
goto -> bb20;
246-
}
247-
248-
bb20: {
249-
StorageDead(_34);
250-
goto -> bb22;
251-
}
252-
253-
bb21: {
254-
StorageDead(_30);
255-
StorageDead(_29);
256-
_35 = const Less;
257-
goto -> bb22;
258-
}
259-
260-
bb22: {
261-
StorageDead(_31);
262-
_0 = Option::<std::cmp::Ordering>::Some(move _35);
263-
StorageDead(_35);
264-
StorageDead(_28);
265-
StorageDead(_27);
266-
goto -> bb25;
267-
}
268-
269-
bb23: {
270-
_0 = _24;
271-
goto -> bb25;
131+
bb5: {
132+
_0 = _16;
133+
goto -> bb7;
272134
}
273135

274-
bb24: {
275-
_0 = _12;
276-
goto -> bb25;
136+
bb6: {
137+
_0 = _8;
138+
goto -> bb7;
277139
}
278140

279-
bb25: {
141+
bb7: {
280142
return;
281143
}
282144
}

0 commit comments

Comments
 (0)
Please sign in to comment.