Skip to content

Commit caa50bd

Browse files
committed
Auto merge of rust-lang#127210 - jieyouxu:array-imm-noundef-param, r=<try>
Annotate eligible small immediate arguments with `noundef` Retake of rust-lang#123425. We try to annotate small (fits within target pointer width) aggregate arguments passed as immediates (specifically casted as "appropriately sized integer type") with `noundef`. Example: ```rs #[no_mangle] pub fn short_array_u64x1(v: [u64; 1]) -> [u64; 1] { v } ``` currently produces ```llvm define i64 `@short_array_u64x1(i64` %0) ... ``` This PR changes that to ```llvm define noundef i64 `@short_array_u64x1(i64` noundef %0) ... ``` The `noundef` attribute is added only if the immediate value has no padding. Specifically, the conservative heuristic we use is to: - Peel away layers of `#[repr(Rust)]` or `#[repr(transparent)]` wrappers if present - Check for innermost simple arrays (whose element type is a primitive type) that can fit within target pointer width Union immediates or otherwise anything that contains unions will not have `noundef` attribute applied. Closes rust-lang#123183. cc `@/RalfJung` who pointed out various problems with the previous take, hopefully I addressed most of them in this take. r? `@ghost` (perf)
2 parents c3774be + 80171db commit caa50bd

File tree

3 files changed

+299
-2
lines changed

3 files changed

+299
-2
lines changed

compiler/rustc_abi/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ pub struct ReprOptions {
101101
}
102102

103103
impl ReprOptions {
104+
pub fn rust(&self) -> bool {
105+
!(self.simd() || self.c() || self.packed() || self.transparent() || self.linear())
106+
}
107+
104108
#[inline]
105109
pub fn simd(&self) -> bool {
106110
self.flags.contains(ReprFlags::IS_SIMD)

compiler/rustc_ty_utils/src/abi.rs

+53-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use rustc_middle::ty::{self, InstanceKind, Ty, TyCtxt};
99
use rustc_session::config::OptLevel;
1010
use rustc_span::def_id::DefId;
1111
use rustc_target::abi::call::{
12-
ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, Conv, FnAbi, PassMode, Reg, RegKind,
13-
RiscvInterruptKind,
12+
ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, CastTarget, Conv, FnAbi, PassMode, Reg,
13+
RegKind, RiscvInterruptKind,
1414
};
1515
use rustc_target::abi::*;
1616
use rustc_target::spec::abi::Abi as SpecAbi;
@@ -787,6 +787,19 @@ fn fn_abi_adjust_for_abi<'tcx>(
787787
// an LLVM aggregate type for this leads to bad optimizations,
788788
// so we pick an appropriately sized integer type instead.
789789
arg.cast_to(Reg { kind: RegKind::Integer, size });
790+
791+
// Now we see if we are allowed to annotate the small immediate argument with
792+
// `noundef`. This is only legal for small aggregates that do not have padding. For
793+
// instance, all unions are allowed `undef` so we must not annotate union (or
794+
// anything that contain unions) immediates with `noundef`.
795+
if can_annotate_small_immediate_argument_with_noundef(cx, arg.layout) {
796+
// Fixup arg attribute with `noundef`.
797+
let PassMode::Cast { ref mut cast, .. } = &mut arg.mode else {
798+
bug!("this cannot fail because of the previous cast_to `Reg`");
799+
};
800+
let box CastTarget { ref mut attrs, .. } = cast;
801+
attrs.set(ArgAttribute::NoUndef);
802+
}
790803
}
791804

792805
// If we deduced that this parameter was read-only, add that to the attribute list now.
@@ -822,6 +835,44 @@ fn fn_abi_adjust_for_abi<'tcx>(
822835
Ok(())
823836
}
824837

838+
fn can_annotate_small_immediate_argument_with_noundef<'tcx>(
839+
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
840+
outermost_layout: TyAndLayout<'tcx>,
841+
) -> bool {
842+
fn allowed_repr_rust<'tcx>(candidate_ty: Ty<'tcx>) -> bool {
843+
match candidate_ty.kind() {
844+
ty::Adt(def, _) if def.repr().rust() && !def.is_union() => true,
845+
ty::Array(elem_ty, _) if allowed_repr_rust(*elem_ty) => true,
846+
t => t.is_primitive(),
847+
}
848+
}
849+
850+
fn is_transparent_or_rust_wrapper<'tcx>(layout: TyAndLayout<'tcx>) -> bool {
851+
return layout.is_transparent::<LayoutCx<'tcx, TyCtxt<'tcx>>>()
852+
|| allowed_repr_rust(layout.ty);
853+
}
854+
855+
if outermost_layout.ty.is_union() {
856+
return false;
857+
}
858+
859+
let mut innermost_layout = outermost_layout;
860+
// Recursively peel away wrapper layers.
861+
while is_transparent_or_rust_wrapper(innermost_layout) {
862+
let Some((_, layout)) = innermost_layout.non_1zst_field(cx) else {
863+
break;
864+
};
865+
866+
if layout.ty.is_union() {
867+
return false;
868+
}
869+
870+
innermost_layout = layout;
871+
}
872+
873+
allowed_repr_rust(innermost_layout.ty)
874+
}
875+
825876
#[tracing::instrument(level = "debug", skip(cx))]
826877
fn make_thin_self_ptr<'tcx>(
827878
cx: &(impl HasTyCtxt<'tcx> + HasParamEnv<'tcx>),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
//! We would like to try add `noundef` to small immediate arguments (the heuristic here is if the
2+
//! immediate argument fits within target pointer width) where possible and legal. Adding `noundef`
3+
//! attribute is legal iff the immediate argument do not have padding (indeterminate value
4+
//! otherwise). Only simple immediates are considered currently (trivially no padding), but could be
5+
//! potentially expanded to other small aggregate immediates that do not have padding (subject to
6+
//! being able to correctly calculate "no padding").
7+
//!
8+
//! - We should recursively see through `#[repr(transparent)]` and `#[repr(Rust)]` layouts.
9+
//! - Unions cannot have `noundef` because all unions are currently allowed to be `undef`. This
10+
//! property is "infectious", anything that contains unions also may not have `noundef` applied.
11+
12+
// ignore-tidy-linelength
13+
14+
#![crate_type = "lib"]
15+
16+
//@ compile-flags: -C no-prepopulate-passes
17+
18+
// We setup two revisions to check that `noundef` is only added when optimization is enabled.
19+
//@ revisions: NoOpt Opt
20+
//@ [NoOpt] compile-flags: -C opt-level=0
21+
//@ [Opt] compile-flags: -O
22+
23+
// Presence of `noundef` depends on target pointer width (it's only applied when the immediate fits
24+
// within target pointer width).
25+
//@ only-64bit
26+
27+
// -------------------------------------------------------------------------------------------------
28+
29+
// # Positive test cases
30+
//
31+
// - Simple arrays of primitive types whose size fits within target pointer width (referred to as
32+
// "simple arrays" for the following positive test cases).
33+
// - `#[repr(transparent)]` ADTs which eventually contain simple arrays.
34+
// - `#[repr(Rust)]` ADTs which eventually contain simple arrays. This relies on rustc layout
35+
// behavior, and is not guaranteed by `#[repr(Rust)]`.
36+
37+
// ## Simple arrays
38+
39+
// NoOpt: define i64 @short_array_u64x1(i64 %{{.*}})
40+
// Opt: define noundef i64 @short_array_u64x1(i64 noundef %{{.*}})
41+
#[no_mangle]
42+
pub fn short_array_u64x1(v: [u64; 1]) -> [u64; 1] {
43+
v
44+
}
45+
46+
// NoOpt: define i32 @short_array_u32x1(i32 %{{.*}})
47+
// Opt: define noundef i32 @short_array_u32x1(i32 noundef %{{.*}})
48+
#[no_mangle]
49+
pub fn short_array_u32x1(v: [u32; 1]) -> [u32; 1] {
50+
v
51+
}
52+
53+
// NoOpt: define i64 @short_array_u32x2(i64 %{{.*}})
54+
// Opt: define noundef i64 @short_array_u32x2(i64 noundef %{{.*}})
55+
#[no_mangle]
56+
pub fn short_array_u32x2(v: [u32; 2]) -> [u32; 2] {
57+
v
58+
}
59+
60+
// NoOpt: define i16 @short_array_u16x1(i16 %{{.*}})
61+
// Opt: define noundef i16 @short_array_u16x1(i16 noundef %{{.*}})
62+
#[no_mangle]
63+
pub fn short_array_u16x1(v: [u16; 1]) -> [u16; 1] {
64+
v
65+
}
66+
67+
// NoOpt: define i32 @short_array_u16x2(i32 %{{.*}})
68+
// Opt: define noundef i32 @short_array_u16x2(i32 noundef %{{.*}})
69+
#[no_mangle]
70+
pub fn short_array_u16x2(v: [u16; 2]) -> [u16; 2] {
71+
v
72+
}
73+
74+
// NoOpt: define i48 @short_array_u16x3(i48 %{{.*}})
75+
// Opt: define noundef i48 @short_array_u16x3(i48 noundef %{{.*}})
76+
#[no_mangle]
77+
pub fn short_array_u16x3(v: [u16; 3]) -> [u16; 3] {
78+
v
79+
}
80+
81+
// NoOpt: define i64 @short_array_u16x4(i64 %{{.*}})
82+
// Opt: define noundef i64 @short_array_u16x4(i64 noundef %{{.*}})
83+
#[no_mangle]
84+
pub fn short_array_u16x4(v: [u16; 4]) -> [u16; 4] {
85+
v
86+
}
87+
88+
// NoOpt: define i8 @short_array_u8x1(i8 %{{.*}})
89+
// Opt: define noundef i8 @short_array_u8x1(i8 noundef %{{.*}})
90+
#[no_mangle]
91+
pub fn short_array_u8x1(v: [u8; 1]) -> [u8; 1] {
92+
v
93+
}
94+
95+
// NoOpt: define i16 @short_array_u8x2(i16 %{{.*}})
96+
// Opt: define noundef i16 @short_array_u8x2(i16 noundef %{{.*}})
97+
#[no_mangle]
98+
pub fn short_array_u8x2(v: [u8; 2]) -> [u8; 2] {
99+
v
100+
}
101+
102+
// NoOpt: define i24 @short_array_u8x3(i24 %{{.*}})
103+
// Opt: define noundef i24 @short_array_u8x3(i24 noundef %{{.*}})
104+
#[no_mangle]
105+
pub fn short_array_u8x3(v: [u8; 3]) -> [u8; 3] {
106+
v
107+
}
108+
109+
// NoOpt: define i64 @short_array_u8x8(i64 %{{.*}})
110+
// Opt: define noundef i64 @short_array_u8x8(i64 noundef %{{.*}})
111+
#[no_mangle]
112+
pub fn short_array_u8x8(v: [u8; 8]) -> [u8; 8] {
113+
v
114+
}
115+
116+
// ## Small `#[repr(transparent)]` wrappers
117+
118+
#[repr(transparent)]
119+
pub struct TransparentWrapper([u8; 4]);
120+
121+
// NoOpt: define i32 @repr_transparent_wrapper(i32 %{{.*}})
122+
// Opt: define noundef i32 @repr_transparent_wrapper(i32 noundef %{{.*}})
123+
#[no_mangle]
124+
pub fn repr_transparent_wrapper(v: TransparentWrapper) -> TransparentWrapper {
125+
v
126+
}
127+
128+
#[repr(transparent)]
129+
pub struct RecursiveTransparentWrapper(TransparentWrapper);
130+
131+
// NoOpt: define i32 @recursive_repr_transparent_wrapper(i32 %{{.*}})
132+
// Opt: define noundef i32 @recursive_repr_transparent_wrapper(i32 noundef %{{.*}})
133+
#[no_mangle]
134+
pub fn recursive_repr_transparent_wrapper(
135+
v: RecursiveTransparentWrapper,
136+
) -> RecursiveTransparentWrapper {
137+
v
138+
}
139+
140+
// ## Small `#[repr(Rust)]` wrappers
141+
//
142+
// Note that this relies on rustc self-consistency in handling simple `#[repr(Rust)]` wrappers, i.e.
143+
// that `struct Foo([u8; 4])` has the same layout as its sole inner member and that no additional
144+
// padding is introduced.
145+
146+
pub struct ReprRustWrapper([u8; 4]);
147+
148+
// NoOpt: define i32 @repr_rust_wrapper(i32 %{{.*}})
149+
// Opt: define noundef i32 @repr_rust_wrapper(i32 noundef %{{.*}})
150+
#[no_mangle]
151+
pub fn repr_rust_wrapper(v: ReprRustWrapper) -> ReprRustWrapper {
152+
v
153+
}
154+
155+
// ## Cases not handled
156+
//
157+
// - Aggregates that have no padding and fits within target pointer width which are not simple
158+
// arrays. Potentially aggregates such as tuples `(u32, u32)`. This is left as an exercise to the
159+
// reader (follow-up welcomed) :)
160+
161+
// No `noundef` annotation on return `{i32, i32}`, but argument does (when optimizations enabled).
162+
// NoOpt: define { i32, i32 } @unhandled_small_pair_ret(i32 %v.0, i32 %v.1)
163+
// Opt: define { i32, i32 } @unhandled_small_pair_ret(i32 noundef %v.0, i32 noundef %v.1)
164+
#[no_mangle]
165+
pub fn unhandled_small_pair_ret(v: (u32, u32)) -> (u32, u32) {
166+
v
167+
}
168+
169+
// -------------------------------------------------------------------------------------------------
170+
171+
// # Negative test cases ()
172+
//
173+
// - Other representations (not `transparent` or `Rust`)
174+
// - Unions cannot have `noundef` because they are allowed to be `undef`.
175+
// - Array of unions still contains unions, so they cannot have `noundef`
176+
// - Transparent unions are still unions, so they cannot have `noundef`
177+
178+
// ## Other representations
179+
180+
#[repr(C)]
181+
pub struct ReprCWrapper([u8; 4]);
182+
183+
// NoOpt: define i32 @repr_c_immediate(i32 %0)
184+
// Opt: define i32 @repr_c_immediate(i32 %0)
185+
#[no_mangle]
186+
pub fn repr_c_immediate(v: ReprCWrapper) -> ReprCWrapper {
187+
v
188+
}
189+
190+
// ## Unions
191+
192+
union U {
193+
u1: u64,
194+
u2: [u8; 4],
195+
}
196+
197+
// All unions can be `undef`, must not have `noundef` as immediate argument.
198+
// NoOpt: define i64 @union_immediate(i64 %0)
199+
// Opt: define i64 @union_immediate(i64 %0)
200+
#[no_mangle]
201+
pub fn union_immediate(v: U) -> U {
202+
v
203+
}
204+
205+
// ## Array of unions
206+
//
207+
// Cannot have `noundef` because tainted by unions.
208+
209+
union SmallButDangerous {
210+
u1: [u8; 2],
211+
u2: u16,
212+
}
213+
214+
// NoOpt: define i16 @one_elem_array_of_unions(i16 %0)
215+
// Opt: define i16 @one_elem_array_of_unions(i16 %0)
216+
#[no_mangle]
217+
pub fn one_elem_array_of_unions(v: [SmallButDangerous; 1]) -> [SmallButDangerous; 1] {
218+
v
219+
}
220+
221+
// NoOpt: define i32 @two_elem_array_of_unions(i32 %0)
222+
// Opt: define i32 @two_elem_array_of_unions(i32 %0)
223+
#[no_mangle]
224+
pub fn two_elem_array_of_unions(v: [SmallButDangerous; 2]) -> [SmallButDangerous; 2] {
225+
v
226+
}
227+
228+
// # `#[repr(transparent)]` unions
229+
230+
union Inner {
231+
i1: u8,
232+
}
233+
234+
#[repr(transparent)]
235+
pub struct TransparentUnionWrapper(Inner);
236+
237+
// NoOpt: define i8 @repr_transparent_union(i8 %v)
238+
// Opt: define i8 @repr_transparent_union(i8 %v)
239+
#[no_mangle]
240+
pub fn repr_transparent_union(v: TransparentUnionWrapper) -> TransparentUnionWrapper {
241+
v
242+
}

0 commit comments

Comments
 (0)