Skip to content

Commit ac69dae

Browse files
veluca93taiki-e
authored andcommitted
Emit error when calling/declaring functions with unavailable vectors.
On some architectures, vector types may have a different ABI when relevant target features are enabled. As discussed in rust-lang/lang-team#235, this turns out to very easily lead to unsound code. This commit makes it an error to declare or call functions using those vector types in a context in which the corresponding target features are disabled, if using an ABI for which the difference is relevant.
1 parent f6648f2 commit ac69dae

16 files changed

+278
-20
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -4138,6 +4138,7 @@ dependencies = [
41384138
name = "rustc_monomorphize"
41394139
version = "0.0.0"
41404140
dependencies = [
4141+
"rustc_abi",
41414142
"rustc_data_structures",
41424143
"rustc_errors",
41434144
"rustc_fluent_macro",

compiler/rustc_monomorphize/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2021"
55

66
[dependencies]
77
# tidy-alphabetical-start
8+
rustc_abi = { path = "../rustc_abi" }
89
rustc_data_structures = { path = "../rustc_data_structures" }
910
rustc_errors = { path = "../rustc_errors" }
1011
rustc_fluent_macro = { path = "../rustc_fluent_macro" }

compiler/rustc_monomorphize/messages.ftl

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
monomorphize_abi_error_disabled_vector_type_call =
2+
ABI error: this function call uses a {$required_feature} vector type, which is not enabled in the caller
3+
.help = consider enabling it globally (-C target-feature=+{$required_feature}) or locally (#[target_feature(enable="{$required_feature}")])
4+
monomorphize_abi_error_disabled_vector_type_def =
5+
ABI error: this function definition uses a {$required_feature} vector type, which is not enabled
6+
.help = consider enabling it globally (-C target-feature=+{$required_feature}) or locally (#[target_feature(enable="{$required_feature}")])
7+
18
monomorphize_couldnt_dump_mono_stats =
29
unexpected error occurred while dumping monomorphization stats: {$error}
310

compiler/rustc_monomorphize/src/collector.rs

+3
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@
205205
//! this is not implemented however: a mono item will be produced
206206
//! regardless of whether it is actually needed or not.
207207
208+
mod abi_check;
208209
mod move_check;
209210

210211
use std::path::PathBuf;
@@ -766,6 +767,7 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
766767
self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty));
767768
let callee_ty = self.monomorphize(callee_ty);
768769
self.check_fn_args_move_size(callee_ty, args, *fn_span, location);
770+
abi_check::check_call_site_abi(tcx, callee_ty, *fn_span, self.body.source.instance);
769771
visit_fn_use(self.tcx, callee_ty, true, source, &mut self.used_items)
770772
}
771773
mir::TerminatorKind::Drop { ref place, .. } => {
@@ -1207,6 +1209,7 @@ fn collect_items_of_instance<'tcx>(
12071209
mentioned_items: &mut MonoItems<'tcx>,
12081210
mode: CollectionMode,
12091211
) {
1212+
abi_check::check_instance_abi(tcx, instance);
12101213
let body = tcx.instance_mir(instance.def);
12111214
// Naively, in "used" collection mode, all functions get added to *both* `used_items` and
12121215
// `mentioned_items`. Mentioned items processing will then notice that they have already been
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use rustc_abi::Abi;
2+
use rustc_middle::ty::{self, Instance, InstanceKind, ParamEnv, Ty, TyCtxt};
3+
use rustc_span::def_id::DefId;
4+
use rustc_span::{Span, Symbol};
5+
use rustc_target::abi::call::{FnAbi, PassMode};
6+
7+
use crate::errors::{AbiErrorDisabledVectorTypeCall, AbiErrorDisabledVectorTypeDef};
8+
9+
// Represents the least-constraining feature that is required for vector types up to a certain size
10+
// to have their "proper" ABI.
11+
const X86_VECTOR_FEATURES: &'static [(u64, &'static str)] =
12+
&[(128, "sse"), (256, "avx"), (512, "avx512f")];
13+
14+
fn do_check_abi<'tcx>(
15+
tcx: TyCtxt<'tcx>,
16+
abi: &FnAbi<'tcx, Ty<'tcx>>,
17+
target_feature_def: DefId,
18+
emit_err: impl Fn(&'static str),
19+
) {
20+
let feature_def = if tcx.sess.target.arch == "x86" || tcx.sess.target.arch == "x86_64" {
21+
X86_VECTOR_FEATURES
22+
} else if tcx.sess.target.arch == "aarch64" {
23+
// ABI on aarch64 does not depend on target features.
24+
return;
25+
} else {
26+
// FIXME: add support for non-tier1 architectures
27+
return;
28+
};
29+
let codegen_attrs = tcx.codegen_fn_attrs(target_feature_def);
30+
for arg_abi in abi.args.iter().chain(std::iter::once(&abi.ret)) {
31+
let size = arg_abi.layout.size;
32+
if matches!(arg_abi.layout.abi, Abi::Vector { .. })
33+
&& !matches!(arg_abi.mode, PassMode::Indirect { .. })
34+
{
35+
let feature = match feature_def.iter().find(|(bits, _)| size.bits() <= *bits) {
36+
Some((_, feature)) => feature,
37+
None => panic!("Unknown vector size: {}; arg = {:?}", size.bits(), arg_abi),
38+
};
39+
let feature_sym = Symbol::intern(feature);
40+
if !tcx.sess.unstable_target_features.contains(&feature_sym)
41+
&& !codegen_attrs.target_features.iter().any(|x| x.name == feature_sym)
42+
{
43+
emit_err(feature);
44+
}
45+
}
46+
}
47+
}
48+
49+
/// Checks that the ABI of a given instance of a function does not contain vector-passed arguments
50+
/// or return values for which the corresponding target feature is not enabled.
51+
pub fn check_instance_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
52+
let param_env = ParamEnv::reveal_all();
53+
let Ok(abi) = tcx.fn_abi_of_instance(param_env.and((instance, ty::List::empty()))) else {
54+
// An error will be reported during codegen if we cannot determine the ABI of this
55+
// function.
56+
return;
57+
};
58+
do_check_abi(tcx, abi, instance.def_id(), |required_feature| {
59+
tcx.dcx().emit_err(AbiErrorDisabledVectorTypeDef {
60+
span: tcx.def_span(instance.def_id()),
61+
required_feature,
62+
});
63+
})
64+
}
65+
66+
/// Checks that a call expression does not try to pass a vector-passed argument which requires a
67+
/// target feature that the caller does not have, as doing so causes UB because of ABI mismatch.
68+
pub fn check_call_site_abi<'tcx>(
69+
tcx: TyCtxt<'tcx>,
70+
ty: Ty<'tcx>,
71+
span: Span,
72+
caller: InstanceKind<'tcx>,
73+
) {
74+
let param_env = ParamEnv::reveal_all();
75+
let callee_abi = match *ty.kind() {
76+
ty::FnPtr(..) => tcx.fn_abi_of_fn_ptr(param_env.and((ty.fn_sig(tcx), ty::List::empty()))),
77+
ty::FnDef(def_id, args) => {
78+
// Intrinsics are handled separately by the compiler.
79+
if tcx.intrinsic(def_id).is_some() {
80+
return;
81+
}
82+
let instance = ty::Instance::expect_resolve(tcx, param_env, def_id, args, span);
83+
tcx.fn_abi_of_instance(param_env.and((instance, ty::List::empty())))
84+
}
85+
_ => {
86+
panic!("Invalid function call");
87+
}
88+
};
89+
90+
let Ok(callee_abi) = callee_abi else {
91+
// ABI failed to compute; this will not get through codegen.
92+
return;
93+
};
94+
do_check_abi(tcx, callee_abi, caller.def_id(), |required_feature| {
95+
tcx.dcx().emit_err(AbiErrorDisabledVectorTypeCall { span, required_feature });
96+
})
97+
}

compiler/rustc_monomorphize/src/errors.rs

+18
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,21 @@ pub(crate) struct StartNotFound;
9292
pub(crate) struct UnknownCguCollectionMode<'a> {
9393
pub mode: &'a str,
9494
}
95+
96+
#[derive(Diagnostic)]
97+
#[diag(monomorphize_abi_error_disabled_vector_type_def)]
98+
#[help]
99+
pub struct AbiErrorDisabledVectorTypeDef<'a> {
100+
#[primary_span]
101+
pub span: Span,
102+
pub required_feature: &'a str,
103+
}
104+
105+
#[derive(Diagnostic)]
106+
#[diag(monomorphize_abi_error_disabled_vector_type_call)]
107+
#[help]
108+
pub struct AbiErrorDisabledVectorTypeCall<'a> {
109+
#[primary_span]
110+
pub span: Span,
111+
pub required_feature: &'a str,
112+
}

library/core/src/primitive_docs.rs

+1-20
Original file line numberDiff line numberDiff line change
@@ -1752,8 +1752,7 @@ mod prim_ref {}
17521752
///
17531753
/// For two signatures to be considered *ABI-compatible*, they must use a compatible ABI string,
17541754
/// must take the same number of arguments, the individual argument types and the return types must
1755-
/// be ABI-compatible, and the target feature requirements must be met (see the subsection below for
1756-
/// the last point). The ABI string is declared via `extern "ABI" fn(...) -> ...`; note that
1755+
/// be ABI-compatible. The ABI string is declared via `extern "ABI" fn(...) -> ...`; note that
17571756
/// `fn name(...) -> ...` implicitly uses the `"Rust"` ABI string and `extern fn name(...) -> ...`
17581757
/// implicitly uses the `"C"` ABI string.
17591758
///
@@ -1821,24 +1820,6 @@ mod prim_ref {}
18211820
/// Behavior since transmuting `None::<NonZero<i32>>` to `NonZero<i32>` violates the non-zero
18221821
/// requirement.
18231822
///
1824-
/// #### Requirements concerning target features
1825-
///
1826-
/// Under some conditions, the signature used by the caller and the callee can be ABI-incompatible
1827-
/// even if the exact same ABI string and types are being used. As an example, the
1828-
/// `std::arch::x86_64::__m256` type has a different `extern "C"` ABI when the `avx` feature is
1829-
/// enabled vs when it is not enabled.
1830-
///
1831-
/// Therefore, to ensure ABI compatibility when code using different target features is combined
1832-
/// (such as via `#[target_feature]`), we further require that one of the following conditions is
1833-
/// met:
1834-
///
1835-
/// - The function uses the `"Rust"` ABI string (which is the default without `extern`).
1836-
/// - Caller and callee are using the exact same set of target features. For the callee we consider
1837-
/// the features enabled (via `#[target_feature]` and `-C target-feature`/`-C target-cpu`) at the
1838-
/// declaration site; for the caller we consider the features enabled at the call site.
1839-
/// - Neither any argument nor the return value involves a SIMD type (`#[repr(simd)]`) that is not
1840-
/// behind a pointer indirection (i.e., `*mut __m256` is fine, but `(i32, __m256)` is not).
1841-
///
18421823
/// ### Trait implementations
18431824
///
18441825
/// In this documentation the shorthand `fn(T₁, T₂, …, Tₙ)` is used to represent non-variadic

tests/assembly/simd-bitmask.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//@ ignore-test
12
//@ revisions: x86 x86-avx2 x86-avx512 aarch64
23
//@ [x86] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel
34
//@ [x86] needs-llvm-components: x86

tests/assembly/simd-intrinsic-gather.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//@ ignore-test
12
//@ revisions: x86-avx512
23
//@ [x86-avx512] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel
34
//@ [x86-avx512] compile-flags: -C target-feature=+avx512f,+avx512vl,+avx512bw,+avx512dq

tests/assembly/simd-intrinsic-mask-load.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//@ ignore-test
12
//@ revisions: x86-avx2 x86-avx512
23
//@ [x86-avx2] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel
34
//@ [x86-avx2] compile-flags: -C target-feature=+avx2

tests/assembly/simd-intrinsic-mask-reduce.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//@ ignore-test
12
// verify that simd mask reductions do not introduce additional bit shift operations
23
//@ revisions: x86 aarch64
34
//@ [x86] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel

tests/assembly/simd-intrinsic-mask-store.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//@ ignore-test
12
//@ revisions: x86-avx2 x86-avx512
23
//@ [x86-avx2] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel
34
//@ [x86-avx2] compile-flags: -C target-feature=+avx2

tests/assembly/simd-intrinsic-scatter.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//@ ignore-test
12
//@ revisions: x86-avx512
23
//@ [x86-avx512] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel
34
//@ [x86-avx512] compile-flags: -C target-feature=+avx512f,+avx512vl,+avx512bw,+avx512dq

tests/assembly/simd-intrinsic-select.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//@ ignore-test
12
//@ revisions: x86-avx2 x86-avx512 aarch64
23
//@ [x86-avx2] compile-flags: --target=x86_64-unknown-linux-gnu -C llvm-args=-x86-asm-syntax=intel
34
//@ [x86-avx2] compile-flags: -C target-feature=+avx2

tests/ui/simd-abi-checks.rs

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//@ only-x86_64
2+
//@ build-fail
3+
4+
#![feature(avx512_target_feature)]
5+
#![feature(portable_simd)]
6+
#![allow(improper_ctypes_definitions)]
7+
8+
use std::arch::x86_64::*;
9+
10+
#[repr(transparent)]
11+
struct Wrapper(__m256);
12+
13+
unsafe extern "C" fn w(_: Wrapper) {
14+
//~^ ABI error: this function definition uses a avx vector type, which is not enabled
15+
todo!()
16+
}
17+
18+
unsafe extern "C" fn f(_: __m256) {
19+
//~^ ABI error: this function definition uses a avx vector type, which is not enabled
20+
todo!()
21+
}
22+
23+
unsafe extern "C" fn g() -> __m256 {
24+
//~^ ABI error: this function definition uses a avx vector type, which is not enabled
25+
todo!()
26+
}
27+
28+
#[target_feature(enable = "avx2")]
29+
unsafe extern "C" fn favx(_: __m256) {
30+
todo!()
31+
}
32+
33+
#[target_feature(enable = "avx")]
34+
unsafe extern "C" fn gavx() -> __m256 {
35+
todo!()
36+
}
37+
38+
fn as_f64x8(d: __m512d) -> std::simd::f64x8 {
39+
unsafe { std::mem::transmute(d) }
40+
}
41+
42+
unsafe fn test() {
43+
let arg = std::mem::transmute([0.0f64; 8]);
44+
as_f64x8(arg);
45+
}
46+
47+
fn main() {
48+
unsafe {
49+
f(g());
50+
//~^ ERROR ABI error: this function call uses a avx vector type, which is not enabled in the caller
51+
//~| ERROR ABI error: this function call uses a avx vector type, which is not enabled in the caller
52+
}
53+
54+
unsafe {
55+
favx(gavx());
56+
//~^ ERROR ABI error: this function call uses a avx vector type, which is not enabled in the caller
57+
//~| ERROR ABI error: this function call uses a avx vector type, which is not enabled in the caller
58+
}
59+
60+
unsafe {
61+
test();
62+
}
63+
64+
unsafe {
65+
w(Wrapper(g()));
66+
//~^ ERROR ABI error: this function call uses a avx vector type, which is not enabled in the caller
67+
//~| ERROR ABI error: this function call uses a avx vector type, which is not enabled in the caller
68+
}
69+
}

tests/ui/simd-abi-checks.stderr

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
error: ABI error: this function call uses a avx vector type, which is not enabled in the caller
2+
--> $DIR/simd-abi-checks.rs:49:11
3+
|
4+
LL | f(g());
5+
| ^^^
6+
|
7+
= help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
8+
9+
error: ABI error: this function call uses a avx vector type, which is not enabled in the caller
10+
--> $DIR/simd-abi-checks.rs:49:9
11+
|
12+
LL | f(g());
13+
| ^^^^^^
14+
|
15+
= help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
16+
17+
error: ABI error: this function call uses a avx vector type, which is not enabled in the caller
18+
--> $DIR/simd-abi-checks.rs:55:14
19+
|
20+
LL | favx(gavx());
21+
| ^^^^^^
22+
|
23+
= help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
24+
25+
error: ABI error: this function call uses a avx vector type, which is not enabled in the caller
26+
--> $DIR/simd-abi-checks.rs:55:9
27+
|
28+
LL | favx(gavx());
29+
| ^^^^^^^^^^^^
30+
|
31+
= help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
32+
33+
error: ABI error: this function call uses a avx vector type, which is not enabled in the caller
34+
--> $DIR/simd-abi-checks.rs:65:19
35+
|
36+
LL | w(Wrapper(g()));
37+
| ^^^
38+
|
39+
= help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
40+
41+
error: ABI error: this function call uses a avx vector type, which is not enabled in the caller
42+
--> $DIR/simd-abi-checks.rs:65:9
43+
|
44+
LL | w(Wrapper(g()));
45+
| ^^^^^^^^^^^^^^^
46+
|
47+
= help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
48+
49+
error: ABI error: this function definition uses a avx vector type, which is not enabled
50+
--> $DIR/simd-abi-checks.rs:23:1
51+
|
52+
LL | unsafe extern "C" fn g() -> __m256 {
53+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54+
|
55+
= help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
56+
57+
error: ABI error: this function definition uses a avx vector type, which is not enabled
58+
--> $DIR/simd-abi-checks.rs:18:1
59+
|
60+
LL | unsafe extern "C" fn f(_: __m256) {
61+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62+
|
63+
= help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
64+
65+
error: ABI error: this function definition uses a avx vector type, which is not enabled
66+
--> $DIR/simd-abi-checks.rs:13:1
67+
|
68+
LL | unsafe extern "C" fn w(_: Wrapper) {
69+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
70+
|
71+
= help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
72+
73+
error: aborting due to 9 previous errors
74+

0 commit comments

Comments
 (0)