-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Implement RFC 2574, "SIMD vectors in FFI" #86546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
22e8f1e
4a22c0a
f387030
d5471a7
4702a9a
ad10322
d617ed4
79ac2dc
2360446
564779c
eb9ed3a
5002006
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2479,43 +2479,202 @@ fn compute_sig_of_foreign_fn_decl<'tcx>( | |
None, | ||
); | ||
|
||
// Feature gate SIMD types in FFI, since I am not sure that the | ||
// ABIs are handled at all correctly. -huonw | ||
if abi != abi::Abi::RustIntrinsic | ||
&& abi != abi::Abi::PlatformIntrinsic | ||
&& !tcx.features().simd_ffi | ||
{ | ||
let check = |ast_ty: &hir::Ty<'_>, ty: Ty<'_>| { | ||
if ty.is_simd() { | ||
let snip = tcx | ||
.sess | ||
.source_map() | ||
.span_to_snippet(ast_ty.span) | ||
.map_or_else(|_| String::new(), |s| format!(" `{}`", s)); | ||
tcx.sess | ||
.struct_span_err( | ||
ast_ty.span, | ||
&format!( | ||
"use of SIMD type{} in FFI is highly experimental and \ | ||
may result in invalid code", | ||
snip | ||
), | ||
) | ||
.help("add `#![feature(simd_ffi)]` to the crate attributes to enable") | ||
.emit(); | ||
} | ||
}; | ||
// Using SIMD types in FFI signatures requires the signature | ||
// to have appropriate `#[target_feature]`'s enabled. | ||
if abi != abi::Abi::RustIntrinsic && abi != abi::Abi::PlatformIntrinsic { | ||
for (input, ty) in iter::zip(decl.inputs, fty.inputs().skip_binder()) { | ||
check(&input, ty) | ||
simd_ffi_check(tcx, def_id, &input, ty) | ||
} | ||
if let hir::FnRetTy::Return(ref ty) = decl.output { | ||
check(&ty, fty.output().skip_binder()) | ||
simd_ffi_check(tcx, def_id, &ty, fty.output().skip_binder()) | ||
} | ||
} | ||
|
||
fty | ||
} | ||
|
||
/// Returns `Ok()` if the target-feature allows using the SIMD type on C FFI. | ||
/// Otherwise, returns `Err(Some())` if the target_feature needs to be enabled or | ||
/// or `Err(None)` if it's unsupported. | ||
/// Some notes about arguments: | ||
/// - `simd_len`: A vector register size as octet, i.e. vN in the tests. | ||
/// - `simd_elem_width`: A width of each element as octet, e.g. v512 should be 16 on avx512. | ||
fn simd_ffi_feature_check( | ||
target: &str, | ||
simd_width: u64, | ||
simd_elem_width: u64, | ||
JohnTitor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
feature: Option<String>, | ||
) -> Result<(), Option<&'static str>> { | ||
let feature = feature.unwrap_or_default(); | ||
match target { | ||
t if t.contains("x86") => { | ||
// FIXME: this needs to be architecture dependent and | ||
// should probably belong somewhere else: | ||
// * on mips: 16 => msa, | ||
// * wasm: 16 => simd128 | ||
match simd_width { | ||
8 if feature.contains("mmx") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe instead of match:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I'm not sure if it's much improvement, at least the current is readable enough to me. And I feel like that it makes it slightly harder to understand that what |
||
|| feature.contains("sse") | ||
|| feature.contains("ssse") | ||
|| feature.contains("avx") => | ||
{ | ||
Ok(()) | ||
} | ||
8 => Err(None), | ||
16 if feature.contains("sse") => Ok(()), | ||
16 => Err(Some("sse")), | ||
32 if feature.contains("avx") => Ok(()), | ||
32 => Err(Some("avx")), | ||
64 if feature.contains("avx512") => Ok(()), | ||
64 => Err(Some("avx512")), | ||
_ => Err(None), | ||
} | ||
} | ||
t if t.contains("arm") => match simd_width { | ||
8 | 16 | 32 => { | ||
if feature.contains("neon") { | ||
Ok(()) | ||
} else { | ||
Err(Some("neon")) | ||
} | ||
} | ||
_ => Err(None), | ||
}, | ||
t if t.contains("aarch64") => match simd_width { | ||
8 | 16 => { | ||
if feature.contains("neon") { | ||
Ok(()) | ||
} else { | ||
Err(Some("neon")) | ||
} | ||
} | ||
_ => Err(None), | ||
}, | ||
t if t.contains("powerpc") => { | ||
match simd_width { | ||
// 64-bit wide elements are only available in VSX: | ||
16 if simd_elem_width == 8 => { | ||
if feature.contains("vsx") { | ||
Ok(()) | ||
} else { | ||
Err(Some("vsx")) | ||
} | ||
} | ||
16 if simd_elem_width < 8 => { | ||
if feature.contains("altivec") { | ||
Ok(()) | ||
} else { | ||
Err(Some("altivec")) | ||
} | ||
} | ||
_ => Err(None), | ||
} | ||
} | ||
t if t.contains("mips") => match simd_width { | ||
16 => { | ||
if feature.contains("msa") { | ||
Ok(()) | ||
} else { | ||
Err(Some("msa")) | ||
} | ||
} | ||
_ => Err(None), | ||
}, | ||
_ => Err(None), | ||
} | ||
} | ||
|
||
fn simd_ffi_check<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, ast_ty: &hir::Ty<'_>, ty: Ty<'tcx>) { | ||
if !ty.is_simd() { | ||
return; | ||
} | ||
|
||
// The use of SIMD types in FFI is feature-gated: | ||
if !tcx.features().simd_ffi { | ||
let snip = tcx | ||
.sess | ||
.source_map() | ||
.span_to_snippet(ast_ty.span) | ||
.map_or_else(|_| String::new(), |s| format!("{}", s)); | ||
tcx.sess | ||
.struct_span_err( | ||
ast_ty.span, | ||
&format!("use of SIMD type `{}` in FFI is unstable", snip), | ||
) | ||
.help("add `#![feature(simd_ffi)]` to the crate attributes to enable") | ||
.emit(); | ||
return; | ||
} | ||
|
||
// If rustdoc, then we don't type check SIMD on FFI because rustdoc requires | ||
// being able to compile a target, with features of other targets enabled | ||
// (e.g. `x86+neon`, yikes). | ||
if tcx.sess.opts.actually_rustdoc { | ||
return; | ||
} | ||
|
||
let attrs = tcx.codegen_fn_attrs(def_id); | ||
|
||
// Skip LLVM intrinsics. | ||
if let Some(link_name) = attrs.link_name { | ||
if link_name.to_ident_string().starts_with("llvm.") { | ||
return; | ||
} | ||
} | ||
|
||
let features = &attrs.target_features; | ||
let simd_len = tcx | ||
.layout_of(ty::ParamEnvAnd { param_env: ty::ParamEnv::empty(), value: ty }) | ||
.unwrap() | ||
.layout | ||
.size | ||
.bytes(); | ||
let (simd_size, _) = ty.simd_size_and_type(tcx); | ||
let simd_elem_width = simd_len / simd_size; | ||
let target: &str = &tcx.sess.target.arch; | ||
|
||
let type_str = tcx | ||
.sess | ||
.source_map() | ||
.span_to_snippet(ast_ty.span) | ||
.map_or_else(|_| String::new(), |s| format!("{}", s)); | ||
|
||
if features.is_empty() { | ||
// Should **NOT** be `Ok()`. | ||
let f = simd_ffi_feature_check(target, simd_len, simd_elem_width, None).unwrap_err(); | ||
let msg = if let Some(f) = f { | ||
format!( | ||
"use of SIMD type `{}` in FFI requires `#[target_feature(enable = \"{}\")]`", | ||
type_str, f, | ||
) | ||
} else { | ||
format!("use of SIMD type `{}` in FFI not supported by any target features", type_str) | ||
}; | ||
|
||
tcx.sess.struct_span_err(ast_ty.span, &msg).emit(); | ||
} | ||
|
||
for f in features { | ||
if let Err(v) = | ||
simd_ffi_feature_check(target, simd_len, simd_elem_width, Some(f.to_ident_string())) | ||
{ | ||
let msg = if let Some(f) = v { | ||
format!( | ||
"use of SIMD type `{}` in FFI requires `#[target_feature(enable = \"{}\")]`", | ||
type_str, f, | ||
) | ||
} else { | ||
format!( | ||
"use of SIMD type `{}` in FFI not supported by any target features", | ||
type_str | ||
) | ||
}; | ||
tcx.sess.struct_span_err(ast_ty.span, &msg).emit(); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
fn is_foreign_item(tcx: TyCtxt<'_>, def_id: DefId) -> bool { | ||
match tcx.hir().get_if_local(def_id) { | ||
Some(Node::ForeignItem(..)) => true, | ||
|
@@ -2799,7 +2958,10 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs { | |
codegen_fn_attrs.export_name = Some(s); | ||
} | ||
} else if tcx.sess.check_name(attr, sym::target_feature) { | ||
if !tcx.is_closure(id) && tcx.fn_sig(id).unsafety() == hir::Unsafety::Normal { | ||
if !tcx.is_closure(id) | ||
&& !tcx.is_foreign_item(id) | ||
&& tcx.fn_sig(id).unsafety() == hir::Unsafety::Normal | ||
{ | ||
if tcx.sess.target.is_like_wasm || tcx.sess.opts.actually_rustdoc { | ||
// The `#[target_feature]` attribute is allowed on | ||
// WebAssembly targets on all functions, including safe | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know enough about simd to properly review this function. @lqd can you help or ping others?
That said, the function body looks very fragile and easy to get accidentally wrong to me. Is there something declarative that we could parse and process here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can help ... by asking the 2 greats @Amanieu and @nagisa for help ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree but I haven't come up with/know a better solution. @Amanieu Do you have any ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The true source of these ABI constraints is in LLVM's handling of calling conventions. I don't think there is anything we can easily parse here.
My recommendation would be to directly refer to LLVM's source code for calling convention handling to determine which target features affect ABI.
In a way this is similar to what I had to do for
asm!
incompiler/rustc_target/src/asm
which is similarly tied to LLVM implementation details for reserved registers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't LLVM be extended to provide APIs to query information about calling conventions instead of relying on its source code, which is also fragile. LLVM is huge database. Or maybe use tablegen to create rust files ...