I’ve been playing with AVX lately, and I experienced first-hand that building
a program that uses non-standard instructions with Cargo is not as easy as it
could be. I would like to propose a way to specify required and optional
target features in Cargo.toml
. I don’t know if such a small change actually
requires an RFC, but I am requesting comments on this, so here we go:
- Feature Name: cargo_target_feature
- Start Date: 2016-03-22
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
Summary
Add a required-target-features
field to Cargo.toml
where libraries can
declare their required target features, and a target-features
field where
top-level projects can declare the target features that they want to enable.
(Target features here refer to the -C target-feature
codegen option,
not to the [features]
section of Cargo.toml
.)
Motivation
Special instructions that require target features to be enabled can often provide a significant perfomance win on modern CPUs. Projects that wish to take advantage of this (e.g. audio and video encoders or decoders, cryptography libraries, scientific computing libraries) should not be harder to compile and use than any other project.
Detailed design
A project can use a target feature in two ways: optional, or required. When a target feature is required, the code relies on the corresponding instructions being available. An example would be a matrix manipulation library where the algorithms are tuned specifically for the AVX instruction set.
It is also possible for projects to take advantage of target features when
they are available and gracefully fall back otherwise. The
#[cfg(target_feature = "...")]
attribute can be used for this.
An example would be a vector math library that can use AVX when available,
fall back to SSE if that is available, or fall back to serial operations
otherwise. A different example would be a cryto library that has an AES
implementation based on the AES instructions, and a fallback implementation
in pure Rust.
When compiling, only the top level project knows what system it is going to run on, so the top level project should control which target features are enabled. If one of the dependencies requires a certain target feature, it should not be enabled silently: this might unintendedly make the resulting binary incompatible with some systems. This leads to the following design:
-
Libraries should declare their required target features in the
required-target-features
field under[package]
. The type of this field is a list of strings. Example:[package] required-target-features = ["sse", "sse2"]
-
The top-level project should declare the target features to build with in the
target-features
field under[package]
. The type of this field is a list of strings. -
Cargo will verify that the union of all
required-target-feature
s in all dependencies is a subset oftarget-features
of the top-level project. If this is not the case, building will fail with an error message indicating which target features must be enabled, and by which crates they are required. -
The
target-features
declared in the top-level project will be passed to the compiler for all crates that need to be compiled. For instance, if inCargo.toml
the following features are enabled:[package] target-features = ["sse", "sse2"]
Then the compiler will be invoked with:
$ rustc ... -C target-feature=+sse,+sse2 ...
Drawbacks
None that I can think of.
Alternatives
-
RUSTFLAGS
support just landed. It is possible to setRUSTFLAGS
to-C target-feature=+feature
. This can even be done from.cargo/config
. However, this mechanism is not intended for required target features of a crate, and it spreads build configuration over two files, instead of keeping it central inCargo.toml
. -
It is possible to call
cargo rustc -- -C target-feature=+feature
, but this will only pass the target feature to the top-level project, not to dependencies. This makes e.g. the simd crate completely useless. Furthermore it is not very ergonomic to use. -
In the proposed design, instead of failing to build if a required target feature is not declared, it could be enabled implicitly. This will make it easier to build, but it will also make it easier to accidentally produce binaries that are incompatible with some systems.
Unresolved questions
-
The
[package]
section feels like the wrong section to puttarget-features
andrequired-target-features
under. Other codegen options are currently under[profile.*]
, but target features generally do not differ per profile (you want to debug the same code that fails in release).Perhaps they should go under
[target.<triple>]
? This makes sense because the target features depend on the architecture. But what would putting target features in[target.cfg(target_feature = "...")]
do? And how to indicate that only one target is supported? -
Should
target-features
default torequired-target-features
? This can reduce boilerplate for libraries. -
Should there be a way to couple target features to features in the
[features]
section? -
The current design only allows passing target features as
-C target-feature=+feature
. Is there any use case for passing-C target-feature=-feature
? -
Should there be a way for crates to advertise their optional target features?
-
What about
target-cpu
?
I haven’t contributed anything significant to Cargo before, but I am willing to attempt to implement this.
Feedback would be appreciated!