Skip to content

Commit 1bcb0ec

Browse files
committed
assume value ranges in transmute
Fixes rust-lang#109958
1 parent d8fc819 commit 1bcb0ec

File tree

4 files changed

+253
-16
lines changed

4 files changed

+253
-16
lines changed

Diff for: compiler/rustc_codegen_ssa/src/mir/rvalue.rs

+58-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_middle::mir::Operand;
1212
use rustc_middle::ty::cast::{CastTy, IntTy};
1313
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, TyAndLayout};
1414
use rustc_middle::ty::{self, adjustment::PointerCast, Instance, Ty, TyCtxt};
15+
use rustc_session::config::OptLevel;
1516
use rustc_span::source_map::{Span, DUMMY_SP};
1617
use rustc_target::abi::{self, FIRST_VARIANT};
1718

@@ -231,10 +232,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
231232
(ScalarOrZst::Scalar(in_scalar), ScalarOrZst::Scalar(out_scalar))
232233
if in_scalar.size(self.cx) == out_scalar.size(self.cx) =>
233234
{
235+
let operand_bty = bx.backend_type(operand.layout);
234236
let cast_bty = bx.backend_type(cast);
235-
Some(OperandValue::Immediate(
236-
self.transmute_immediate(bx, imm, in_scalar, out_scalar, cast_bty),
237-
))
237+
Some(OperandValue::Immediate(self.transmute_immediate(
238+
bx,
239+
imm,
240+
in_scalar,
241+
operand_bty,
242+
out_scalar,
243+
cast_bty,
244+
)))
238245
}
239246
_ => None,
240247
}
@@ -250,11 +257,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
250257
&& in_a.size(self.cx) == out_a.size(self.cx)
251258
&& in_b.size(self.cx) == out_b.size(self.cx)
252259
{
260+
let in_a_ibty = bx.scalar_pair_element_backend_type(operand.layout, 0, false);
261+
let in_b_ibty = bx.scalar_pair_element_backend_type(operand.layout, 1, false);
253262
let out_a_ibty = bx.scalar_pair_element_backend_type(cast, 0, false);
254263
let out_b_ibty = bx.scalar_pair_element_backend_type(cast, 1, false);
255264
Some(OperandValue::Pair(
256-
self.transmute_immediate(bx, imm_a, in_a, out_a, out_a_ibty),
257-
self.transmute_immediate(bx, imm_b, in_b, out_b, out_b_ibty),
265+
self.transmute_immediate(bx, imm_a, in_a, in_a_ibty, out_a, out_a_ibty),
266+
self.transmute_immediate(bx, imm_b, in_b, in_b_ibty, out_b, out_b_ibty),
258267
))
259268
} else {
260269
None
@@ -273,13 +282,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
273282
bx: &mut Bx,
274283
mut imm: Bx::Value,
275284
from_scalar: abi::Scalar,
285+
from_backend_ty: Bx::Type,
276286
to_scalar: abi::Scalar,
277287
to_backend_ty: Bx::Type,
278288
) -> Bx::Value {
279289
debug_assert_eq!(from_scalar.size(self.cx), to_scalar.size(self.cx));
280290

281291
use abi::Primitive::*;
282292
imm = bx.from_immediate(imm);
293+
self.assume_scalar_range(bx, imm, from_scalar, from_backend_ty);
283294
imm = match (from_scalar.primitive(), to_scalar.primitive()) {
284295
(Int(..) | F32 | F64, Int(..) | F32 | F64) => bx.bitcast(imm, to_backend_ty),
285296
(Pointer(..), Pointer(..)) => bx.pointercast(imm, to_backend_ty),
@@ -294,10 +305,52 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
294305
bx.bitcast(int_imm, to_backend_ty)
295306
}
296307
};
308+
self.assume_scalar_range(bx, imm, to_scalar, to_backend_ty);
297309
imm = bx.to_immediate_scalar(imm, to_scalar);
298310
imm
299311
}
300312

313+
fn assume_scalar_range(
314+
&self,
315+
bx: &mut Bx,
316+
imm: Bx::Value,
317+
scalar: abi::Scalar,
318+
backend_ty: Bx::Type,
319+
) {
320+
if matches!(self.cx.sess().opts.optimize, OptLevel::No | OptLevel::Less)
321+
|| !matches!(scalar.primitive(), abi::Primitive::Int(..))
322+
|| scalar.is_always_valid(self.cx)
323+
{
324+
return;
325+
}
326+
327+
let abi::WrappingRange { start, end } = scalar.valid_range(self.cx);
328+
329+
if start <= end {
330+
if start > 0 {
331+
let low = bx.const_uint_big(backend_ty, start);
332+
let cmp = bx.icmp(IntPredicate::IntUGE, imm, low);
333+
bx.assume(cmp);
334+
}
335+
336+
let type_max = scalar.size(self.cx).unsigned_int_max();
337+
if end < type_max {
338+
let high = bx.const_uint_big(backend_ty, end);
339+
let cmp = bx.icmp(IntPredicate::IntULE, imm, high);
340+
bx.assume(cmp);
341+
}
342+
} else {
343+
let low = bx.const_uint_big(backend_ty, start);
344+
let cmp_low = bx.icmp(IntPredicate::IntUGE, imm, low);
345+
346+
let high = bx.const_uint_big(backend_ty, end);
347+
let cmp_high = bx.icmp(IntPredicate::IntULE, imm, high);
348+
349+
let or = bx.or(cmp_low, cmp_high);
350+
bx.assume(or);
351+
}
352+
}
353+
301354
pub fn codegen_rvalue_unsized(
302355
&mut self,
303356
bx: &mut Bx,

Diff for: tests/codegen/intrinsics/transmute-niched.rs

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// revisions: OPT DBG
2+
// [OPT] compile-flags: -C opt-level=3 -C no-prepopulate-passes
3+
// [DBG] compile-flags: -C opt-level=0 -C no-prepopulate-passes
4+
// only-64bit (so I don't need to worry about usize)
5+
// min-llvm-version: 15.0 # this test assumes `ptr`s
6+
7+
#![crate_type = "lib"]
8+
9+
use std::mem::transmute;
10+
use std::num::NonZeroU32;
11+
12+
#[repr(u8)]
13+
pub enum SmallEnum {
14+
A = 10,
15+
B = 11,
16+
C = 12,
17+
}
18+
19+
// CHECK-LABEL: @check_to_enum(
20+
#[no_mangle]
21+
pub unsafe fn check_to_enum(x: i8) -> SmallEnum {
22+
// OPT: %0 = icmp uge i8 %x, 10
23+
// OPT: call void @llvm.assume(i1 %0)
24+
// OPT: %1 = icmp ule i8 %x, 12
25+
// OPT: call void @llvm.assume(i1 %1)
26+
// DBG-NOT: icmp
27+
// DBG-NOT: assume
28+
// CHECK: ret i8 %x
29+
30+
transmute(x)
31+
}
32+
33+
// CHECK-LABEL: @check_from_enum(
34+
#[no_mangle]
35+
pub unsafe fn check_from_enum(x: SmallEnum) -> i8 {
36+
// OPT: %0 = icmp uge i8 %x, 10
37+
// OPT: call void @llvm.assume(i1 %0)
38+
// OPT: %1 = icmp ule i8 %x, 12
39+
// OPT: call void @llvm.assume(i1 %1)
40+
// DBG-NOT: icmp
41+
// DBG-NOT: assume
42+
// CHECK: ret i8 %x
43+
44+
transmute(x)
45+
}
46+
47+
// CHECK-LABEL: @check_to_ordering(
48+
#[no_mangle]
49+
pub unsafe fn check_to_ordering(x: u8) -> std::cmp::Ordering {
50+
// OPT: %0 = icmp uge i8 %x, -1
51+
// OPT: %1 = icmp ule i8 %x, 1
52+
// OPT: %2 = or i1 %0, %1
53+
// OPT: call void @llvm.assume(i1 %2)
54+
// DBG-NOT: icmp
55+
// DBG-NOT: assume
56+
// CHECK: ret i8 %x
57+
58+
transmute(x)
59+
}
60+
61+
// CHECK-LABEL: @check_from_ordering(
62+
#[no_mangle]
63+
pub unsafe fn check_from_ordering(x: std::cmp::Ordering) -> u8 {
64+
// OPT: %0 = icmp uge i8 %x, -1
65+
// OPT: %1 = icmp ule i8 %x, 1
66+
// OPT: %2 = or i1 %0, %1
67+
// OPT: call void @llvm.assume(i1 %2)
68+
// DBG-NOT: icmp
69+
// DBG-NOT: assume
70+
// CHECK: ret i8 %x
71+
72+
transmute(x)
73+
}
74+
75+
#[repr(i32)]
76+
pub enum Minus100ToPlus100 {
77+
A = -100,
78+
B = -90,
79+
C = -80,
80+
D = -70,
81+
E = -60,
82+
F = -50,
83+
G = -40,
84+
H = -30,
85+
I = -20,
86+
J = -10,
87+
K = 0,
88+
L = 10,
89+
M = 20,
90+
N = 30,
91+
O = 40,
92+
P = 50,
93+
Q = 60,
94+
R = 70,
95+
S = 80,
96+
T = 90,
97+
U = 100,
98+
}
99+
100+
// CHECK-LABEL: @check_enum_from_char(
101+
#[no_mangle]
102+
pub unsafe fn check_enum_from_char(x: char) -> Minus100ToPlus100 {
103+
// OPT: %0 = icmp ule i32 %x, 1114111
104+
// OPT: call void @llvm.assume(i1 %0)
105+
// OPT: %1 = icmp uge i32 %x, -100
106+
// OPT: %2 = icmp ule i32 %x, 100
107+
// OPT: %3 = or i1 %1, %2
108+
// OPT: call void @llvm.assume(i1 %3)
109+
// DBG-NOT: icmp
110+
// DBG-NOT: assume
111+
// CHECK: ret i32 %x
112+
113+
transmute(x)
114+
}
115+
116+
// CHECK-LABEL: @check_enum_to_char(
117+
#[no_mangle]
118+
pub unsafe fn check_enum_to_char(x: Minus100ToPlus100) -> char {
119+
// OPT: %0 = icmp uge i32 %x, -100
120+
// OPT: %1 = icmp ule i32 %x, 100
121+
// OPT: %2 = or i1 %0, %1
122+
// OPT: call void @llvm.assume(i1 %2)
123+
// OPT: %3 = icmp ule i32 %x, 1114111
124+
// OPT: call void @llvm.assume(i1 %3)
125+
// DBG-NOT: icmp
126+
// DBG-NOT: assume
127+
// CHECK: ret i32 %x
128+
129+
transmute(x)
130+
}
131+
132+
// CHECK-LABEL: @check_swap_pair(
133+
#[no_mangle]
134+
pub unsafe fn check_swap_pair(x: (char, NonZeroU32)) -> (NonZeroU32, char) {
135+
// OPT: %0 = icmp ule i32 %x.0, 1114111
136+
// OPT: call void @llvm.assume(i1 %0)
137+
// OPT: %1 = icmp uge i32 %x.0, 1
138+
// OPT: call void @llvm.assume(i1 %1)
139+
// OPT: %2 = icmp uge i32 %x.1, 1
140+
// OPT: call void @llvm.assume(i1 %2)
141+
// OPT: %3 = icmp ule i32 %x.1, 1114111
142+
// OPT: call void @llvm.assume(i1 %3)
143+
// DBG-NOT: icmp
144+
// DBG-NOT: assume
145+
// CHECK: %[[P1:.+]] = insertvalue { i32, i32 } poison, i32 %x.0, 0
146+
// CHECK: %[[P2:.+]] = insertvalue { i32, i32 } %[[P1]], i32 %x.1, 1
147+
// CHECK: ret { i32, i32 } %[[P2]]
148+
149+
transmute(x)
150+
}
151+
152+
// CHECK-LABEL: @check_bool_from_ordering(
153+
#[no_mangle]
154+
pub unsafe fn check_bool_from_ordering(x: std::cmp::Ordering) -> bool {
155+
// OPT: %0 = icmp uge i8 %x, -1
156+
// OPT: %1 = icmp ule i8 %x, 1
157+
// OPT: %2 = or i1 %0, %1
158+
// OPT: call void @llvm.assume(i1 %2)
159+
// OPT: %3 = icmp ule i8 %x, 1
160+
// OPT: call void @llvm.assume(i1 %3)
161+
// DBG-NOT: icmp
162+
// DBG-NOT: assume
163+
// CHECK: %[[R:.+]] = trunc i8 %x to i1
164+
// CHECK: ret i1 %[[R]]
165+
166+
transmute(x)
167+
}
168+
169+
// CHECK-LABEL: @check_bool_to_ordering(
170+
#[no_mangle]
171+
pub unsafe fn check_bool_to_ordering(x: bool) -> std::cmp::Ordering {
172+
// CHECK: %0 = zext i1 %x to i8
173+
// OPT: %1 = icmp ule i8 %0, 1
174+
// OPT: call void @llvm.assume(i1 %1)
175+
// OPT: %2 = icmp uge i8 %0, -1
176+
// OPT: %3 = icmp ule i8 %0, 1
177+
// OPT: %4 = or i1 %2, %3
178+
// OPT: call void @llvm.assume(i1 %4)
179+
// DBG-NOT: icmp
180+
// DBG-NOT: assume
181+
// CHECK: ret i8 %0
182+
183+
transmute(x)
184+
}

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -169,17 +169,17 @@ pub unsafe fn check_aggregate_from_bool(x: bool) -> Aggregate8 {
169169
#[no_mangle]
170170
pub unsafe fn check_byte_to_bool(x: u8) -> bool {
171171
// CHECK-NOT: alloca
172-
// CHECK: %0 = trunc i8 %x to i1
173-
// CHECK: ret i1 %0
172+
// CHECK: %[[R:.+]] = trunc i8 %x to i1
173+
// CHECK: ret i1 %[[R]]
174174
transmute(x)
175175
}
176176

177177
// CHECK-LABEL: @check_byte_from_bool(
178178
#[no_mangle]
179179
pub unsafe fn check_byte_from_bool(x: bool) -> u8 {
180180
// CHECK-NOT: alloca
181-
// CHECK: %0 = zext i1 %x to i8
182-
// CHECK: ret i8 %0
181+
// CHECK: %[[R:.+]] = zext i1 %x to i8
182+
// CHECK: ret i8 %[[R:.+]]
183183
transmute(x)
184184
}
185185

Diff for: tests/codegen/transmute-scalar.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// compile-flags: -O -C no-prepopulate-passes
1+
// compile-flags: -C opt-level=0 -C no-prepopulate-passes
22
// min-llvm-version: 15.0 # this test assumes `ptr`s and thus no `pointercast`s
33

44
#![crate_type = "lib"]
@@ -10,46 +10,46 @@
1010
// However, `bitcast`s and `ptrtoint`s and `inttoptr`s are still worth doing when
1111
// that allows us to avoid the `alloca`s entirely; see `rvalue_creates_operand`.
1212

13-
// CHECK-LABEL: define{{.*}}i32 @f32_to_bits(float noundef %x)
13+
// CHECK-LABEL: define{{.*}}i32 @f32_to_bits(float %x)
1414
// CHECK: %0 = bitcast float %x to i32
1515
// CHECK-NEXT: ret i32 %0
1616
#[no_mangle]
1717
pub fn f32_to_bits(x: f32) -> u32 {
1818
unsafe { std::mem::transmute(x) }
1919
}
2020

21-
// CHECK-LABEL: define{{.*}}i8 @bool_to_byte(i1 noundef zeroext %b)
21+
// CHECK-LABEL: define{{.*}}i8 @bool_to_byte(i1 zeroext %b)
2222
// CHECK: %0 = zext i1 %b to i8
2323
// CHECK-NEXT: ret i8 %0
2424
#[no_mangle]
2525
pub fn bool_to_byte(b: bool) -> u8 {
2626
unsafe { std::mem::transmute(b) }
2727
}
2828

29-
// CHECK-LABEL: define{{.*}}noundef zeroext i1 @byte_to_bool(i8 noundef %byte)
29+
// CHECK-LABEL: define{{.*}}zeroext i1 @byte_to_bool(i8 %byte)
3030
// CHECK: %0 = trunc i8 %byte to i1
3131
// CHECK-NEXT: ret i1 %0
3232
#[no_mangle]
3333
pub unsafe fn byte_to_bool(byte: u8) -> bool {
3434
std::mem::transmute(byte)
3535
}
3636

37-
// CHECK-LABEL: define{{.*}}ptr @ptr_to_ptr(ptr noundef %p)
37+
// CHECK-LABEL: define{{.*}}ptr @ptr_to_ptr(ptr %p)
3838
// CHECK: ret ptr %p
3939
#[no_mangle]
4040
pub fn ptr_to_ptr(p: *mut u16) -> *mut u8 {
4141
unsafe { std::mem::transmute(p) }
4242
}
4343

44-
// CHECK: define{{.*}}[[USIZE:i[0-9]+]] @ptr_to_int(ptr noundef %p)
44+
// CHECK: define{{.*}}[[USIZE:i[0-9]+]] @ptr_to_int(ptr %p)
4545
// CHECK: %0 = ptrtoint ptr %p to [[USIZE]]
4646
// CHECK-NEXT: ret [[USIZE]] %0
4747
#[no_mangle]
4848
pub fn ptr_to_int(p: *mut u16) -> usize {
4949
unsafe { std::mem::transmute(p) }
5050
}
5151

52-
// CHECK: define{{.*}}ptr @int_to_ptr([[USIZE]] noundef %i)
52+
// CHECK: define{{.*}}ptr @int_to_ptr([[USIZE]] %i)
5353
// CHECK: %0 = inttoptr [[USIZE]] %i to ptr
5454
// CHECK-NEXT: ret ptr %0
5555
#[no_mangle]

0 commit comments

Comments
 (0)