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-features in all
dependencies is a subset of target-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
in Cargo.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 set RUSTFLAGS 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 in
Cargo.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 put
target-features and required-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 to required-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!