Skip to content

Commit 23806f4

Browse files
committed
Lower BinOp::Cmp to llvm.{s,u}cmp.* intrinsics
Lowers `mir::BinOp::Cmp` (`three_way_compare` intrinsic) to the corresponding LLVM `llvm.{s,u}cmp.i8.*` intrinsics, added in LLVM 19.
1 parent 8dc8377 commit 23806f4

File tree

5 files changed

+98
-13
lines changed

5 files changed

+98
-13
lines changed

compiler/rustc_codegen_llvm/src/builder.rs

+29
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_codegen_ssa::mir::place::PlaceRef;
1212
use rustc_codegen_ssa::traits::*;
1313
use rustc_data_structures::small_c_str::SmallCStr;
1414
use rustc_hir::def_id::DefId;
15+
use rustc_middle::bug;
1516
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
1617
use rustc_middle::ty::layout::{
1718
FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTypingEnv, LayoutError, LayoutOfHelpers,
@@ -873,6 +874,34 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
873874
unsafe { llvm::LLVMBuildFCmp(self.llbuilder, op as c_uint, lhs, rhs, UNNAMED) }
874875
}
875876

877+
fn three_way_compare(
878+
&mut self,
879+
ty: Ty<'tcx>,
880+
lhs: Self::Value,
881+
rhs: Self::Value,
882+
) -> Option<Self::Value> {
883+
if crate::llvm_util::get_version() < (19, 0, 0) {
884+
return None;
885+
}
886+
887+
let name = match (ty.is_signed(), ty.primitive_size(self.tcx).bits()) {
888+
(true, 8) => "llvm.scmp.i8.i8",
889+
(true, 16) => "llvm.scmp.i8.i16",
890+
(true, 32) => "llvm.scmp.i8.i32",
891+
(true, 64) => "llvm.scmp.i8.i64",
892+
(true, 128) => "llvm.scmp.i8.i128",
893+
894+
(false, 8) => "llvm.ucmp.i8.i8",
895+
(false, 16) => "llvm.ucmp.i8.i16",
896+
(false, 32) => "llvm.ucmp.i8.i32",
897+
(false, 64) => "llvm.ucmp.i8.i64",
898+
(false, 128) => "llvm.ucmp.i8.i128",
899+
900+
_ => bug!("three-way compare unsupported for type {ty:?}"),
901+
};
902+
Some(self.call_intrinsic(name, &[lhs, rhs]))
903+
}
904+
876905
/* Miscellaneous instructions */
877906
fn memcpy(
878907
&mut self,

compiler/rustc_codegen_llvm/src/context.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,18 @@ impl<'ll> CodegenCx<'ll, '_> {
10361036
ifn!("llvm.usub.sat.i64", fn(t_i64, t_i64) -> t_i64);
10371037
ifn!("llvm.usub.sat.i128", fn(t_i128, t_i128) -> t_i128);
10381038

1039+
ifn!("llvm.scmp.i8.i8", fn(t_i8, t_i8) -> t_i8);
1040+
ifn!("llvm.scmp.i8.i16", fn(t_i16, t_i16) -> t_i8);
1041+
ifn!("llvm.scmp.i8.i32", fn(t_i32, t_i32) -> t_i8);
1042+
ifn!("llvm.scmp.i8.i64", fn(t_i64, t_i64) -> t_i8);
1043+
ifn!("llvm.scmp.i8.i128", fn(t_i128, t_i128) -> t_i8);
1044+
1045+
ifn!("llvm.ucmp.i8.i8", fn(t_i8, t_i8) -> t_i8);
1046+
ifn!("llvm.ucmp.i8.i16", fn(t_i16, t_i16) -> t_i8);
1047+
ifn!("llvm.ucmp.i8.i32", fn(t_i32, t_i32) -> t_i8);
1048+
ifn!("llvm.ucmp.i8.i64", fn(t_i64, t_i64) -> t_i8);
1049+
ifn!("llvm.ucmp.i8.i128", fn(t_i128, t_i128) -> t_i8);
1050+
10391051
ifn!("llvm.lifetime.start.p0i8", fn(t_i64, ptr) -> void);
10401052
ifn!("llvm.lifetime.end.p0i8", fn(t_i64, ptr) -> void);
10411053

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

+3
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
954954
mir::BinOp::Cmp => {
955955
use std::cmp::Ordering;
956956
assert!(!is_float);
957+
if let Some(value) = bx.three_way_compare(input_ty, lhs, rhs) {
958+
return value;
959+
}
957960
let pred = |op| base::bin_op_to_icmp_predicate(op, is_signed);
958961
if bx.cx().tcx().sess.opts.optimize == OptLevel::No {
959962
// FIXME: This actually generates tighter assembly, and is a classic trick

compiler/rustc_codegen_ssa/src/traits/builder.rs

+12
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,18 @@ pub trait BuilderMethods<'a, 'tcx>:
309309
fn icmp(&mut self, op: IntPredicate, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
310310
fn fcmp(&mut self, op: RealPredicate, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
311311

312+
/// Returns `-1` if `lhs < rhs`, `0` if `lhs == rhs`, and `1` if `lhs > rhs`.
313+
// FIXME: Move the default implementation from `codegen_scalar_binop` into this method and
314+
// remove the `Option` return once LLVM 19 is the minimum version.
315+
fn three_way_compare(
316+
&mut self,
317+
_ty: Ty<'tcx>,
318+
_lhs: Self::Value,
319+
_rhs: Self::Value,
320+
) -> Option<Self::Value> {
321+
None
322+
}
323+
312324
fn memcpy(
313325
&mut self,
314326
dst: Self::Value,

tests/codegen/integer-cmp.rs

+42-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// This is test for more optimal Ord implementation for integers.
22
// See <https://github.com/rust-lang/rust/issues/63758> for more info.
33

4-
//@ revisions: llvm-pre-20 llvm-20
5-
//@ [llvm-20] min-llvm-version: 20
6-
//@ [llvm-pre-20] max-llvm-major-version: 19
4+
//@ revisions: llvm-pre-19 llvm-19
5+
//@ [llvm-19] min-llvm-version: 19
6+
//@ [llvm-pre-19] max-llvm-major-version: 18
77
//@ compile-flags: -C opt-level=3
88

99
#![crate_type = "lib"]
@@ -13,21 +13,50 @@ use std::cmp::Ordering;
1313
// CHECK-LABEL: @cmp_signed
1414
#[no_mangle]
1515
pub fn cmp_signed(a: i64, b: i64) -> Ordering {
16-
// llvm-20: @llvm.scmp.i8.i64
17-
// llvm-pre-20: icmp slt
18-
// llvm-pre-20: icmp ne
19-
// llvm-pre-20: zext i1
20-
// llvm-pre-20: select i1
16+
// llvm-19: call{{.*}} i8 @llvm.scmp.i8.i64
17+
// llvm-pre-19: icmp slt
18+
// llvm-pre-19: icmp ne
19+
// llvm-pre-19: zext i1
20+
// llvm-pre-19: select i1
2121
a.cmp(&b)
2222
}
2323

2424
// CHECK-LABEL: @cmp_unsigned
2525
#[no_mangle]
2626
pub fn cmp_unsigned(a: u32, b: u32) -> Ordering {
27-
// llvm-20: @llvm.ucmp.i8.i32
28-
// llvm-pre-20: icmp ult
29-
// llvm-pre-20: icmp ne
30-
// llvm-pre-20: zext i1
31-
// llvm-pre-20: select i1
27+
// llvm-19: call{{.*}} i8 @llvm.ucmp.i8.i32
28+
// llvm-pre-19: icmp ult
29+
// llvm-pre-19: icmp ne
30+
// llvm-pre-19: zext i1
31+
// llvm-pre-19: select i1
32+
a.cmp(&b)
33+
}
34+
35+
// CHECK-LABEL: @cmp_char
36+
#[no_mangle]
37+
pub fn cmp_char(a: char, b: char) -> Ordering {
38+
// llvm-19: call{{.*}} i8 @llvm.ucmp.i8.i32
39+
// llvm-pre-19: icmp ult
40+
// llvm-pre-19: icmp ne
41+
// llvm-pre-19: zext i1
42+
// llvm-pre-19: select i1
43+
a.cmp(&b)
44+
}
45+
46+
// CHECK-LABEL: @cmp_tuple
47+
#[no_mangle]
48+
pub fn cmp_tuple(a: (i16, u16), b: (i16, u16)) -> Ordering {
49+
// llvm-19-DAG: call{{.*}} i8 @llvm.ucmp.i8.i16
50+
// llvm-19-DAG: call{{.*}} i8 @llvm.scmp.i8.i16
51+
// llvm-19: select i1
52+
// llvm-pre-19: icmp slt
53+
// llvm-pre-19: icmp ne
54+
// llvm-pre-19: zext i1
55+
// llvm-pre-19: select i1
56+
// llvm-pre-19: icmp ult
57+
// llvm-pre-19: icmp ne
58+
// llvm-pre-19: zext i1
59+
// llvm-pre-19: select i1
60+
// llvm-pre-19: select i1
3261
a.cmp(&b)
3362
}

0 commit comments

Comments
 (0)