Skip to content

Commit

Permalink
Auto merge of rust-lang#133984 - DaniPopes:scmp-ucmp, r=scottmcm
Browse files Browse the repository at this point in the history
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.

These are the intrinsics mentioned in rust-lang#118310, which are now available in LLVM 19.

I couldn't find any follow-up PRs/discussions about this, please let me know if I missed something.

r? `@scottmcm`
  • Loading branch information
bors committed Feb 18, 2025
2 parents 3b022d8 + dcdb7ed commit 28ef42e
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 3 deletions.
30 changes: 30 additions & 0 deletions compiler/rustc_codegen_llvm/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use rustc_codegen_ssa::mir::place::PlaceRef;
use rustc_codegen_ssa::traits::*;
use rustc_data_structures::small_c_str::SmallCStr;
use rustc_hir::def_id::DefId;
use rustc_middle::bug;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
use rustc_middle::ty::layout::{
FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTypingEnv, LayoutError, LayoutOfHelpers,
Expand Down Expand Up @@ -1066,6 +1067,35 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
unsafe { llvm::LLVMBuildFCmp(self.llbuilder, op as c_uint, lhs, rhs, UNNAMED) }
}

fn three_way_compare(
&mut self,
ty: Ty<'tcx>,
lhs: Self::Value,
rhs: Self::Value,
) -> Option<Self::Value> {
// FIXME: See comment on the definition of `three_way_compare`.
if crate::llvm_util::get_version() < (20, 0, 0) {
return None;
}

let name = match (ty.is_signed(), ty.primitive_size(self.tcx).bits()) {
(true, 8) => "llvm.scmp.i8.i8",
(true, 16) => "llvm.scmp.i8.i16",
(true, 32) => "llvm.scmp.i8.i32",
(true, 64) => "llvm.scmp.i8.i64",
(true, 128) => "llvm.scmp.i8.i128",

(false, 8) => "llvm.ucmp.i8.i8",
(false, 16) => "llvm.ucmp.i8.i16",
(false, 32) => "llvm.ucmp.i8.i32",
(false, 64) => "llvm.ucmp.i8.i64",
(false, 128) => "llvm.ucmp.i8.i128",

_ => bug!("three-way compare unsupported for type {ty:?}"),
};
Some(self.call_intrinsic(name, &[lhs, rhs]))
}

/* Miscellaneous instructions */
fn memcpy(
&mut self,
Expand Down
12 changes: 12 additions & 0 deletions compiler/rustc_codegen_llvm/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,18 @@ impl<'ll> CodegenCx<'ll, '_> {
ifn!("llvm.usub.sat.i64", fn(t_i64, t_i64) -> t_i64);
ifn!("llvm.usub.sat.i128", fn(t_i128, t_i128) -> t_i128);

ifn!("llvm.scmp.i8.i8", fn(t_i8, t_i8) -> t_i8);
ifn!("llvm.scmp.i8.i16", fn(t_i16, t_i16) -> t_i8);
ifn!("llvm.scmp.i8.i32", fn(t_i32, t_i32) -> t_i8);
ifn!("llvm.scmp.i8.i64", fn(t_i64, t_i64) -> t_i8);
ifn!("llvm.scmp.i8.i128", fn(t_i128, t_i128) -> t_i8);

ifn!("llvm.ucmp.i8.i8", fn(t_i8, t_i8) -> t_i8);
ifn!("llvm.ucmp.i8.i16", fn(t_i16, t_i16) -> t_i8);
ifn!("llvm.ucmp.i8.i32", fn(t_i32, t_i32) -> t_i8);
ifn!("llvm.ucmp.i8.i64", fn(t_i64, t_i64) -> t_i8);
ifn!("llvm.ucmp.i8.i128", fn(t_i128, t_i128) -> t_i8);

ifn!("llvm.lifetime.start.p0i8", fn(t_i64, ptr) -> void);
ifn!("llvm.lifetime.end.p0i8", fn(t_i64, ptr) -> void);

Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
mir::BinOp::Cmp => {
use std::cmp::Ordering;
assert!(!is_float);
if let Some(value) = bx.three_way_compare(input_ty, lhs, rhs) {
return value;
}
let pred = |op| base::bin_op_to_icmp_predicate(op, is_signed);
if bx.cx().tcx().sess.opts.optimize == OptLevel::No {
// FIXME: This actually generates tighter assembly, and is a classic trick
Expand Down
12 changes: 12 additions & 0 deletions compiler/rustc_codegen_ssa/src/traits/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,18 @@ pub trait BuilderMethods<'a, 'tcx>:
fn icmp(&mut self, op: IntPredicate, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
fn fcmp(&mut self, op: RealPredicate, lhs: Self::Value, rhs: Self::Value) -> Self::Value;

/// Returns `-1` if `lhs < rhs`, `0` if `lhs == rhs`, and `1` if `lhs > rhs`.
// FIXME: Move the default implementation from `codegen_scalar_binop` into this method and
// remove the `Option` return once LLVM 20 is the minimum version.
fn three_way_compare(
&mut self,
_ty: Ty<'tcx>,
_lhs: Self::Value,
_rhs: Self::Value,
) -> Option<Self::Value> {
None
}

fn memcpy(
&mut self,
dst: Self::Value,
Expand Down
35 changes: 32 additions & 3 deletions tests/codegen/integer-cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//@ revisions: llvm-pre-20 llvm-20
//@ [llvm-20] min-llvm-version: 20
//@ [llvm-pre-20] max-llvm-major-version: 19
//@ compile-flags: -C opt-level=3
//@ compile-flags: -C opt-level=3 -Zmerge-functions=disabled

#![crate_type = "lib"]

Expand All @@ -13,7 +13,7 @@ use std::cmp::Ordering;
// CHECK-LABEL: @cmp_signed
#[no_mangle]
pub fn cmp_signed(a: i64, b: i64) -> Ordering {
// llvm-20: @llvm.scmp.i8.i64
// llvm-20: call{{.*}} i8 @llvm.scmp.i8.i64
// llvm-pre-20: icmp slt
// llvm-pre-20: icmp ne
// llvm-pre-20: zext i1
Expand All @@ -24,10 +24,39 @@ pub fn cmp_signed(a: i64, b: i64) -> Ordering {
// CHECK-LABEL: @cmp_unsigned
#[no_mangle]
pub fn cmp_unsigned(a: u32, b: u32) -> Ordering {
// llvm-20: @llvm.ucmp.i8.i32
// llvm-20: call{{.*}} i8 @llvm.ucmp.i8.i32
// llvm-pre-20: icmp ult
// llvm-pre-20: icmp ne
// llvm-pre-20: zext i1
// llvm-pre-20: select i1
a.cmp(&b)
}

// CHECK-LABEL: @cmp_char
#[no_mangle]
pub fn cmp_char(a: char, b: char) -> Ordering {
// llvm-20: call{{.*}} i8 @llvm.ucmp.i8.i32
// llvm-pre-20: icmp ult
// llvm-pre-20: icmp ne
// llvm-pre-20: zext i1
// llvm-pre-20: select i1
a.cmp(&b)
}

// CHECK-LABEL: @cmp_tuple
#[no_mangle]
pub fn cmp_tuple(a: (i16, u16), b: (i16, u16)) -> Ordering {
// llvm-20-DAG: call{{.*}} i8 @llvm.ucmp.i8.i16
// llvm-20-DAG: call{{.*}} i8 @llvm.scmp.i8.i16
// llvm-20: ret i8
// llvm-pre-20: icmp slt
// llvm-pre-20: icmp ne
// llvm-pre-20: zext i1
// llvm-pre-20: select i1
// llvm-pre-20: icmp ult
// llvm-pre-20: icmp ne
// llvm-pre-20: zext i1
// llvm-pre-20: select i1
// llvm-pre-20: select i1
a.cmp(&b)
}

0 comments on commit 28ef42e

Please sign in to comment.