Skip to content

Commit 7e23d18

Browse files
committed
Auto merge of #109993 - scottmcm:transmute-niches, r=oli-obk
`assume` value ranges in `transmute` Fixes #109958
2 parents 13fc33e + baf98e7 commit 7e23d18

File tree

5 files changed

+371
-16
lines changed

5 files changed

+371
-16
lines changed

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

+67-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,21 @@ 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+
294+
// When scalars are passed by value, there's no metadata recording their
295+
// valid ranges. For example, `char`s are passed as just `i32`, with no
296+
// way for LLVM to know that they're 0x10FFFF at most. Thus we assume
297+
// the range of the input value too, not just the output range.
298+
self.assume_scalar_range(bx, imm, from_scalar, from_backend_ty);
299+
283300
imm = match (from_scalar.primitive(), to_scalar.primitive()) {
284301
(Int(..) | F32 | F64, Int(..) | F32 | F64) => bx.bitcast(imm, to_backend_ty),
285302
(Pointer(..), Pointer(..)) => bx.pointercast(imm, to_backend_ty),
@@ -294,10 +311,55 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
294311
bx.bitcast(int_imm, to_backend_ty)
295312
}
296313
};
314+
self.assume_scalar_range(bx, imm, to_scalar, to_backend_ty);
297315
imm = bx.to_immediate_scalar(imm, to_scalar);
298316
imm
299317
}
300318

319+
fn assume_scalar_range(
320+
&self,
321+
bx: &mut Bx,
322+
imm: Bx::Value,
323+
scalar: abi::Scalar,
324+
backend_ty: Bx::Type,
325+
) {
326+
if matches!(self.cx.sess().opts.optimize, OptLevel::No | OptLevel::Less)
327+
// For now, the critical niches are all over `Int`eger values.
328+
// Should floating-point values or pointers ever get more complex
329+
// niches, then this code will probably want to handle them too.
330+
|| !matches!(scalar.primitive(), abi::Primitive::Int(..))
331+
|| scalar.is_always_valid(self.cx)
332+
{
333+
return;
334+
}
335+
336+
let abi::WrappingRange { start, end } = scalar.valid_range(self.cx);
337+
338+
if start <= end {
339+
if start > 0 {
340+
let low = bx.const_uint_big(backend_ty, start);
341+
let cmp = bx.icmp(IntPredicate::IntUGE, imm, low);
342+
bx.assume(cmp);
343+
}
344+
345+
let type_max = scalar.size(self.cx).unsigned_int_max();
346+
if end < type_max {
347+
let high = bx.const_uint_big(backend_ty, end);
348+
let cmp = bx.icmp(IntPredicate::IntULE, imm, high);
349+
bx.assume(cmp);
350+
}
351+
} else {
352+
let low = bx.const_uint_big(backend_ty, start);
353+
let cmp_low = bx.icmp(IntPredicate::IntUGE, imm, low);
354+
355+
let high = bx.const_uint_big(backend_ty, end);
356+
let cmp_high = bx.icmp(IntPredicate::IntULE, imm, high);
357+
358+
let or = bx.or(cmp_low, cmp_high);
359+
bx.assume(or);
360+
}
361+
}
362+
301363
pub fn codegen_rvalue_unsized(
302364
&mut self,
303365
bx: &mut Bx,
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+
}

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

0 commit comments

Comments
 (0)