Skip to content

Commit 30a2b14

Browse files
Distinguish repr(C) ZSTs from others in ABI computation
Co-Authored-By: Ralf Jung <post@ralfj.de>
1 parent b998449 commit 30a2b14

40 files changed

Lines changed: 351 additions & 79 deletions

compiler/rustc_abi/src/layout.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
197197
size,
198198
max_repr_align: None,
199199
unadjusted_abi_align: element.align.abi,
200+
repr_c: false,
200201
randomization_seed: element.randomization_seed.wrapping_add(Hash64::new(count)),
201202
})
202203
}
@@ -498,6 +499,10 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
498499
return Err(LayoutCalculatorError::EmptyUnion);
499500
};
500501

502+
// We also need to check for a `repr(transparent)` wrapper around `repr(C)`
503+
let repr_c =
504+
repr.c() || (repr.transparent() && only_variant.iter().any(|field| field.repr_c));
505+
501506
let combined_seed = only_variant
502507
.iter()
503508
.map(|v| v.randomization_seed)
@@ -513,6 +518,7 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
513518
size: size.align_to(align),
514519
max_repr_align,
515520
unadjusted_abi_align,
521+
repr_c,
516522
randomization_seed: combined_seed,
517523
})
518524
}
@@ -736,6 +742,7 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
736742
align: AbiAlign::new(align),
737743
max_repr_align,
738744
unadjusted_abi_align,
745+
repr_c: repr.c(),
739746
randomization_seed: combined_seed,
740747
};
741748

@@ -1067,6 +1074,7 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
10671074
size,
10681075
max_repr_align,
10691076
unadjusted_abi_align,
1077+
repr_c: repr.c(),
10701078
randomization_seed: combined_seed,
10711079
};
10721080

@@ -1416,6 +1424,9 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
14161424
unadjusted_abi_align
14171425
};
14181426

1427+
// We also need to check for a `repr(transparent)` wrapper around `repr(C)`
1428+
let repr_c = repr.c() || (repr.transparent() && fields.iter().any(|field| field.repr_c));
1429+
14191430
let seed = field_seed.wrapping_add(repr.field_shuffle_seed);
14201431

14211432
Ok(LayoutData {
@@ -1428,6 +1439,7 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
14281439
size,
14291440
max_repr_align,
14301441
unadjusted_abi_align,
1442+
repr_c,
14311443
randomization_seed: seed,
14321444
})
14331445
}
@@ -1530,6 +1542,7 @@ where
15301542
align: AbiAlign::new(align),
15311543
max_repr_align: None,
15321544
unadjusted_abi_align: elt.align.abi,
1545+
repr_c: false,
15331546
randomization_seed: elt.randomization_seed.wrapping_add(Hash64::new(count)),
15341547
})
15351548
}

compiler/rustc_abi/src/layout/coroutine.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ pub(super) fn layout<
310310
align,
311311
max_repr_align: None,
312312
unadjusted_abi_align: align.abi,
313+
repr_c: false,
313314
randomization_seed: Default::default(),
314315
})
315316
}

compiler/rustc_abi/src/layout/simple.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ impl<FieldIdx: Idx, VariantIdx: Idx> LayoutData<FieldIdx, VariantIdx> {
2525
size: Size::ZERO,
2626
max_repr_align: None,
2727
unadjusted_abi_align: dl.i8_align,
28+
repr_c: false,
2829
randomization_seed: Hash64::new(0),
2930
}
3031
}
@@ -42,6 +43,7 @@ impl<FieldIdx: Idx, VariantIdx: Idx> LayoutData<FieldIdx, VariantIdx> {
4243
size: Size::ZERO,
4344
max_repr_align: None,
4445
unadjusted_abi_align: dl.i8_align,
46+
repr_c: false,
4547
randomization_seed: Hash64::ZERO,
4648
}
4749
}
@@ -84,6 +86,7 @@ impl<FieldIdx: Idx, VariantIdx: Idx> LayoutData<FieldIdx, VariantIdx> {
8486
align,
8587
max_repr_align: None,
8688
unadjusted_abi_align: align.abi,
89+
repr_c: false,
8790
randomization_seed: Hash64::new(randomization_seed),
8891
}
8992
}
@@ -117,6 +120,7 @@ impl<FieldIdx: Idx, VariantIdx: Idx> LayoutData<FieldIdx, VariantIdx> {
117120
size,
118121
max_repr_align: None,
119122
unadjusted_abi_align: align,
123+
repr_c: false,
120124
randomization_seed: Hash64::new(combined_seed),
121125
}
122126
}
@@ -143,6 +147,7 @@ impl<FieldIdx: Idx, VariantIdx: Idx> LayoutData<FieldIdx, VariantIdx> {
143147
size: Size::ZERO,
144148
max_repr_align: None,
145149
unadjusted_abi_align: dl.i8_align,
150+
repr_c: false,
146151
// Variant layouts never flow back into actual layout computations,
147152
// so dummy values are fine here.
148153
randomization_seed: Hash64::ZERO,
@@ -169,6 +174,7 @@ impl<FieldIdx: Idx, VariantIdx: Idx> LayoutData<FieldIdx, VariantIdx> {
169174
align: parent.align,
170175
max_repr_align: parent.max_repr_align,
171176
unadjusted_abi_align: parent.unadjusted_abi_align,
177+
repr_c: parent.repr_c,
172178
// Variant layouts never flow back into actual layout computations,
173179
// so dummy values are fine here.
174180
randomization_seed: Hash64::ZERO,

compiler/rustc_abi/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,6 +2143,11 @@ pub struct LayoutData<FieldIdx: Idx, VariantIdx: Idx> {
21432143
/// in some cases.
21442144
pub unadjusted_abi_align: Align,
21452145

2146+
/// Whether this type is `repr(C)`, or a`repr(transparent)` wrapper around such.
2147+
/// Some C ABIs pass `repr(C)` ZSTs by pointer, but `repr(Rust)` ZSTs should always
2148+
/// be ignored.
2149+
pub repr_c: bool,
2150+
21462151
/// The randomization seed based on this type's own repr and its fields.
21472152
///
21482153
/// Since randomization is toggled on a per-crate basis even crates that do not have randomization
@@ -2171,6 +2176,12 @@ impl<FieldIdx: Idx, VariantIdx: Idx> LayoutData<FieldIdx, VariantIdx> {
21712176
pub fn is_uninhabited(&self) -> bool {
21722177
self.uninhabited
21732178
}
2179+
2180+
/// Returns `true` if this is a `repr(C)` type,
2181+
/// or an array of such, or a `repr(transparent)` wrapper around such
2182+
pub fn is_repr_c(&self) -> bool {
2183+
self.repr_c
2184+
}
21742185
}
21752186

21762187
impl<FieldIdx: Idx, VariantIdx: Idx> fmt::Debug for LayoutData<FieldIdx, VariantIdx>
@@ -2192,6 +2203,7 @@ where
21922203
variants,
21932204
max_repr_align,
21942205
unadjusted_abi_align,
2206+
repr_c,
21952207
randomization_seed,
21962208
} = self;
21972209
f.debug_struct("Layout")
@@ -2204,6 +2216,7 @@ where
22042216
.field("variants", variants)
22052217
.field("max_repr_align", max_repr_align)
22062218
.field("unadjusted_abi_align", unadjusted_abi_align)
2219+
.field("repr_c", repr_c)
22072220
.field("randomization_seed", randomization_seed)
22082221
.finish()
22092222
}

compiler/rustc_target/src/callconv/powerpc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ where
1919
// powerpc-unknown-linux-{gnu,musl,uclibc} doesn't ignore ZSTs.
2020
if cx.target_spec().os == Os::Linux
2121
&& matches!(cx.target_spec().env, Env::Gnu | Env::Musl | Env::Uclibc)
22-
&& arg.layout.is_zst()
22+
&& arg.layout.is_repr_c()
2323
{
2424
arg.make_indirect_from_ignore();
2525
}

compiler/rustc_target/src/callconv/s390x.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ where
3131
// s390x-unknown-linux-{gnu,musl,uclibc} doesn't ignore ZSTs.
3232
if cx.target_spec().os == Os::Linux
3333
&& matches!(cx.target_spec().env, Env::Gnu | Env::Musl | Env::Uclibc)
34-
&& arg.layout.is_zst()
34+
&& arg.layout.is_repr_c()
3535
{
3636
arg.make_indirect_from_ignore();
3737
}

compiler/rustc_target/src/callconv/sparc64.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ where
212212
continue;
213213
}
214214
if arg.is_ignore() {
215-
if passes_zsts && arg.layout.is_zst() {
215+
if passes_zsts && arg.layout.is_repr_c() {
216216
arg.make_indirect_from_ignore();
217217
double_word_count += 1;
218218
}

compiler/rustc_target/src/callconv/x86_win64.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,25 @@ where
4848
}
4949
};
5050

51-
if !fn_abi.ret.is_ignore() {
51+
// Windows ABIs do not talk about ZST since such types do not exist in MSVC.
52+
// However, clang and gcc allow ZST in their windows-gnu targets, and pass them by pointer indirection.
53+
// We follow that for `repr(C)` ZSTs (and `repr(transparent)` wrappers around them),
54+
// but `repr(Rust)` ones are always ignored (ensuring that `()` matches C `void`).
55+
56+
if fn_abi.ret.is_ignore() {
57+
if fn_abi.ret.layout.is_repr_c() {
58+
fn_abi.ret.make_indirect_from_ignore();
59+
}
60+
} else {
5261
fixup(&mut fn_abi.ret, true);
5362
}
5463

5564
for arg in fn_abi.args.iter_mut() {
56-
if arg.is_ignore() && arg.layout.is_zst() {
57-
// Windows ABIs do not talk about ZST since such types do not exist in MSVC.
58-
// In that sense we can do whatever we want here, and maybe we should throw an error
59-
// (but of course that would be a massive breaking change now).
60-
// We try to match clang and gcc (which allow ZST is their windows-gnu targets), so we
61-
// pass ZST via pointer indirection.
62-
arg.make_indirect_from_ignore();
65+
if arg.is_ignore() {
66+
if arg.layout.is_repr_c() {
67+
arg.make_indirect_from_ignore();
68+
}
69+
6370
continue;
6471
}
6572
if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
@@ -68,7 +75,4 @@ where
6875
}
6976
fixup(arg, false);
7077
}
71-
// FIXME: We should likely also do something about ZST return types, similar to above.
72-
// However, that's non-trivial due to `()`.
73-
// See <https://github.com/rust-lang/unsafe-code-guidelines/issues/552>.
7478
}

library/core/src/primitive_docs.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,9 +1841,14 @@ mod prim_ref {}
18411841
/// call will be valid ABI-wise. The callee receives the result of transmuting the function pointer
18421842
/// from `fn()` to `fn(i32)`; that transmutation is itself a well-defined operation, it's just
18431843
/// almost certainly UB to later call that function pointer.)
1844-
/// - Any two types with size 0 and alignment 1 are ABI-compatible.
1845-
/// - A `repr(transparent)` type `T` is ABI-compatible with its unique non-trivial field, i.e., the
1846-
/// unique field that doesn't have size 0 and alignment 1 (if there is such a field).
1844+
/// - Any two types fulfilling all the following conditions are ABI-compatible;
1845+
/// such types are said to have "trivial ABI":
1846+
/// - Size 0
1847+
/// - Alignment 1
1848+
/// - Not `repr(C)`
1849+
/// - Not a `repr(transparent)` wrapper around a type that fails to satisfy these conditions
1850+
/// - A `repr(transparent)` type is ABI-compatible with its unique field that does not have trivial ABI
1851+
/// (as defined above). If there is no such field, the type has trivial ABI.
18471852
/// - `i32` is ABI-compatible with `NonZero<i32>`, and similar for all other integer types.
18481853
/// - If `T` is guaranteed to be subject to the [null pointer
18491854
/// optimization](option/index.html#representation), and `E` is an enum satisfying the following

tests/codegen-llvm/abi-win64-zst.rs

Lines changed: 110 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,129 @@
2121
extern crate minicore;
2222
use minicore::*;
2323

24-
// Make sure the argument is always passed when explicitly requesting a Windows ABI.
24+
#[repr(C)]
25+
struct CMaybeZst;
26+
27+
#[repr(transparent)]
28+
struct CMaybeZst2((), CMaybeZst, ());
29+
30+
// Make sure the argument is always passed when explicitly requesting a Windows ABI,
31+
// and it is `repr(C)` - but not if it is `repr(Rust)`.
2532
// Our goal here is to match clang: <https://clang.godbolt.org/z/Wr4jMWq3P>.
2633

27-
// CHECK: define win64cc void @pass_zst_win64(ptr {{[^,]*}})
34+
// CHECK: define win64cc void @pass_rust_zst_win64()
35+
#[no_mangle]
36+
extern "win64" fn pass_rust_zst_win64(_: ()) {}
37+
38+
// CHECK: define win64cc void @pass_c_maybezst_win64(ptr {{[^,]*}})
39+
#[no_mangle]
40+
extern "win64" fn pass_c_maybezst_win64(_: CMaybeZst) {}
41+
42+
// CHECK: define win64cc void @pass_c_maybezst_2_win64(ptr {{[^,]*}})
43+
#[no_mangle]
44+
extern "win64" fn pass_c_maybezst_2_win64(_: CMaybeZst2) {}
45+
46+
// CHECK: define x86_vectorcallcc void @pass_rust_zst_vectorcall()
47+
#[no_mangle]
48+
extern "vectorcall" fn pass_rust_zst_vectorcall(_: ()) {}
49+
50+
// CHECK: define x86_vectorcallcc void @pass_c_maybezst_vectorcall(ptr {{[^,]*}})
2851
#[no_mangle]
29-
extern "win64" fn pass_zst_win64(_: ()) {}
52+
extern "vectorcall" fn pass_c_maybezst_vectorcall(_: CMaybeZst) {}
3053

31-
// CHECK: define x86_vectorcallcc void @pass_zst_vectorcall(ptr {{[^,]*}})
54+
// CHECK: define x86_vectorcallcc void @pass_c_maybezst_2_vectorcall(ptr {{[^,]*}})
3255
#[no_mangle]
33-
extern "vectorcall" fn pass_zst_vectorcall(_: ()) {}
56+
extern "vectorcall" fn pass_c_maybezst_2_vectorcall(_: CMaybeZst2) {}
3457

35-
// windows-gnu: define void @pass_zst_fastcall(ptr {{[^,]*}})
36-
// windows-msvc: define void @pass_zst_fastcall(ptr {{[^,]*}})
58+
// windows-gnu: define void @pass_rust_zst_fastcall()
59+
// windows-msvc: define void @pass_rust_zst_fastcall()
3760
#[no_mangle]
3861
#[cfg(windows)] // "fastcall" is not valid on 64bit Linux
39-
extern "fastcall" fn pass_zst_fastcall(_: ()) {}
62+
extern "fastcall" fn pass_rust_zst_fastcall(_: ()) {}
63+
64+
// windows-gnu: define void @pass_c_maybezst_fastcall(ptr {{[^,]*}})
65+
// windows-msvc: define void @pass_c_maybezst_fastcall(ptr {{[^,]*}})
66+
#[no_mangle]
67+
#[cfg(windows)] // "fastcall" is not valid on 64bit Linux
68+
extern "fastcall" fn pass_c_maybezst_fastcall(_: CMaybeZst) {}
69+
70+
// windows-gnu: define void @pass_c_maybezst_2_fastcall(ptr {{[^,]*}})
71+
// windows-msvc: define void @pass_c_maybezst_2_fastcall(ptr {{[^,]*}})
72+
#[no_mangle]
73+
#[cfg(windows)] // "fastcall" is not valid on 64bit Linux
74+
extern "fastcall" fn pass_c_maybezst_2_fastcall(_: CMaybeZst2) {}
4075

4176
// The sysv64 ABI ignores ZST.
4277

43-
// CHECK: define x86_64_sysvcc void @pass_zst_sysv64()
78+
// CHECK: define x86_64_sysvcc void @pass_rust_zst_sysv64()
79+
#[no_mangle]
80+
extern "sysv64" fn pass_rust_zst_sysv64(_: ()) {}
81+
82+
// CHECK: define x86_64_sysvcc void @pass_c_maybezst_sysv64()
83+
#[no_mangle]
84+
extern "sysv64" fn pass_c_maybezst_sysv64(_: CMaybeZst) {}
85+
86+
// CHECK: define x86_64_sysvcc void @pass_c_maybezst_2_sysv64()
4487
#[no_mangle]
45-
extern "sysv64" fn pass_zst_sysv64(_: ()) {}
88+
extern "sysv64" fn pass_c_maybezst_2_sysv64(_: CMaybeZst2) {}
4689

4790
// For `extern "C"` functions, ZST are ignored on Linux put passed on Windows.
4891

49-
// linux: define void @pass_zst_c()
50-
// windows-msvc: define void @pass_zst_c(ptr {{[^,]*}})
51-
// windows-gnu: define void @pass_zst_c(ptr {{[^,]*}})
92+
// linux: define void @pass_rust_zst_c()
93+
// windows-msvc: define void @pass_rust_zst_c()
94+
// windows-gnu: define void @pass_rust_zst_c()
95+
#[no_mangle]
96+
extern "C" fn pass_rust_zst_c(_: ()) {}
97+
98+
// linux: define void @pass_c_maybezst_c()
99+
// windows-msvc: define void @pass_c_maybezst_c(ptr {{[^,]*}})
100+
// windows-gnu: define void @pass_c_maybezst_c(ptr {{[^,]*}})
101+
#[no_mangle]
102+
extern "C" fn pass_c_maybezst_c(_: CMaybeZst) {}
103+
104+
// linux: define void @pass_c_maybezst_2_c()
105+
// windows-msvc: define void @pass_c_maybezst_2_c(ptr {{[^,]*}})
106+
// windows-gnu: define void @pass_c_maybezst_2_c(ptr {{[^,]*}})
107+
#[no_mangle]
108+
extern "C" fn pass_c_maybezst_2_c(_: CMaybeZst2) {}
109+
110+
// Now check `repr(C)` return types.
111+
// Again, we seek to match clang: <https://clang.godbolt.org/z/hKv74ThnE>
112+
113+
// CHECK: define win64cc void @ret_c_maybezst_win64(ptr {{[^,]*}})
114+
#[no_mangle]
115+
extern "win64" fn ret_c_maybezst_win64() -> CMaybeZst {
116+
CMaybeZst
117+
}
118+
119+
// CHECK: define x86_vectorcallcc void @ret_c_maybezst_vectorcall(ptr {{[^,]*}})
120+
#[no_mangle]
121+
extern "vectorcall" fn ret_c_maybezst_vectorcall() -> CMaybeZst {
122+
CMaybeZst
123+
}
124+
125+
// windows-gnu: define void @ret_c_maybezst_fastcall(ptr {{[^,]*}})
126+
// windows-msvc: define void @ret_c_maybezst_fastcall(ptr {{[^,]*}})
127+
#[no_mangle]
128+
#[cfg(windows)] // "fastcall" is not valid on 64bit Linux
129+
extern "fastcall" fn ret_c_maybezst_fastcall() -> CMaybeZst {
130+
CMaybeZst
131+
}
132+
133+
// The sysv64 ABI ignores ZST.
134+
135+
// CHECK: define x86_64_sysvcc void @ret_c_maybezst_sysv64()
136+
#[no_mangle]
137+
extern "sysv64" fn ret_c_maybezst_sysv64() -> CMaybeZst {
138+
CMaybeZst
139+
}
140+
141+
// For `extern "C"` functions, ZST are ignored on Linux but returned via pointer on Windows.
142+
143+
// linux: define void @ret_c_maybezst_c()
144+
// windows-msvc: define void @ret_c_maybezst_c(ptr {{[^,]*}})
145+
// windows-gnu: define void @ret_c_maybezst_c(ptr {{[^,]*}})
52146
#[no_mangle]
53-
extern "C" fn pass_zst_c(_: ()) {}
147+
extern "C" fn ret_c_maybezst_c() -> CMaybeZst {
148+
CMaybeZst
149+
}

0 commit comments

Comments
 (0)