Skip to content
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

LLVM produces SIGILL when enabling avx2 target feature on x86_64-unknown-none #117938

Open
japaric opened this issue Nov 15, 2023 · 31 comments
Open
Labels
A-target-feature Area: Enabling/disabling target features like AVX, Neon, etc. C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@japaric
Copy link
Member

japaric commented Nov 15, 2023

Steps to reproduce

$ cargo new --lib repro
$ cd repro

$ echo '#![no_std]' > src/lib.rs
$ cargo add [email protected]

$ rustup default 1.73.0
$ rustup target add x86_64-unknown-none
$ cargo b --target x86_64-unknown-none
error: could not compile `poly1305` (lib)

Caused by:
  process didn't exit successfully: `$RUSTUP_TOOLCHAIN/bin/rustc (..)` (signal: 4, SIGILL: illegal instruction)

Running gdb --args $RUSTC_INVOCATION_PRINTED_BY_CARGO produces this backtrace:

Stable Backtrace

Thread 7 "opt cgu.1" received signal SIGILL, Illegal instruction.
[Switching to Thread 0x7fffe11ff6c0 (LWP 102419)]
0x00007ffff13c939f in llvm::X86TargetLowering::ReplaceNodeResults(llvm::SDNode*, llvm::SmallVectorImpl<llvm::SDValue>&, llvm::SelectionDAG&) const [clone .cold.0] ()
   from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
(gdb) backtrace
#0  0x00007ffff13c939f in llvm::X86TargetLowering::ReplaceNodeResults(llvm::SDNode*, llvm::SmallVectorImpl<llvm::SDValue>&, llvm::SelectionDAG&) const [clone .cold.0] ()
   from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
#1  0x00007ffff1130ef5 in llvm::DAGTypeLegalizer::SplitVectorResult(llvm::SDNode*, unsigned int) [clone .cold.0] () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
#2  0x00007ffff000d41a in llvm::DAGTypeLegalizer::run() () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
#3  0x00007ffff01ff81a in llvm::SelectionDAGISel::SelectBasicBlock(llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction, false, false, void>, false, true>, llvm::ilist_iterator<llvm::ilist_detail::node_options<llvm::Instruction, false, false, void>, false, true>, bool&) () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
#4  0x00007ffff04ff282 in llvm::SelectionDAGISel::SelectAllBasicBlocks(llvm::Function const&) () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
#5  0x00007ffff034fc0a in llvm::SelectionDAGISel::runOnMachineFunction(llvm::MachineFunction&) () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
#6  0x00007ffff034f4ee in (anonymous namespace)::X86DAGToDAGISel::runOnMachineFunction(llvm::MachineFunction&) [clone .llvm.6232165262612102610] () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
#7  0x00007ffff016d66a in llvm::FPPassManager::runOnModule(llvm::Module&) () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
#8  0x00007ffff05dcaac in llvm::legacy::PassManagerImpl::run(llvm::Module&) () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.73.0-stable.so
#9  0x00007ffff64d36a6 in LLVMRustWriteOutputFile () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-453cf35e1dd187fa.so
#10 0x00007ffff64d2558 in rustc_codegen_llvm[13e834ec38ef84a5]::back::write::write_output_file () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-453cf35e1dd187fa.so
#11 0x00007ffff64cfcd4 in rustc_codegen_llvm[13e834ec38ef84a5]::back::write::codegen () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-453cf35e1dd187fa.so
#12 0x00007ffff64cd074 in rustc_codegen_ssa[1239057ba2d16fcb]::back::write::finish_intra_module_work::<rustc_codegen_llvm[13e834ec38ef84a5]::LlvmCodegenBackend> () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-453cf35e1dd187fa.so
#13 0x00007ffff64cc75d in rustc_codegen_ssa[1239057ba2d16fcb]::back::write::execute_optimize_work_item::<rustc_codegen_llvm[13e834ec38ef84a5]::LlvmCodegenBackend> () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-453cf35e1dd187fa.so
#14 0x00007ffff64ca627 in std[3759e478f3a6c4f2]::sys_common::backtrace::__rust_begin_short_backtrace::<<rustc_codegen_llvm[13e834ec38ef84a5]::LlvmCodegenBackend as rustc_codegen_ssa[1239057ba2d16fcb]::traits::backend::ExtraBackendMethods>::spawn_named_thread<rustc_codegen_ssa[1239057ba2d16fcb]::back::write::spawn_work<rustc_codegen_llvm[13e834ec38ef84a5]::LlvmCodegenBackend>::{closure#0}, ()>::{closure#0}, ()> () from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-453cf35e1dd187fa.so
#15 0x00007ffff6459256 in <<std[3759e478f3a6c4f2]::thread::Builder>::spawn_unchecked_<<rustc_codegen_llvm[13e834ec38ef84a5]::LlvmCodegenBackend as rustc_codegen_ssa[1239057ba2d16fcb]::traits::backend::ExtraBackendMethods>::spawn_named_thread<rustc_codegen_ssa[1239057ba2d16fcb]::back::write::spawn_work<rustc_codegen_llvm[13e834ec38ef84a5]::LlvmCodegenBackend>::{closure#0}, ()>::{closure#0}, ()>::{closure#1} as core[d28c4e8d9c4eebaa]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} ()
   from /home/japaric/.rustup/toolchains/1.73.0-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-453cf35e1dd187fa.so
#16 0x00007ffff3d71295 in alloc::boxed::{impl#47}::call_once<(), dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global> () at library/alloc/src/boxed.rs:2007
#17 alloc::boxed::{impl#47}::call_once<(), alloc::boxed::Box<dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global>, alloc::alloc::Global> () at library/alloc/src/boxed.rs:2007
#18 std::sys::unix::thread::{impl#2}::new::thread_start () at library/std/src/sys/unix/thread.rs:108
#19 0x00007ffff3ae59eb in ?? () from /usr/lib/libc.so.6
#20 0x00007ffff3b697cc in ?? () from /usr/lib/libc.so.6


Using nightly-2023-11-15 toolchain produces a "LLVM ERROR" instead:

$ cargo +nightly-2023-11-15 b --target x86_64-unknown-none
LLVM ERROR: Do not know how to split the result of this operator!

error: could not compile `poly1305` (lib)

Unless the --release flag is used, then you get the SIGILL with the nightly toolchain. The backtrace appears to be similar to the stable toolchain one:

Nightly Backtrace

(gdb) backtrace
#0  0x00007ffff12744ca in llvm::X86TargetLowering::ReplaceNodeResults(llvm::SDNode*, llvm::SmallVectorImpl<llvm::SDValue>&, llvm::SelectionDAG&) const [clone .cold.0] ()
   from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#1  0x00007ffff133a1ce in llvm::DAGTypeLegalizer::SplitVectorResult(llvm::SDNode*, unsigned int) [clone .cold.0] () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#2  0x00007ffff020e085 in llvm::DAGTypeLegalizer::run() () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#3  0x00007ffff03a8ada in llvm::SelectionDAGISel::CodeGenAndEmitDAG() () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#4  0x00007ffff09c09b8 in llvm::SelectionDAGISel::SelectAllBasicBlocks(llvm::Function const&) () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#5  0x00007ffff05f27fa in llvm::SelectionDAGISel::runOnMachineFunction(llvm::MachineFunction&) () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#6  0x00007ffff05f2016 in (anonymous namespace)::X86DAGToDAGISel::runOnMachineFunction(llvm::MachineFunction&) [clone .llvm.4022770523405222600] () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#7  0x00007ffff034e3c1 in llvm::FPPassManager::runOnFunction(llvm::Function&) () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#8  0x00007ffff034d947 in llvm::FPPassManager::runOnModule(llvm::Module&) () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#9  0x00007ffff04e317a in llvm::legacy::PassManagerImpl::run(llvm::Module&) () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/../lib/libLLVM-17-rust-1.76.0-nightly.so
#10 0x00007ffff6b745d0 in LLVMRustWriteOutputFile () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-a2929300a34289e9.so
#11 0x00007ffff6b7420c in rustc_codegen_llvm[e0f834ca461547f0]::back::write::write_output_file () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-a2929300a34289e9.so
#12 0x00007ffff6b71bdf in rustc_codegen_llvm[e0f834ca461547f0]::back::write::codegen () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-a2929300a34289e9.so
#13 0x00007ffff6b7187f in rustc_codegen_ssa[130828829af41105]::back::write::finish_intra_module_work::<rustc_codegen_llvm[e0f834ca461547f0]::LlvmCodegenBackend> ()
   from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-a2929300a34289e9.so
#14 0x00007ffff6ccb32b in std[14019a58b7d275f1]::sys_common::backtrace::__rust_begin_short_backtrace::<<rustc_codegen_llvm[e0f834ca461547f0]::LlvmCodegenBackend as rustc_codegen_ssa[130828829af41105]::traits::backend::ExtraBackendMethods>::spawn_named_thread<rustc_codegen_ssa[130828829af41105]::back::write::spawn_work<rustc_codegen_llvm[e0f834ca461547f0]::LlvmCodegenBackend>::{closure#0}, ()>::{closure#0}, ()> () from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-a2929300a34289e9.so
#15 0x00007ffff6cca7e4 in <<std[14019a58b7d275f1]::thread::Builder>::spawn_unchecked_<<rustc_codegen_llvm[e0f834ca461547f0]::LlvmCodegenBackend as rustc_codegen_ssa[130828829af41105]::traits::backend::ExtraBackendMethods>::spawn_named_thread<rustc_codegen_ssa[130828829af41105]::back::write::spawn_work<rustc_codegen_llvm[e0f834ca461547f0]::LlvmCodegenBackend>::{closure#0}, ()>::{closure#0}, ()>::{closure#1} as core[a62a0f03b43184e2]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} ()
   from /home/japaric/.rustup/toolchains/nightly-2023-11-15-x86_64-unknown-linux-gnu/bin/../lib/librustc_driver-a2929300a34289e9.so
#16 0x00007ffff1f98915 in alloc::boxed::{impl#47}::call_once<(), dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global> () at library/alloc/src/boxed.rs:2007
#17 alloc::boxed::{impl#47}::call_once<(), alloc::boxed::Box<dyn core::ops::function::FnOnce<(), Output=()>, alloc::alloc::Global>, alloc::alloc::Global> () at library/alloc/src/boxed.rs:2007
#18 std::sys::unix::thread::{impl#2}::new::thread_start () at library/std/src/sys/unix/thread.rs:108
#19 0x00007ffff1d899eb in ?? () from /usr/lib/libc.so.6
#20 0x00007ffff1e0d7cc in ?? () from /usr/lib/libc.so.6

Meta

Downstream discussion: RustCrypto/universal-hashes#189

rustc +1.73.0 --version --verbose

rustc 1.73.0 (cc66ad468 2023-10-03)
binary: rustc
commit-hash: cc66ad468955717ab92600c770da8c1601a4ff33
commit-date: 2023-10-03
host: x86_64-unknown-linux-gnu
release: 1.73.0
LLVM version: 17.0.2

rustc +nightly-2023-11-15 --version --verbose

rustc 1.76.0-nightly (dd430bc8c 2023-11-14)
binary: rustc
commit-hash: dd430bc8c22f57992ec1457a87437d14283fdd65
commit-date: 2023-11-14
host: x86_64-unknown-linux-gnu
release: 1.76.0-nightly
LLVM version: 17.0.5
@japaric japaric added the C-bug Category: This is a bug. label Nov 15, 2023
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Nov 15, 2023
@nikic
Copy link
Contributor

nikic commented Nov 15, 2023

With assertions:

SplitVectorResult #0: t41: v8i32 = llvm.x86.avx2.psllv.d.256 TargetConstant:i64<12153>, t28, t39, /rustc/dd430bc8c22f57992ec1457a87437d14283fdd65/library/core/src/../../stdarch/crates/core_arch/src/x86/avx2.rs:2718:15

@nikic
Copy link
Contributor

nikic commented Nov 15, 2023

@llvm.x86.avx2.psllv.d.256 is called inside @_ZN4core9core_arch3x864avx217_mm256_sllv_epi3217ha2b3f3fbfaa54a1bE with these attributes:

attributes #3 = { inlinehint noredzone nounwind nonlazybind "probe-stack"="inline-asm" "target-cpu"="x86-64" "target-features"="-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float,+avx2" }

The combination of +soft-float and +avx2 is not supported. rustc probably needs to explicitly remove that target feature when compiling functions that enabled FP target features.

@newpavlov
Copy link
Contributor

newpavlov commented Nov 15, 2023

@nikic
The code in question uses autodetection, so it has two branches: "soft" and SIMD-based. Autodetection is done using the cpufeatures crate. On targets like x86_64-unknown-none we effectively get the following code:

#[target_feature(enable = "avx2")]
unsafe fn simd_fn() { ... }

if false {
   unsafe { simd_fn() }
} else {
    soft_fn();
}

The idea here is that the first branch should be eliminated by compiler. But it looks like the compiler starts compiling and lowering simd_fn before branch elimination takes place, which triggers SIGILL with enabled soft floats.

@Noratrieb
Copy link
Member

Noratrieb commented Nov 15, 2023

The compiler doesn't eliminate branches like these in debug mode right now (which, while it does cause perf problems sometimes, is not a bug).

@newpavlov
Copy link
Contributor

newpavlov commented Nov 15, 2023

Yes, but note that the SIGILL happens with --release. In debug mode it's fine (though far from ideal) to get "LLVM ERROR: Do not know how to split the result of this operator!".

@nikic
Copy link
Contributor

nikic commented Nov 15, 2023

I checked, and the --release build failure comes down to essentially the same thing. It's still due to functions with +soft-float,+avx2 target features, just catching a different assertion.

@newpavlov
Copy link
Contributor

Can Rust remove +soft-float inherited from target definition for code which explicitly enables FP-dependent target features?

@nikic
Copy link
Contributor

nikic commented Nov 15, 2023

That should be possible, with two caveats:

  • This may change the ABI of scalar floats passed to the function -- something we currently don't force indirect passing for, unlike vectors.
  • There may be a danger that this results in the generation of libcalls that will fail to link on a +soft-float target.

cc @RalfJung I'm sure you will appreciate this new bit of target feature fun.

@newpavlov
Copy link
Contributor

newpavlov commented Nov 15, 2023

As noted in the linked RustCrypto issue, ideally we need something like this to properly handle nastiness like this in libraries. But I guess it's a separate discussion.

@RalfJung
Copy link
Member

RalfJung commented Nov 15, 2023

I'd say compiling SIMD code on a softfloat target makes fairly little sense. Maybe we should have cfg(hardfloat) so that these functions can be entirely removed on softfloat targets?

Letting you disable target features in a function wouldn't really help, we'd still want to reject even declaring such a function since its ABI is all wrong. (Or we'd have to get LLVM to support a softfloat ABI for SIMD types I guess.) See rust-lang/lang-team#235 for more details on the ABI issues surrounding target features.

We definitely do not want to support soft-float in target_feature, neither positively nor negatively, due to its ABI impact. Soft-float vs hard-float is a target-wide decision that can't be altered on a per-function or even per-compilation-unit level. We currently accept some nonsense like -Ctarget-features=+soft-float on our hardfloat target but that's completely unsupported and pretty broken (you can cause UB in safe code due to ABI incompatibility), IMO we should reject such flags.

@newpavlov
Copy link
Contributor

Maybe we should have cfg(hardfloat) so that these functions can be entirely removed on softfloat targets?

Yes, it could work, but I think a more fundamental solution would be a proper support of "negative" target features. Arguably, we should consider the relation between hard floats and SIMD instructions nothing more than an implementation detail of the x86 targets.

Another alternative is to replace SIMD functions (i.e. functions marked with #[target_feature(enable = "..")]) with placeholders on soft-float targets. The placeholders may panic, abort, or even be something like unreachable_unchecked. SIMD functions should not be reachable on soft-float targets in the first place, so, since calling them is UB, such replacement should be legal for compiler.

@RalfJung
Copy link
Member

RalfJung commented Nov 15, 2023

Yes, it could work, but I think a more fundamental solution would be a proper support of "negative" target features.

But what do you want to do with them? I don't think there is any way we can accept a +avx,-soft-float function on a softfloat target. Such a function has the wrong ABI and should just be rejected. This should be rejected with both -C and #[target_feature]; it's a good thing that the latter doesn't have this problem so we only need to fix the former.

Maybe after fixing all the LLVM issues around this we could accept this and give it a softfloat ABI. But that's far off.

And even then I'm not sure it is desired; in some cases people compiling for softfloat targets want to be really sure that the hardfloat registers are not used, since they plan to not save/restore them on context switches. Enabling hardfloat mode on a softfloat target is unsound in such situations.

Hardfloat/softfloat isn't just a regular target feature you can switch locally. It's a global decision. #[target_feature] has no business overwriting such global decisions.

@tarcieri
Copy link
Contributor

Note that this issue impacts curve25519-dalek as well: dalek-cryptography/curve25519-dalek#601

@tarcieri
Copy link
Contributor

#[cfg(hardfloat)] would probably be sufficient to gate the relevant code, although a little annoying to sprinkle around everywhere

@RalfJung
Copy link
Member

RalfJung commented Nov 15, 2023

#[cfg(hardfloat)] would probably be sufficient to gate the relevant code, although a little annoying to sprinkle around everywhere

Yeah... I just wasn't able to come up with a better alternative yet.

Maybe we should declare (and have the feature-detect macros implement) that SSE features are never available on softfloat targets. Then we can compile functions with SSE #[target_features] into unreachable_unchecked and so their ABI does not matter so we can generate whatever LLVM IR we want.

@tarcieri
Copy link
Contributor

tarcieri commented Nov 15, 2023

I think that's what @newpavlov was suggesting earlier. Sounds good to me.

Edit: specifically meant the second paragraph of #117938 (comment)

@newpavlov
Copy link
Contributor

newpavlov commented Nov 15, 2023

@RalfJung

But what do you want to do with them?

I am talking about this proposal.

Right now this part of target specification:

"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float"

from library point of view only means that the SIMD features are not enabled. Thus code which supports runtime detection of target features could assume that during execution SIMD features may be available, so it has to keep SIMD detection and SIMD-optimized branches.

In other words, right now Rust provides only two target feature states ?feature (target feature is not enabled, but may be present during execution) and +feature (target feature is enabled and can be freely used). Ideally, we need the third "negative" state -feature (alternatively, !feature), i.e. the target feature is not enabled and can not be available during execution. If a target feature is "negative", then libraries should remove autodetection branches which depend on such target feature.

Enabling soft floats would automatically make all SIMD features like SSE and AVX "negative", thus in libraries we will be properly to cfg them out.

@saethlin saethlin added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-ABI Area: Concerning the application binary interface (ABI) A-target-feature Area: Enabling/disabling target features like AVX, Neon, etc. and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. A-ABI Area: Concerning the application binary interface (ABI) labels Nov 15, 2023
@RalfJung
Copy link
Member

RalfJung commented Nov 15, 2023

I think that's what @newpavlov was suggesting earlier. Sounds good to me.

Ah yes they were. I somehow missed that -- sorry. Great, good to see designs converge :)

In other words, right now Rust provides only two target feature states ?feature (target feature is not enabled, but may be present during execution) and +feature (target feature is enabled and can be freely used). Ideally, we need the third "negative" state -feature (alternatively, !feature), i.e. the target feature is not enabled and can not be available during execution. If a target feature is "negative", then libraries should remove autodetection branches which depend on such target feature.

I don't understand what you mean. I thought you were suggesting #[target_feature("-soft-float")] fn ..., but now this sounds different?

In terms of enabled target features, there's the set of target features that are statically enabled in the current code (via #[target_feature] and -Ctarget-feature), which can be queried via cfg, and there's the set of target features that are dynamically enabled at runtime, which can be queried via is_x86_feature_detected! etc. The runtime set of features is always a superset of the compiletime set of features. Target features are either on or off, there's no "?" state.

(I don't think it is sound to use any other way of querying for runtime target features, it must be our macros. I hope cpufeatures uses those macros internally.)

@newpavlov

This comment was marked as off-topic.

@RalfJung

This comment was marked as off-topic.

@newpavlov

This comment was marked as off-topic.

@RalfJung

This comment was marked as off-topic.

@RalfJung
Copy link
Member

Anyway this is getting highly speculative and it's discussing a significant language extension. If we want to continue discussing this we should start a new thread. It's not really relevant for this issue.

The core of this issue is that enabling certain features on certain targets just doesn't work currently, which leads to portability issues.

@RalfJung RalfJung changed the title LLVM produces SIGILL when compiling [email protected] to x86_64-unknown-none LLVM produces SIGILL when enabling avx2 target feature on x86_64-unknown-none Nov 16, 2023
@briansmith
Copy link
Contributor

And even then I'm not sure it is desired; in some cases people compiling for softfloat targets want to be really sure that the hardfloat registers are not used, since they plan to not save/restore them on context switches.

We want to make sure they aren't used unless specifically requested.

Enabling hardfloat mode on a softfloat target is unsound in such situations.

Not necessarily. The Linux kernel has kernel_fpu_begin() and kernel_fpu_end() with which you must wrap your vector-register-using code. Every operating system kernel will likely have something equivalent because they need their in-kernel crypto code to be able to use vector registers (indeed, this is what the Linux kernel crypto code does). Unfortunately, the current design of these targets doesn't seem to expose enough information for us to know which environment we're in. Ideally we'd have target_env="linuxkernel" or something so we could discover what we need to do using cfg.

I do agree that #[target_feature(enable = "avx2")] and the like need to be able to work on these -none targets and that if these targets are going to be +softfloat then softfloat can't be mutually exclusive with using target_feature to enable vector instruction use.

The discussion of "negative" features is a totally separate thing. What's really happening is that the crypto libraries are using CPUID/_xgetbv to detect CPU features, and if CPUID/_xgetbv says some CPU feature is available, then they feel free to use it. This is not the correct thing for us to be doing on these -none targets and it's not something for the language team to solve. It should be tracked in a separate issue; there's already #60123 (comment) where I point out why the proposed has_cpuid misleads us regarding this.

@RalfJung
Copy link
Member

I do agree that #[target_feature(enable = "avx2")] and the like need to be able to work on these -none targets and that if these targets are going to be +softfloat then softfloat can't be mutually exclusive with using target_feature to enable vector instruction use.

That needs work on the LLVM side then, since currently this is not supported in LLVM.

@RalfJung
Copy link
Member

RalfJung commented Nov 21, 2023

And that also complicates the ABI story. Given the principle that target features may not affect ABI (rust-lang/lang-team#235), I guess we need to

  • make sure that floats are always passed the softfloat way, even then AVX or whatever are enabled
  • apply the usual rules for SIMD types, where some ABIs do not support passing them by-value unless the corresponding target feature is enabled

I don't know if LLVM supports the first point here. If the goal is to eventually allow using such target features on softfloat targets, then we should reject these target features until we have a way to enable them without affecting ABI. But that would mean there is no portable way to write code like what triggered this issue until LLVM is fixed...

The fact that LLVM ties together "target features used by ABI" and "target features used by codegen" makes this hard to support. But I don't think we should compromise on having a consistent ABI within any given target triple.

@briansmith
Copy link
Contributor

The fact that LLVM ties together "target features used by ABI" and "target features used by codegen" makes this hard to support. But I don't think we should compromise on having a consistent ABI within any given target triple.

Maybe it already supports this, since clang can build the Linux kernel? Or maybe they only do their SIMD stuff in external .S files?

@tarcieri
Copy link
Contributor

Would CPUID-gating + asm! work on these targets?

@newpavlov
Copy link
Contributor

What's really happening is that the crypto libraries are using CPUID/_xgetbv to detect CPU features, and if CPUID/_xgetbv says some CPU feature is available, then they feel free to use it. This is not the correct thing for us to be doing on these -none targets and it's not something for the language team to solve.

Not quite. In the cpufeatures crate we specifically gate on *-none, *-uefi, and *-sgx targets, which results in code described in this comment. The problem is that Rust/LLVM can not compile (unused) functions which enable SIMD target features (e.g. #[target_feature(enable = "avx2")]) for soft-float targets.

@newpavlov
Copy link
Contributor

It may be a regression from 1.69 to 1.70. I was unable to trigger SIGILL on a simplified example, but this snippet gets properly compiled on 1.69, but causes LLVM ERROR on 1.70 and later.

@briansmith
Copy link
Contributor

It may be a regression from 1.69 to 1.70. I was unable to trigger SIGILL on a simplified example, but this snippet gets properly compiled on 1.69, but causes LLVM ERROR on 1.70 and later.

Realistically, even if this were to be fixed in Rust 1.75, we'd need to find a workaround to avoid increasing MSRV for our projects to 1.75 (for these targets).

It seems instead we may need to "just" ensure that all the conditional logic that enables use of vector registers happens at #[cfg(...)] level instead of at cfg! level. We already have to do that to ensure, for example, we aren't trying to compile Aarch64 assembly on x86-64 targets and vice-versa.

So, I see this being two issues:

  • The compiler shouldn't die with SIGILL but should fail gracefully if it doesn't support this kind of usage.
  • The compiler should support dynamic feature detection/usage of vector instructions on -none and maybe all softfloat targets, or we should redo how targets are classified as softfloat vs. hardfloat vs. possibly "nofloat" or something else.

WDYT?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-target-feature Area: Enabling/disabling target features like AVX, Neon, etc. C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

9 participants