Skip to content

Commit cbe2522

Browse files
authored
Rollup merge of #114382 - scottmcm:compare-bytes-intrinsic, r=cjgillot
Add a new `compare_bytes` intrinsic instead of calling `memcmp` directly As discussed in #113435, this lets the backends be the place that can have the "don't call the function if n == 0" logic, if it's needed for the target. (I didn't actually *add* those checks, though, since as I understood it we didn't actually need them on known targets?) Doing this also let me make it `const` (unstable), which I don't think `extern "C" fn memcmp` can be. cc `@RalfJung` `@Amanieu`
2 parents f5df519 + 75277a6 commit cbe2522

File tree

17 files changed

+270
-22
lines changed

17 files changed

+270
-22
lines changed

Diff for: compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,20 @@ fn codegen_regular_intrinsic_call<'tcx>(
11551155
ret.write_cvalue(fx, CValue::by_val(is_eq_value, ret.layout()));
11561156
}
11571157

1158+
sym::compare_bytes => {
1159+
intrinsic_args!(fx, args => (lhs_ptr, rhs_ptr, bytes_val); intrinsic);
1160+
let lhs_ptr = lhs_ptr.load_scalar(fx);
1161+
let rhs_ptr = rhs_ptr.load_scalar(fx);
1162+
let bytes_val = bytes_val.load_scalar(fx);
1163+
1164+
let params = vec![AbiParam::new(fx.pointer_type); 3];
1165+
let returns = vec![AbiParam::new(types::I32)];
1166+
let args = &[lhs_ptr, rhs_ptr, bytes_val];
1167+
// Here we assume that the `memcmp` provided by the target is a NOP for size 0.
1168+
let cmp = fx.lib_call("memcmp", params, returns, args)[0];
1169+
ret.write_cvalue(fx, CValue::by_val(cmp, ret.layout()));
1170+
}
1171+
11581172
sym::const_allocate => {
11591173
intrinsic_args!(fx, args => (_size, _align); intrinsic);
11601174

Diff for: compiler/rustc_codegen_gcc/src/intrinsic/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,21 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'gcc, 'tcx> {
302302
}
303303
}
304304

305+
sym::compare_bytes => {
306+
let a = args[0].immediate();
307+
let b = args[1].immediate();
308+
let n = args[2].immediate();
309+
310+
let void_ptr_type = self.context.new_type::<*const ()>();
311+
let a_ptr = self.bitcast(a, void_ptr_type);
312+
let b_ptr = self.bitcast(b, void_ptr_type);
313+
314+
// Here we assume that the `memcmp` provided by the target is a NOP for size 0.
315+
let builtin = self.context.get_builtin_function("memcmp");
316+
let cmp = self.context.new_call(None, builtin, &[a_ptr, b_ptr, n]);
317+
self.sext(cmp, self.type_ix(32))
318+
}
319+
305320
sym::black_box => {
306321
args[0].val.store(self, result);
307322

Diff for: compiler/rustc_codegen_llvm/src/context.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,8 @@ impl<'ll> CodegenCx<'ll, '_> {
891891
ifn!("llvm.prefetch", fn(ptr, t_i32, t_i32, t_i32) -> void);
892892

893893
// This isn't an "LLVM intrinsic", but LLVM's optimization passes
894-
// recognize it like one and we assume it exists in `core::slice::cmp`
894+
// recognize it like one (including turning it into `bcmp` sometimes)
895+
// and we use it to implement intrinsics like `raw_eq` and `compare_bytes`
895896
match self.sess().target.arch.as_ref() {
896897
"avr" | "msp430" => ifn!("memcmp", fn(ptr, ptr, t_isize) -> t_i16),
897898
_ => ifn!("memcmp", fn(ptr, ptr, t_isize) -> t_i32),

Diff for: compiler/rustc_codegen_llvm/src/intrinsic.rs

+10
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,16 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> {
329329
}
330330
}
331331

332+
sym::compare_bytes => {
333+
// Here we assume that the `memcmp` provided by the target is a NOP for size 0.
334+
let cmp = self.call_intrinsic(
335+
"memcmp",
336+
&[args[0].immediate(), args[1].immediate(), args[2].immediate()],
337+
);
338+
// Some targets have `memcmp` returning `i16`, but the intrinsic is always `i32`.
339+
self.sext(cmp, self.type_ix(32))
340+
}
341+
332342
sym::black_box => {
333343
args[0].val.store(self, result);
334344
let result_val_span = [result.llval];

Diff for: compiler/rustc_const_eval/src/interpret/intrinsics.rs

+22
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
261261
sym::write_bytes => {
262262
self.write_bytes_intrinsic(&args[0], &args[1], &args[2])?;
263263
}
264+
sym::compare_bytes => {
265+
let result = self.compare_bytes_intrinsic(&args[0], &args[1], &args[2])?;
266+
self.write_scalar(result, dest)?;
267+
}
264268
sym::arith_offset => {
265269
let ptr = self.read_pointer(&args[0])?;
266270
let offset_count = self.read_target_isize(&args[1])?;
@@ -643,6 +647,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
643647
self.write_bytes_ptr(dst, bytes)
644648
}
645649

650+
pub(crate) fn compare_bytes_intrinsic(
651+
&mut self,
652+
left: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::Provenance>,
653+
right: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::Provenance>,
654+
byte_count: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::Provenance>,
655+
) -> InterpResult<'tcx, Scalar<M::Provenance>> {
656+
let left = self.read_pointer(left)?;
657+
let right = self.read_pointer(right)?;
658+
let n = Size::from_bytes(self.read_target_usize(byte_count)?);
659+
660+
let left_bytes = self.read_bytes_ptr_strip_provenance(left, n)?;
661+
let right_bytes = self.read_bytes_ptr_strip_provenance(right, n)?;
662+
663+
// `Ordering`'s discriminants are -1/0/+1, so casting does the right thing.
664+
let result = Ord::cmp(left_bytes, right_bytes) as i32;
665+
Ok(Scalar::from_i32(result))
666+
}
667+
646668
pub(crate) fn raw_eq_intrinsic(
647669
&mut self,
648670
lhs: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::Provenance>,

Diff for: compiler/rustc_hir_analysis/src/check/intrinsic.rs

+4
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
273273
],
274274
Ty::new_unit(tcx),
275275
),
276+
sym::compare_bytes => {
277+
let byte_ptr = Ty::new_imm_ptr(tcx, tcx.types.u8);
278+
(0, vec![byte_ptr, byte_ptr, tcx.types.usize], tcx.types.i32)
279+
}
276280
sym::write_bytes | sym::volatile_set_memory => (
277281
1,
278282
vec![

Diff for: compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ symbols! {
498498
cold,
499499
collapse_debuginfo,
500500
column,
501+
compare_bytes,
501502
compare_exchange,
502503
compare_exchange_weak,
503504
compile_error,

Diff for: library/core/src/intrinsics.rs

+34
Original file line numberDiff line numberDiff line change
@@ -2385,6 +2385,25 @@ extern "rust-intrinsic" {
23852385
#[rustc_nounwind]
23862386
pub fn raw_eq<T>(a: &T, b: &T) -> bool;
23872387

2388+
/// Lexicographically compare `[left, left + bytes)` and `[right, right + bytes)`
2389+
/// as unsigned bytes, returning negative if `left` is less, zero if all the
2390+
/// bytes match, or positive if `right` is greater.
2391+
///
2392+
/// This underlies things like `<[u8]>::cmp`, and will usually lower to `memcmp`.
2393+
///
2394+
/// # Safety
2395+
///
2396+
/// `left` and `right` must each be [valid] for reads of `bytes` bytes.
2397+
///
2398+
/// Note that this applies to the whole range, not just until the first byte
2399+
/// that differs. That allows optimizations that can read in large chunks.
2400+
///
2401+
/// [valid]: crate::ptr#safety
2402+
#[cfg(not(bootstrap))]
2403+
#[rustc_const_unstable(feature = "const_intrinsic_compare_bytes", issue = "none")]
2404+
#[rustc_nounwind]
2405+
pub fn compare_bytes(left: *const u8, right: *const u8, bytes: usize) -> i32;
2406+
23882407
/// See documentation of [`std::hint::black_box`] for details.
23892408
///
23902409
/// [`std::hint::black_box`]: crate::hint::black_box
@@ -2825,3 +2844,18 @@ pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
28252844
write_bytes(dst, val, count)
28262845
}
28272846
}
2847+
2848+
/// Backfill for bootstrap
2849+
#[cfg(bootstrap)]
2850+
pub unsafe fn compare_bytes(left: *const u8, right: *const u8, bytes: usize) -> i32 {
2851+
extern "C" {
2852+
fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> crate::ffi::c_int;
2853+
}
2854+
2855+
if bytes != 0 {
2856+
// SAFETY: Since bytes is non-zero, the caller has met `memcmp`'s requirements.
2857+
unsafe { memcmp(left, right, bytes).into() }
2858+
} else {
2859+
0
2860+
}
2861+
}

Diff for: library/core/src/slice/cmp.rs

+6-15
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
11
//! Comparison traits for `[T]`.
22
33
use crate::cmp::{self, BytewiseEq, Ordering};
4-
use crate::ffi;
4+
use crate::intrinsics::compare_bytes;
55
use crate::mem;
66

77
use super::from_raw_parts;
88
use super::memchr;
99

10-
extern "C" {
11-
/// Calls implementation provided memcmp.
12-
///
13-
/// Interprets the data as u8.
14-
///
15-
/// Returns 0 for equal, < 0 for less than and > 0 for greater
16-
/// than.
17-
fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> ffi::c_int;
18-
}
19-
2010
#[stable(feature = "rust1", since = "1.0.0")]
2111
impl<A, B> PartialEq<[B]> for [A]
2212
where
@@ -74,7 +64,8 @@ where
7464
}
7565
}
7666

77-
// Use memcmp for bytewise equality when the types allow
67+
// When each element can be compared byte-wise, we can compare all the bytes
68+
// from the whole size in one call to the intrinsics.
7869
impl<A, B> SlicePartialEq<B> for [A]
7970
where
8071
A: BytewiseEq<B>,
@@ -88,7 +79,7 @@ where
8879
// The two slices have been checked to have the same size above.
8980
unsafe {
9081
let size = mem::size_of_val(self);
91-
memcmp(self.as_ptr() as *const u8, other.as_ptr() as *const u8, size) == 0
82+
compare_bytes(self.as_ptr() as *const u8, other.as_ptr() as *const u8, size) == 0
9283
}
9384
}
9485
}
@@ -183,7 +174,7 @@ impl<A: Ord> SliceOrd for A {
183174
}
184175
}
185176

186-
// memcmp compares a sequence of unsigned bytes lexicographically.
177+
// `compare_bytes` compares a sequence of unsigned bytes lexicographically.
187178
// this matches the order we want for [u8], but no others (not even [i8]).
188179
impl SliceOrd for u8 {
189180
#[inline]
@@ -195,7 +186,7 @@ impl SliceOrd for u8 {
195186
// SAFETY: `left` and `right` are references and are thus guaranteed to be valid.
196187
// We use the minimum of both lengths which guarantees that both regions are
197188
// valid for reads in that interval.
198-
let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize };
189+
let mut order = unsafe { compare_bytes(left.as_ptr(), right.as_ptr(), len) as isize };
199190
if order == 0 {
200191
order = diff;
201192
}

Diff for: src/tools/miri/tests/fail/uninit_buffer.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: Undefined Behavior: reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory
22
--> RUSTLIB/core/src/slice/cmp.rs:LL:CC
33
|
4-
LL | let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize };
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory
4+
LL | let mut order = unsafe { compare_bytes(left.as_ptr(), right.as_ptr(), len) as isize };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

Diff for: src/tools/miri/tests/fail/uninit_buffer_with_provenance.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: Undefined Behavior: reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory
22
--> RUSTLIB/core/src/slice/cmp.rs:LL:CC
33
|
4-
LL | let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize };
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory
4+
LL | let mut order = unsafe { compare_bytes(left.as_ptr(), right.as_ptr(), len) as isize };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

Diff for: tests/codegen/intrinsics/compare_bytes.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// revisions: INT32 INT16
2+
// compile-flags: -O
3+
// [INT32] ignore-16bit
4+
// [INT16] only-16bit
5+
6+
#![crate_type = "lib"]
7+
#![feature(core_intrinsics)]
8+
9+
use std::intrinsics::compare_bytes;
10+
11+
#[no_mangle]
12+
// CHECK-LABEL: @bytes_cmp(
13+
pub unsafe fn bytes_cmp(a: *const u8, b: *const u8, n: usize) -> i32 {
14+
// INT32: %[[TEMP:.+]] = tail call i32 @memcmp(ptr %a, ptr %b, {{i32|i64}} %n)
15+
// INT32-NOT: sext
16+
// INT32: ret i32 %[[TEMP]]
17+
18+
// INT16: %[[TEMP1:.+]] = tail call i16 @memcmp(ptr %a, ptr %b, i16 %n)
19+
// INT16: %[[TEMP2:.+]] = sext i16 %[[TEMP1]] to i32
20+
// INT16: ret i32 %[[TEMP2]]
21+
compare_bytes(a, b, n)
22+
}
23+
24+
// Ensure that, even though there's an `sext` emitted by the intrinsic,
25+
// that doesn't end up pessiming checks against zero.
26+
#[no_mangle]
27+
// CHECK-LABEL: @bytes_eq(
28+
pub unsafe fn bytes_eq(a: *const u8, b: *const u8, n: usize) -> bool {
29+
// CHECK: call {{.+}} @{{bcmp|memcmp}}(ptr %a, ptr %b, {{i16|i32|i64}} %n)
30+
// CHECK-NOT: sext
31+
// INT32: icmp eq i32
32+
// INT16: icmp eq i16
33+
compare_bytes(a, b, n) == 0_i32
34+
}

Diff for: tests/ui/consts/const-compare-bytes-ub.rs

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// check-fail
2+
3+
#![feature(core_intrinsics)]
4+
#![feature(const_intrinsic_compare_bytes)]
5+
use std::intrinsics::compare_bytes;
6+
use std::mem::MaybeUninit;
7+
8+
fn main() {
9+
const LHS_NULL: i32 = unsafe {
10+
compare_bytes(0 as *const u8, 2 as *const u8, 0)
11+
//~^ ERROR evaluation of constant value failed
12+
};
13+
const RHS_NULL: i32 = unsafe {
14+
compare_bytes(1 as *const u8, 0 as *const u8, 0)
15+
//~^ ERROR evaluation of constant value failed
16+
};
17+
const DANGLING_PTR_NON_ZERO_LENGTH: i32 = unsafe {
18+
compare_bytes(1 as *const u8, 2 as *const u8, 1)
19+
//~^ ERROR evaluation of constant value failed
20+
};
21+
const LHS_OUT_OF_BOUNDS: i32 = unsafe {
22+
compare_bytes([1, 2, 3].as_ptr(), [1, 2, 3, 4].as_ptr(), 4)
23+
//~^ ERROR evaluation of constant value failed
24+
};
25+
const RHS_OUT_OF_BOUNDS: i32 = unsafe {
26+
compare_bytes([1, 2, 3, 4].as_ptr(), [1, 2, 3].as_ptr(), 4)
27+
//~^ ERROR evaluation of constant value failed
28+
};
29+
const LHS_UNINIT: i32 = unsafe {
30+
compare_bytes(MaybeUninit::uninit().as_ptr(), [1].as_ptr(), 1)
31+
//~^ ERROR evaluation of constant value failed
32+
};
33+
const RHS_UNINIT: i32 = unsafe {
34+
compare_bytes([1].as_ptr(), MaybeUninit::uninit().as_ptr(), 1)
35+
//~^ ERROR evaluation of constant value failed
36+
};
37+
const WITH_PROVENANCE: i32 = unsafe {
38+
compare_bytes([&1].as_ptr().cast(), [&2].as_ptr().cast(), std::mem::size_of::<usize>())
39+
//~^ ERROR evaluation of constant value failed
40+
};
41+
}

Diff for: tests/ui/consts/const-compare-bytes-ub.stderr

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
error[E0080]: evaluation of constant value failed
2+
--> $DIR/const-compare-bytes-ub.rs:10:9
3+
|
4+
LL | compare_bytes(0 as *const u8, 2 as *const u8, 0)
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: null pointer is a dangling pointer (it has no provenance)
6+
7+
error[E0080]: evaluation of constant value failed
8+
--> $DIR/const-compare-bytes-ub.rs:14:9
9+
|
10+
LL | compare_bytes(1 as *const u8, 0 as *const u8, 0)
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: null pointer is a dangling pointer (it has no provenance)
12+
13+
error[E0080]: evaluation of constant value failed
14+
--> $DIR/const-compare-bytes-ub.rs:18:9
15+
|
16+
LL | compare_bytes(1 as *const u8, 2 as *const u8, 1)
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: 0x1[noalloc] is a dangling pointer (it has no provenance)
18+
19+
error[E0080]: evaluation of constant value failed
20+
--> $DIR/const-compare-bytes-ub.rs:22:9
21+
|
22+
LL | compare_bytes([1, 2, 3].as_ptr(), [1, 2, 3, 4].as_ptr(), 4)
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: alloc6 has size 3, so pointer to 4 bytes starting at offset 0 is out-of-bounds
24+
25+
error[E0080]: evaluation of constant value failed
26+
--> $DIR/const-compare-bytes-ub.rs:26:9
27+
|
28+
LL | compare_bytes([1, 2, 3, 4].as_ptr(), [1, 2, 3].as_ptr(), 4)
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: alloc13 has size 3, so pointer to 4 bytes starting at offset 0 is out-of-bounds
30+
31+
error[E0080]: evaluation of constant value failed
32+
--> $DIR/const-compare-bytes-ub.rs:30:9
33+
|
34+
LL | compare_bytes(MaybeUninit::uninit().as_ptr(), [1].as_ptr(), 1)
35+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at alloc17[0x0..0x1], but memory is uninitialized at [0x0..0x1], and this operation requires initialized memory
36+
37+
error[E0080]: evaluation of constant value failed
38+
--> $DIR/const-compare-bytes-ub.rs:34:9
39+
|
40+
LL | compare_bytes([1].as_ptr(), MaybeUninit::uninit().as_ptr(), 1)
41+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at alloc25[0x0..0x1], but memory is uninitialized at [0x0..0x1], and this operation requires initialized memory
42+
43+
error[E0080]: evaluation of constant value failed
44+
--> $DIR/const-compare-bytes-ub.rs:38:9
45+
|
46+
LL | compare_bytes([&1].as_ptr().cast(), [&2].as_ptr().cast(), std::mem::size_of::<usize>())
47+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into integer
48+
|
49+
= help: this code performed an operation that depends on the underlying bytes representing a pointer
50+
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
51+
52+
error: aborting due to 8 previous errors
53+
54+
For more information about this error, try `rustc --explain E0080`.

0 commit comments

Comments
 (0)