diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 45aa87b0f..77d38581c 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -11,7 +11,7 @@ env:
RUSTFLAGS: '-D warnings'
jobs:
- test:
+ test-auto:
runs-on: ubuntu-latest
strategy:
matrix:
@@ -38,10 +38,58 @@ jobs:
- run: cargo test --target ${{ matrix.target }} --features digest
- run: cargo test --target ${{ matrix.target }} --features rand_core
- run: cargo test --target ${{ matrix.target }} --features serde
+
+ test-fiat:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ include:
+ # 32-bit target
+ - target: i686-unknown-linux-gnu
+ deps: sudo apt update && sudo apt install gcc-multilib
+
+ # 64-bit target
+ - target: x86_64-unknown-linux-gnu
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - run: rustup target add ${{ matrix.target }}
+ - run: ${{ matrix.deps }}
- env:
RUSTFLAGS: '--cfg curve25519_dalek_backend="fiat"'
run: cargo test --target ${{ matrix.target }}
+ test-serial:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ include:
+ # 32-bit target
+ - target: i686-unknown-linux-gnu
+ deps: sudo apt update && sudo apt install gcc-multilib
+
+ # 64-bit target
+ - target: x86_64-unknown-linux-gnu
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - run: rustup target add ${{ matrix.target }}
+ - run: ${{ matrix.deps }}
+ - env:
+ RUSTFLAGS: '--cfg curve25519_dalek_backend="serial"'
+ run: cargo test --target ${{ matrix.target }}
+
+ build-script:
+ name: Test Build Script
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: stable
+ targets: wasm32-unknown-unknown,x86_64-unknown-linux-gnu,i686-unknown-linux-gnu
+ - run: bash tests/build_tests.sh
+
build-nostd:
name: Build on no_std target (thumbv7em-none-eabi)
runs-on: ubuntu-latest
@@ -55,8 +103,8 @@ jobs:
- run: cargo build --target thumbv7em-none-eabi --release
- run: cargo build --target thumbv7em-none-eabi --release --features serde
- test-simd-native:
- name: Test simd backend (native)
+ test-simd-nightly:
+ name: Test simd backend (nightly)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -66,11 +114,12 @@ jobs:
# 1) build all of the x86_64 SIMD code,
# 2) run all of the SIMD-specific tests that the test runner supports,
# 3) run all of the normal tests using the best available SIMD backend.
+ # This should automatically pick up the simd backend in a x84_64 runner
RUSTFLAGS: '-C target_cpu=native'
- run: cargo test --features simd --target x86_64-unknown-linux-gnu
+ run: cargo test --target x86_64-unknown-linux-gnu
- test-simd-avx2:
- name: Test simd backend (avx2)
+ test-simd-stable:
+ name: Test simd backend (stable)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -78,8 +127,10 @@ jobs:
- env:
# This will run AVX2-specific tests and run all of the normal tests
# with the AVX2 backend, even if the runner supports AVX512.
+ # This should automatically pick up the simd backend in a x86_64 runner
+ # It should pick AVX2 due to stable toolchain used since AVX512 requires nigthly
RUSTFLAGS: '-C target_feature=+avx2'
- run: cargo test --no-default-features --features alloc,precomputed-tables,zeroize,simd_avx2 --target x86_64-unknown-linux-gnu
+ run: cargo test --no-default-features --features alloc,precomputed-tables,zeroize --target x86_64-unknown-linux-gnu
build-docs:
name: Build docs
diff --git a/.gitignore b/.gitignore
index 7acc1af24..6eea85527 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
+*/target/*
target
Cargo.lock
-
+*/Cargo.lock
+build*.txt
*~
\#*
.\#*
diff --git a/Cargo.toml b/Cargo.toml
index 33bbb29a5..72952b9bd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -53,7 +53,6 @@ digest = { version = "0.10", default-features = false, optional = true }
subtle = { version = "2.3.0", default-features = false }
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
zeroize = { version = "1", default-features = false, optional = true }
-unsafe_target_feature = { version = "= 0.1.1", optional = true }
[target.'cfg(target_arch = "x86_64")'.dependencies]
cpufeatures = "0.2.6"
@@ -62,20 +61,13 @@ cpufeatures = "0.2.6"
fiat-crypto = "0.1.19"
[features]
-default = ["alloc", "precomputed-tables", "zeroize", "simd"]
+default = ["alloc", "precomputed-tables", "zeroize"]
alloc = ["zeroize?/alloc"]
precomputed-tables = []
legacy_compatibility = []
-# Whether to allow the use of the AVX2 SIMD backend.
-simd_avx2 = ["unsafe_target_feature"]
-
-# Whether to allow the use of the AVX512 SIMD backend.
-# (Note: This requires Rust nightly; on Rust stable this feature will be ignored.)
-simd_avx512 = ["unsafe_target_feature"]
-
-# A meta-feature to allow all SIMD backends to be used.
-simd = ["simd_avx2", "simd_avx512"]
+[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies]
+curve25519-dalek-derive = { version = "0.1", path = "curve25519-dalek-derive" }
[profile.dev]
opt-level = 2
diff --git a/README.md b/README.md
index 12100691d..cd83e9253 100644
--- a/README.md
+++ b/README.md
@@ -53,9 +53,6 @@ curve25519-dalek = "4.0.0-rc.2"
| `alloc` | ✓ | Enables Edwards and Ristretto multiscalar multiplication, batch scalar inversion, and batch Ristretto double-and-compress. Also enables `zeroize`. |
| `zeroize` | ✓ | Enables [`Zeroize`][zeroize-trait] for all scalar and curve point types. |
| `precomputed-tables` | ✓ | Includes precomputed basepoint multiplication tables. This speeds up `EdwardsPoint::mul_base` and `RistrettoPoint::mul_base` by ~4x, at the cost of ~30KB added to the code size. |
-| `simd_avx2` | ✓ | Allows the AVX2 SIMD backend to be used, if available. |
-| `simd_avx512` | ✓ | Allows the AVX512 SIMD backend to be used, if available. |
-| `simd` | ✓ | Allows every SIMD backend to be used, if available. |
| `rand_core` | | Enables `Scalar::random` and `RistrettoPoint::random`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. |
| `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. |
| `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. |
@@ -90,25 +87,27 @@ latest breaking changes in high level are below:
This release also does a lot of dependency updates and relaxations to unblock upstream build issues.
-### 4.0.0 - Open Breaking Changes
+# Backends
-See tracking issue: [curve25519-dalek/issues/521](https://github.com/dalek-cryptography/curve25519-dalek/issues/521)
+Curve arithmetic is implemented and used by one of the following backends:
-# Backends
+| Backend | Selection | Implementation | Bits / Word sizes |
+| :--- | :--- | :--- | :--- |
+| `serial` | Automatic | An optimized, non-parllel implementation | `32` and `64` |
+| `fiat` | Manual | Formally verified field arithmetic from [fiat-crypto] | `32` and `64` |
+| `simd` | Automatic | Intel AVX2 / AVX512 IFMA accelerated backend | `64` only |
-Curve arithmetic is implemented and used by selecting one of the following backends:
+At runtime, `curve25519-dalek` selects an arithmetic backend from the set of backends it was compiled to support. For Intel x86-64 targets, unless otherwise specified, it will build itself with `simd` support, and default to `serial` at runtime if the appropriate CPU features aren't detected. See [SIMD backend] for more details.
-| Backend | Implementation | Target backends |
-| :--- | :--- | :--- |
-| `[default]` | Automatic runtime backend selection (either serial or SIMD) | `u32`
`u64`
`avx2`
`avx512` |
-| `fiat` | Formally verified field arithmetic from [fiat-crypto] | `fiat_u32`
`fiat_u64` |
+In the future, `simd` backend may be extended to cover more instruction sets. This change will be non-breaking as this is considered as implementation detail.
-To choose a backend other than the `[default]` backend, set the
-environment variable:
+## Manual Backend Override
+
+You can force the crate to compile with specific backend support, e.g., `serial` for x86-64 targets to save code size, or `fiat` to force the runtime to use verified code. To do this, set the environment variable:
```sh
RUSTFLAGS='--cfg curve25519_dalek_backend="BACKEND"'
```
-where `BACKEND` is `fiat`. Equivalently, you can write to
+Equivalently, you can write to
`~/.cargo/config`:
```toml
[build]
@@ -117,53 +116,49 @@ rustflags = ['--cfg=curve25519_dalek_backend="BACKEND"']
More info [here](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags).
Note for contributors: The target backends are not entirely independent of each
-other. The SIMD backend directly depends on parts of the the `u64` backend to
+other. The [SIMD backend] directly depends on parts of the serial backend to
function.
-## Word size for serial backends
+## Bits / Word size
-`curve25519-dalek` will automatically choose the word size for the `[default]`
-and `fiat` serial backends, based on the build target. For example, building
-for a 64-bit machine, the default `u64` target backend is automatically chosen
-when the `[default]` backend is selected, and `fiat_u64` is chosen when the
-`fiat backend is selected.
+`curve25519-dalek` will automatically choose the word size for the `fiat` and
+`serial` backends, based on the build target.
+For example, building for a 64-bit machine, the default 64 bit word size is
+automatically chosen when either the `serial` or `fiat` backend is selected.
-Backend word size can be overridden for `[default]` and `fiat` by setting the
+In some targets it might be required to override the word size for better
+performance.
+Backend word size can be overridden for `serial` and `fiat` by setting the
environment variable:
```sh
RUSTFLAGS='--cfg curve25519_dalek_bits="SIZE"'
```
-where `SIZE` is `32` or `64`. As in the above section, this can also be placed
+`SIZE` is `32` or `64`. As in the above section, this can also be placed
in `~/.cargo/config`.
-**NOTE:** Using a word size of 32 will automatically disable SIMD support.
+Note: The [SIMD backend] requires a word size of 64 bits. Attempting to set bits=32 and backend=`simd` will yield a compile error.
### Cross-compilation
-Because backend selection is done by target, cross-compiling will select the
-correct word size automatically. For example, on an x86-64 Linux machine,
-`curve25519-dalek` will use the `u32` target backend if the following is run:
+Because backend selection is done by target, cross-compiling will select the correct word size automatically. For example, if a x86-64 Linux machine runs the following commands, `curve25519-dalek` will be compiled with the 32-bit `serial` backend.
```console
$ sudo apt install gcc-multilib # (or whatever package manager you use)
$ rustup target add i686-unknown-linux-gnu
$ cargo build --target i686-unknown-linux-gnu
```
-## SIMD target backends
+## SIMD backend
-The SIMD target backend selection is done automatically at runtime depending
-on the available CPU features, provided the appropriate feature flag is enabled.
+The specific SIMD backend (AVX512 / AVX2 / `serial` default) is selected automatically at runtime, depending on the currently available CPU features, and whether Rust nightly is being used for compilation. The precise conditions are specified below.
-You can also specify an appropriate `-C target_feature` to build a binary
-which assumes the required SIMD instructions are always available.
+For a given CPU feature, you can also specify an appropriate `-C target_feature` to build a binary which assumes the required SIMD instructions are always available. Don't do this if you don't have a good reason.
-| Backend | Feature flag | `RUSTFLAGS` | Requires nightly? |
-| :--- | :--- | :--- | :--- |
-| avx2 | `simd_avx2` | `-C target_feature=+avx2` | no |
-| avx512 | `simd_avx512` | `-C target_feature=+avx512ifma,+avx512vl` | yes |
+| Backend | `RUSTFLAGS` | Requires nightly? |
+| :--- | :--- | :--- |
+| avx2 | `-C target_feature=+avx2` | no |
+| avx512 | `-C target_feature=+avx512ifma,+avx512vl` | yes |
-The AVX512 backend requires Rust nightly. When compiled on a non-nightly
-compiler it will always be disabled.
+If compiled on a non-nightly compiler, `curve25519-dalek` will not include AVX512 code, and therefore will never select it at runtime.
# Documentation
@@ -326,3 +321,4 @@ contributions.
[semver]: https://semver.org/spec/v2.0.0.html
[rngcorestd]: https://github.com/rust-random/rand/tree/7aa25d577e2df84a5156f824077bb7f6bdf28d97/rand_core#crate-features
[zeroize-trait]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html
+[SIMD backend]: #simd-backend
diff --git a/build.rs b/build.rs
index 04f4d9ca3..92d2802cd 100644
--- a/build.rs
+++ b/build.rs
@@ -3,6 +3,7 @@
#![deny(clippy::unwrap_used, dead_code)]
#[allow(non_camel_case_types)]
+#[derive(PartialEq, Debug)]
enum DalekBits {
Dalek32,
Dalek64,
@@ -34,6 +35,38 @@ fn main() {
// so for those we want to apply the `#[allow(unused_unsafe)]` attribute to get rid of that warning.
println!("cargo:rustc-cfg=allow_unused_unsafe");
}
+
+ let target_arch = match std::env::var("CARGO_CFG_TARGET_ARCH") {
+ Ok(arch) => arch,
+ _ => "".to_string(),
+ };
+
+ // Backend overrides / defaults
+ let curve25519_dalek_backend =
+ match std::env::var("CARGO_CFG_CURVE25519_DALEK_BACKEND").as_deref() {
+ Ok("fiat") => "fiat",
+ Ok("serial") => "serial",
+ Ok("simd") => {
+ // simd can only be enabled on x86_64 & 64bit target_pointer_width
+ match is_capable_simd(&target_arch, curve25519_dalek_bits) {
+ true => "simd",
+ // If override is not possible this must result to compile error
+ // See: issues/532
+ false => panic!("Could not override curve25519_dalek_backend to simd"),
+ }
+ }
+ // default between serial / simd (if potentially capable)
+ _ => match is_capable_simd(&target_arch, curve25519_dalek_bits) {
+ true => "simd",
+ false => "serial",
+ },
+ };
+ println!("cargo:rustc-cfg=curve25519_dalek_backend=\"{curve25519_dalek_backend}\"");
+}
+
+// Is the target arch & curve25519_dalek_bits potentially simd capable ?
+fn is_capable_simd(arch: &str, bits: DalekBits) -> bool {
+ arch == "x86_64" && bits == DalekBits::Dalek64
}
// Deterministic cfg(curve25519_dalek_bits) when this is not explicitly set.
diff --git a/curve25519-dalek-derive/Cargo.toml b/curve25519-dalek-derive/Cargo.toml
new file mode 100644
index 000000000..11c875ebf
--- /dev/null
+++ b/curve25519-dalek-derive/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "curve25519-dalek-derive"
+version = "0.1.0"
+edition = "2021"
+
+repository = "https://github.com/dalek-cryptography/curve25519-dalek"
+homepage = "https://github.com/dalek-cryptography/curve25519-dalek"
+documentation = "https://docs.rs/curve25519-dalek-derive"
+license = "MIT/Apache-2.0"
+readme = "README.md"
+description = "curve25519-dalek Derives"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0.53"
+quote = "1.0.26"
+syn = { version = "2.0.8", features = ["full"] }
diff --git a/curve25519-dalek-derive/README.md b/curve25519-dalek-derive/README.md
new file mode 100644
index 000000000..7f52d440d
--- /dev/null
+++ b/curve25519-dalek-derive/README.md
@@ -0,0 +1,191 @@
+# A more convenient `#[target_feature]` replacement
+
+To get good performance out of SIMD everything on the SIMD codepath must be inlined.
+With how SIMD is currently implemented in Rust one of two things have to be true for
+a function using SIMD to be inlinable: (and this includes the SIMD intrinsics themselves)
+
+ a) The whole program has to be compiled with the relevant `-C target-cpu` or `-C target-feature` flags.
+
+ b) SIMD support must be automatically detected at runtime, and every function on the SIMD codepath must be marked with `#[target_feature]`.
+
+Both have their downsides. Setting the `target-cpu` or `target-features` makes the resulting binary
+incompatible with older CPUs, while using `#[target_feature]` is incredibly inconvenient.
+
+This crate is meant to make `#[target_feature]` less painful to use.
+
+## Problems with `#[target_feature]`
+
+When we're not compiling with the relevant `target-cpu`/`target-feature` flags everything on
+the SIMD codepath must be marked with the `#[target_feature]` attribute. This is not a problem
+when all of your SIMD code is neatly encapsulated inside of a single function, but once you start
+to build out more elaborate abstractions it starts to become painful to use.
+
+ * It can only be used on `unsafe` functions, so everything on your SIMD codepath now has to be `unsafe`.
+
+ In theory this is nice - these functions require the relevant SIMD instructions to be present at runtime,
+ so calling them without checking is obviously unsafe! But in practice this is rarely what you want. When
+ you build an abstraction over SIMD code you usually want to assume that *internally* within your module
+ all of the necessary SIMD instructions are available, and you only want to check this at the boundaries
+ when you're first entering your module. You do *not* want to infect everything *inside* of the module with
+ `unsafe` since you've already checked this invariant at the module's API boundary.
+
+ * It cannot be used on non-`unsafe` trait methods.
+
+ If you're implementing a trait, say for example `std::ops::Add`, then you cannot mark the method `unsafe`
+ unless the original trait also has it marked as `unsafe`, and usually it doesn't.
+
+ * It makes it impossible to abstract over a given SIMD instruction set using a trait.
+
+ For example, let's assume you want to abstract over which SIMD instructions you use using a trait in the following way:
+
+ ```rust
+ trait Backend {
+ unsafe fn sum(input: &[u32]) -> u32;
+ }
+
+ struct AVX;
+ impl Backend for AVX {
+ #[target_feature(enable = "avx")]
+ unsafe fn sum(xs: &[u32]) -> u32 {
+ // ...
+ todo!();
+ }
+ }
+
+ struct AVX2;
+ impl Backend for AVX2 {
+ #[target_feature(enable = "avx2")]
+ unsafe fn sum(xs: &[u32]) -> u32 {
+ // ...
+ todo!();
+ }
+ }
+
+ // And now you want a have function which calls into that trait:
+ unsafe fn do_calculations(xs: &[u32]) -> u32 where B: Backend {
+ let value = B::sum(xs);
+ // ...do some more calculations here...
+ value
+ }
+ ```
+
+ We have a problem here. This has to be marked with `#[target_feature]`, and that has to specify the concrete
+ feature flag for a given SIMD instruction set, but this function is generic so we can't do that!
+
+## How does this crate make it better?
+
+### You can now mark safe functions with `#[target_feature]`
+
+This crate exposes an `#[unsafe_target_feature]` macro which works just like `#[target_feature]` except
+it moves the `unsafe` from the function prototype into the macro name, and can be used on safe functions.
+
+```rust,compile_fail
+// ERROR: `#[target_feature(..)]` can only be applied to `unsafe` functions
+#[target_feature(enable = "avx2")]
+fn func() {}
+```
+
+```rust
+// It works, but must be `unsafe`
+#[target_feature(enable = "avx2")]
+unsafe fn func() {}
+```
+
+```rust
+use curve25519_dalek_derive::unsafe_target_feature;
+
+// No `unsafe` on the function itself!
+#[unsafe_target_feature("avx2")]
+fn func() {}
+```
+
+It can also be used to mark functions inside of impls:
+
+```rust,compile_fail
+struct S;
+
+impl core::ops::Add for S {
+ type Output = S;
+ // ERROR: method `add` has an incompatible type for trait
+ #[target_feature(enable = "avx2")]
+ unsafe fn add(self, rhs: S) -> S {
+ S
+ }
+}
+```
+
+```rust
+use curve25519_dalek_derive::unsafe_target_feature;
+
+struct S;
+
+#[unsafe_target_feature("avx2")]
+impl core::ops::Add for S {
+ type Output = S;
+ // No `unsafe` on the function itself!
+ fn add(self, rhs: S) -> S {
+ S
+ }
+}
+
+```
+
+### You can generate specialized copies of a module for each target feature
+
+```rust
+use curve25519_dalek_derive::unsafe_target_feature_specialize;
+
+#[unsafe_target_feature_specialize("sse2", "avx2", conditional("avx512ifma", nightly))]
+mod simd {
+ #[for_target_feature("sse2")]
+ pub const CONSTANT: u32 = 1;
+
+ #[for_target_feature("avx2")]
+ pub const CONSTANT: u32 = 2;
+
+ #[for_target_feature("avx512ifma")]
+ pub const CONSTANT: u32 = 3;
+
+ pub fn func() { /* ... */ }
+}
+
+fn entry_point() {
+ #[cfg(nightly)]
+ if std::is_x86_feature_detected!("avx512ifma") {
+ return simd_avx512ifma::func();
+ }
+
+ if std::is_x86_feature_detected!("avx2") {
+ return simd_avx2::func();
+ }
+
+ if std::is_x86_feature_detected!("sse2") {
+ return simd_sse2::func();
+ }
+
+ unimplemented!();
+}
+```
+
+## How to use `#[unsafe_target_feature]`?
+
+ - Can be used on `fn`s, `impl`s and `mod`s.
+ - When used on a function will only apply to that function; it won't apply to any nested functions, traits, mods, etc.
+ - When used on an `impl` will only apply to all of the functions directly defined inside of that `impl`.
+ - When used on a `mod` will only apply to all of the `fn`s and `impl`s directly defined inside of that `mod`.
+ - Cannot be used on methods which use `self` or `Self`; instead use it on the `impl` in which the method is defined.
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or )
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or )
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
diff --git a/curve25519-dalek-derive/src/lib.rs b/curve25519-dalek-derive/src/lib.rs
new file mode 100644
index 000000000..53877493e
--- /dev/null
+++ b/curve25519-dalek-derive/src/lib.rs
@@ -0,0 +1,466 @@
+#![doc = include_str!("../README.md")]
+
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use syn::spanned::Spanned;
+
+macro_rules! unsupported_if_some {
+ ($value:expr) => {
+ if let Some(value) = $value {
+ return syn::Error::new(value.span(), "unsupported by #[unsafe_target_feature(...)]")
+ .into_compile_error()
+ .into();
+ }
+ };
+}
+
+macro_rules! unsupported {
+ ($value: expr) => {
+ return syn::Error::new(
+ $value.span(),
+ "unsupported by #[unsafe_target_feature(...)]",
+ )
+ .into_compile_error()
+ .into()
+ };
+}
+
+mod kw {
+ syn::custom_keyword!(conditional);
+}
+
+enum SpecializeArg {
+ LitStr(syn::LitStr),
+ Conditional(Conditional),
+}
+
+impl SpecializeArg {
+ fn lit(&self) -> &syn::LitStr {
+ match self {
+ SpecializeArg::LitStr(lit) => lit,
+ SpecializeArg::Conditional(conditional) => &conditional.lit,
+ }
+ }
+
+ fn condition(&self) -> Option<&TokenStream2> {
+ match self {
+ SpecializeArg::LitStr(..) => None,
+ SpecializeArg::Conditional(conditional) => Some(&conditional.attr),
+ }
+ }
+}
+
+struct Conditional {
+ lit: syn::LitStr,
+ attr: TokenStream2,
+}
+
+impl syn::parse::Parse for Conditional {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result {
+ let lit = input.parse()?;
+ input.parse::()?;
+ let attr = input.parse()?;
+
+ Ok(Conditional { lit, attr })
+ }
+}
+
+impl syn::parse::Parse for SpecializeArg {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::conditional) {
+ input.parse::()?;
+
+ let content;
+ syn::parenthesized!(content in input);
+
+ let conditional = content.parse()?;
+ Ok(SpecializeArg::Conditional(conditional))
+ } else {
+ Ok(SpecializeArg::LitStr(input.parse()?))
+ }
+ }
+}
+
+struct SpecializeArgs(syn::punctuated::Punctuated);
+
+impl syn::parse::Parse for SpecializeArgs {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result {
+ Ok(Self(syn::punctuated::Punctuated::parse_terminated(input)?))
+ }
+}
+
+#[proc_macro_attribute]
+pub fn unsafe_target_feature(attributes: TokenStream, input: TokenStream) -> TokenStream {
+ let attributes = syn::parse_macro_input!(attributes as syn::LitStr);
+ let item = syn::parse_macro_input!(input as syn::Item);
+ process_item(&attributes, item, true)
+}
+
+#[proc_macro_attribute]
+pub fn unsafe_target_feature_specialize(
+ attributes: TokenStream,
+ input: TokenStream,
+) -> TokenStream {
+ let attributes = syn::parse_macro_input!(attributes as SpecializeArgs);
+ let item_mod = syn::parse_macro_input!(input as syn::ItemMod);
+
+ let mut out = Vec::new();
+ for attributes in attributes.0 {
+ let features: Vec<_> = attributes
+ .lit()
+ .value()
+ .split(",")
+ .map(|feature| feature.replace(" ", ""))
+ .collect();
+ let name = format!("{}_{}", item_mod.ident, features.join("_"));
+ let ident = syn::Ident::new(&name, item_mod.ident.span());
+ let mut attrs = item_mod.attrs.clone();
+ if let Some(condition) = attributes.condition() {
+ attrs.push(syn::Attribute {
+ pound_token: Default::default(),
+ style: syn::AttrStyle::Outer,
+ bracket_token: Default::default(),
+ meta: syn::Meta::List(syn::MetaList {
+ path: syn::Ident::new("cfg", attributes.lit().span()).into(),
+ delimiter: syn::MacroDelimiter::Paren(Default::default()),
+ tokens: condition.clone(),
+ }),
+ });
+ }
+
+ let item_mod = process_mod(
+ attributes.lit(),
+ syn::ItemMod {
+ attrs,
+ ident,
+ ..item_mod.clone()
+ },
+ Some(features),
+ );
+
+ out.push(item_mod);
+ }
+
+ quote::quote! {
+ #(#out)*
+ }
+ .into()
+}
+
+fn process_item(attributes: &syn::LitStr, item: syn::Item, strict: bool) -> TokenStream {
+ match item {
+ syn::Item::Fn(function) => process_function(attributes, function, None),
+ syn::Item::Impl(item_impl) => process_impl(attributes, item_impl),
+ syn::Item::Mod(item_mod) => process_mod(attributes, item_mod, None).into(),
+ item => {
+ if strict {
+ unsupported!(item)
+ } else {
+ quote::quote! { #item }.into()
+ }
+ }
+ }
+}
+
+fn process_mod(
+ attributes: &syn::LitStr,
+ mut item_mod: syn::ItemMod,
+ spec_features: Option>,
+) -> TokenStream2 {
+ if let Some((_, ref mut content)) = item_mod.content {
+ 'next_item: for item in content {
+ if let Some(ref spec_features) = spec_features {
+ match item {
+ syn::Item::Const(syn::ItemConst { ref mut attrs, .. })
+ | syn::Item::Enum(syn::ItemEnum { ref mut attrs, .. })
+ | syn::Item::ExternCrate(syn::ItemExternCrate { ref mut attrs, .. })
+ | syn::Item::Fn(syn::ItemFn { ref mut attrs, .. })
+ | syn::Item::ForeignMod(syn::ItemForeignMod { ref mut attrs, .. })
+ | syn::Item::Impl(syn::ItemImpl { ref mut attrs, .. })
+ | syn::Item::Macro(syn::ItemMacro { ref mut attrs, .. })
+ | syn::Item::Mod(syn::ItemMod { ref mut attrs, .. })
+ | syn::Item::Static(syn::ItemStatic { ref mut attrs, .. })
+ | syn::Item::Struct(syn::ItemStruct { ref mut attrs, .. })
+ | syn::Item::Trait(syn::ItemTrait { ref mut attrs, .. })
+ | syn::Item::TraitAlias(syn::ItemTraitAlias { ref mut attrs, .. })
+ | syn::Item::Type(syn::ItemType { ref mut attrs, .. })
+ | syn::Item::Union(syn::ItemUnion { ref mut attrs, .. })
+ | syn::Item::Use(syn::ItemUse { ref mut attrs, .. }) => {
+ let mut index = 0;
+ while index < attrs.len() {
+ let attr = &attrs[index];
+ if matches!(attr.style, syn::AttrStyle::Outer) {
+ match attr.meta {
+ syn::Meta::List(ref list)
+ if is_path_eq(&list.path, "for_target_feature") =>
+ {
+ let feature: syn::LitStr = match list.parse_args() {
+ Ok(feature) => feature,
+ Err(error) => {
+ return error.into_compile_error();
+ }
+ };
+
+ let feature = feature.value();
+ if !spec_features
+ .iter()
+ .any(|enabled_feature| feature == *enabled_feature)
+ {
+ *item = syn::Item::Verbatim(Default::default());
+ continue 'next_item;
+ }
+
+ attrs.remove(index);
+ continue;
+ }
+ _ => {}
+ }
+ }
+
+ index += 1;
+ continue;
+ }
+ }
+ _ => {
+ unsupported!(item_mod);
+ }
+ }
+ }
+
+ *item = syn::Item::Verbatim(
+ process_item(
+ attributes,
+ std::mem::replace(item, syn::Item::Verbatim(Default::default())),
+ false,
+ )
+ .into(),
+ );
+ }
+ }
+
+ quote::quote! {
+ #item_mod
+ }
+}
+
+fn process_impl(attributes: &syn::LitStr, mut item_impl: syn::ItemImpl) -> TokenStream {
+ unsupported_if_some!(item_impl.defaultness);
+ unsupported_if_some!(item_impl.unsafety);
+
+ let mut items = Vec::new();
+ for item in item_impl.items.drain(..) {
+ match item {
+ syn::ImplItem::Fn(function) => {
+ unsupported_if_some!(function.defaultness);
+ let function = syn::ItemFn {
+ attrs: function.attrs,
+ vis: function.vis,
+ sig: function.sig,
+ block: Box::new(function.block),
+ };
+ let output_item = process_function(
+ attributes,
+ function,
+ Some((item_impl.generics.clone(), item_impl.self_ty.clone())),
+ );
+ items.push(syn::ImplItem::Verbatim(output_item.into()));
+ }
+ item => items.push(item),
+ }
+ }
+
+ item_impl.items = items;
+ quote::quote! {
+ #item_impl
+ }
+ .into()
+}
+
+fn is_path_eq(path: &syn::Path, ident: &str) -> bool {
+ let segments: Vec<_> = ident.split("::").collect();
+ path.segments.len() == segments.len()
+ && path
+ .segments
+ .iter()
+ .zip(segments.iter())
+ .all(|(segment, expected)| segment.ident == expected && segment.arguments.is_none())
+}
+
+fn process_function(
+ attributes: &syn::LitStr,
+ function: syn::ItemFn,
+ outer: Option<(syn::Generics, Box)>,
+) -> TokenStream {
+ if function.sig.unsafety.is_some() {
+ return quote::quote! {
+ #[target_feature(enable = #attributes)]
+ #function
+ }
+ .into();
+ }
+
+ unsupported_if_some!(function.sig.constness);
+ unsupported_if_some!(function.sig.asyncness);
+ unsupported_if_some!(function.sig.abi);
+ unsupported_if_some!(function.sig.variadic);
+
+ let function_visibility = function.vis;
+ let function_name = function.sig.ident;
+ let function_return = function.sig.output;
+ let function_inner_name =
+ syn::Ident::new(&format!("_impl_{}", function_name), function_name.span());
+ let function_args = function.sig.inputs;
+ let function_body = function.block;
+ let mut function_call_args = Vec::new();
+ let mut function_args_outer = Vec::new();
+ let mut function_args_inner = Vec::new();
+ for (index, arg) in function_args.iter().enumerate() {
+ match arg {
+ syn::FnArg::Receiver(receiver) => {
+ unsupported_if_some!(receiver.attrs.first());
+ unsupported_if_some!(receiver.colon_token);
+
+ if outer.is_none() {
+ return syn::Error::new(receiver.span(), "unsupported by #[unsafe_target_feature(...)]; put the attribute on the outer `impl`").into_compile_error().into();
+ }
+
+ function_args_inner.push(syn::FnArg::Receiver(receiver.clone()));
+ function_args_outer.push(syn::FnArg::Receiver(receiver.clone()));
+ function_call_args.push(syn::Ident::new("self", receiver.self_token.span()));
+ }
+ syn::FnArg::Typed(ty) => {
+ unsupported_if_some!(ty.attrs.first());
+
+ match &*ty.pat {
+ syn::Pat::Ident(pat_ident) => {
+ unsupported_if_some!(pat_ident.attrs.first());
+
+ function_args_inner.push(arg.clone());
+ function_args_outer.push(syn::FnArg::Typed(syn::PatType {
+ attrs: Vec::new(),
+ pat: Box::new(syn::Pat::Ident(syn::PatIdent {
+ attrs: Vec::new(),
+ by_ref: None,
+ mutability: None,
+ ident: pat_ident.ident.clone(),
+ subpat: None,
+ })),
+ colon_token: ty.colon_token,
+ ty: ty.ty.clone(),
+ }));
+ function_call_args.push(pat_ident.ident.clone());
+ }
+ syn::Pat::Wild(pat_wild) => {
+ unsupported_if_some!(pat_wild.attrs.first());
+
+ let ident = syn::Ident::new(
+ &format!("__arg_{}__", index),
+ pat_wild.underscore_token.span(),
+ );
+ function_args_inner.push(arg.clone());
+ function_args_outer.push(syn::FnArg::Typed(syn::PatType {
+ attrs: Vec::new(),
+ pat: Box::new(syn::Pat::Ident(syn::PatIdent {
+ attrs: Vec::new(),
+ by_ref: None,
+ mutability: None,
+ ident: ident.clone(),
+ subpat: None,
+ })),
+ colon_token: ty.colon_token,
+ ty: ty.ty.clone(),
+ }));
+ function_call_args.push(ident);
+ }
+ _ => unsupported!(arg),
+ }
+ }
+ }
+ }
+
+ let mut maybe_inline = quote::quote! {};
+ let mut maybe_outer_attributes = Vec::new();
+ let mut maybe_cfg = quote::quote! {};
+ for attribute in function.attrs {
+ match &attribute.meta {
+ syn::Meta::Path(path) if is_path_eq(path, "inline") => {
+ maybe_inline = quote::quote! { #[inline] };
+ }
+ syn::Meta::Path(path) if is_path_eq(path, "test") => {
+ maybe_outer_attributes.push(attribute);
+ maybe_cfg = quote::quote! { #[cfg(target_feature = #attributes)] };
+ }
+ syn::Meta::List(syn::MetaList { path, tokens, .. })
+ if is_path_eq(path, "inline") && tokens.to_string() == "always" =>
+ {
+ maybe_inline = quote::quote! { #[inline] };
+ }
+ syn::Meta::NameValue(syn::MetaNameValue { path, .. }) if is_path_eq(path, "doc") => {
+ maybe_outer_attributes.push(attribute);
+ }
+ syn::Meta::List(syn::MetaList { path, .. })
+ if is_path_eq(path, "cfg")
+ || is_path_eq(path, "allow")
+ || is_path_eq(path, "deny") =>
+ {
+ maybe_outer_attributes.push(attribute);
+ }
+ syn::Meta::Path(path) if is_path_eq(path, "rustfmt::skip") => {
+ maybe_outer_attributes.push(attribute);
+ }
+ _ => unsupported!(attribute),
+ }
+ }
+
+ let (fn_impl_generics, fn_ty_generics, fn_where_clause) =
+ function.sig.generics.split_for_impl();
+ let fn_call_generics = fn_ty_generics.as_turbofish();
+
+ if let Some((generics, self_ty)) = outer {
+ let (outer_impl_generics, outer_ty_generics, outer_where_clause) =
+ generics.split_for_impl();
+ let trait_ident =
+ syn::Ident::new(&format!("__Impl_{}__", function_name), function_name.span());
+ let item_trait = quote::quote! {
+ #[allow(non_camel_case_types)]
+ trait #trait_ident #outer_impl_generics #outer_where_clause {
+ unsafe fn #function_inner_name #fn_impl_generics (#(#function_args_outer),*) #function_return #fn_where_clause;
+ }
+ };
+
+ let item_trait_impl = quote::quote! {
+ impl #outer_impl_generics #trait_ident #outer_ty_generics for #self_ty #outer_where_clause {
+ #[target_feature(enable = #attributes)]
+ #maybe_inline
+ unsafe fn #function_inner_name #fn_impl_generics (#(#function_args_inner),*) #function_return #fn_where_clause #function_body
+ }
+ };
+
+ quote::quote! {
+ #[inline(always)]
+ #(#maybe_outer_attributes)*
+ #function_visibility fn #function_name #fn_impl_generics (#(#function_args_outer),*) #function_return #fn_where_clause {
+ #item_trait
+ #item_trait_impl
+ unsafe {
+ ::#function_inner_name #fn_call_generics (#(#function_call_args),*)
+ }
+ }
+ }.into()
+ } else {
+ quote::quote! {
+ #[inline(always)]
+ #maybe_cfg
+ #(#maybe_outer_attributes)*
+ #function_visibility fn #function_name #fn_impl_generics (#(#function_args_outer),*) #function_return #fn_where_clause {
+ #[target_feature(enable = #attributes)]
+ #maybe_inline
+ unsafe fn #function_inner_name #fn_impl_generics (#(#function_args_inner),*) #function_return #fn_where_clause #function_body
+ unsafe {
+ #function_inner_name #fn_call_generics (#(#function_call_args),*)
+ }
+ }
+ }.into()
+ }
+}
diff --git a/curve25519-dalek-derive/tests/tests.rs b/curve25519-dalek-derive/tests/tests.rs
new file mode 100644
index 000000000..2ccd237e6
--- /dev/null
+++ b/curve25519-dalek-derive/tests/tests.rs
@@ -0,0 +1,151 @@
+#![allow(dead_code)]
+#![allow(unused_imports)]
+
+use curve25519_dalek_derive::{unsafe_target_feature, unsafe_target_feature_specialize};
+
+#[unsafe_target_feature("sse2")]
+/// A doc comment.
+fn function(a: u32, b: u32) -> u32 {
+ a - b
+}
+
+#[unsafe_target_feature("sse2")]
+fn function_with_const_arg(b: u32) -> u32 {
+ N - b
+}
+
+#[unsafe_target_feature("sse2")]
+fn function_with_where_clause(a: T, b: T) -> T::Output
+where
+ T: Copy + core::ops::Sub,
+{
+ a - b
+}
+
+#[unsafe_target_feature("sse2")]
+#[cfg(feature = "dummy")]
+fn function_with_cfg() {}
+
+#[unsafe_target_feature("sse2")]
+#[rustfmt::skip]
+fn function_with_rustfmt_skip() {}
+
+struct Struct {
+ a: u32,
+}
+
+#[unsafe_target_feature("sse2")]
+impl Struct {
+ #[allow(unused_mut)]
+ fn member_function(&self, mut b: u32) -> u32 {
+ self.a - b
+ }
+
+ fn member_function_with_const_arg(self) -> u32 {
+ self.a - N
+ }
+
+ #[cfg(feature = "dummy")]
+ fn member_function_with_cfg() {}
+}
+
+struct StructWithGenerics
+where
+ T: Copy + core::ops::Sub,
+{
+ a: T,
+}
+
+#[unsafe_target_feature("sse2")]
+impl StructWithGenerics
+where
+ T: Copy + core::ops::Sub,
+{
+ #[inline]
+ fn member_function(&self, b: T) -> T::Output {
+ self.a - b
+ }
+}
+
+struct StructWithGenericsNoWhere {
+ a: T,
+}
+
+#[unsafe_target_feature("sse2")]
+impl StructWithGenericsNoWhere {
+ #[inline(always)]
+ fn member_function(&self, b: T) -> T::Output {
+ self.a - b
+ }
+}
+
+#[unsafe_target_feature("sse2")]
+#[allow(dead_code)]
+impl<'a> From<&'a Struct> for () {
+ fn from(_: &'a Struct) -> Self {
+ ()
+ }
+}
+
+#[unsafe_target_feature("sse2")]
+mod inner {
+ fn inner_function(a: u32, b: u32) -> u32 {
+ a - b
+ }
+}
+
+#[unsafe_target_feature_specialize("sse2", "avx2", conditional("avx512ifma", disabled))]
+mod inner_spec {
+ use std;
+
+ #[for_target_feature("sse2")]
+ const CONST: u32 = 1;
+
+ #[for_target_feature("avx2")]
+ const CONST: u32 = 2;
+
+ pub fn spec_function(a: u32, b: u32) -> u32 {
+ a - b - CONST
+ }
+
+ #[for_target_feature("sse2")]
+ const IS_AVX2: bool = false;
+
+ #[for_target_feature("avx2")]
+ const IS_AVX2: bool = true;
+
+ #[test]
+ fn test_specialized() {
+ assert!(!IS_AVX2);
+ }
+
+ #[cfg(test)]
+ mod tests {
+ #[test]
+ fn test_specialized_inner() {
+ assert!(!super::IS_AVX2);
+ }
+ }
+}
+
+#[unsafe_target_feature("sse2")]
+#[test]
+fn test_sse2_only() {}
+
+#[unsafe_target_feature("avx2")]
+#[test]
+fn test_avx2_only() {
+ compile_error!();
+}
+
+#[test]
+fn test_function() {
+ assert_eq!(function(10, 3), 7);
+ assert_eq!(function_with_where_clause(10, 3), 7);
+ assert_eq!(function_with_const_arg::<10>(3), 7);
+ assert_eq!(Struct { a: 10 }.member_function(3), 7);
+ assert_eq!(StructWithGenerics { a: 10 }.member_function(3), 7);
+ assert_eq!(StructWithGenericsNoWhere { a: 10 }.member_function(3), 7);
+ assert_eq!(inner_spec_sse2::spec_function(10, 3), 6);
+ assert_eq!(inner_spec_avx2::spec_function(10, 3), 5);
+}
diff --git a/src/backend/mod.rs b/src/backend/mod.rs
index 18c8c2251..4424e0a53 100644
--- a/src/backend/mod.rs
+++ b/src/backend/mod.rs
@@ -39,41 +39,21 @@ use crate::Scalar;
pub mod serial;
-#[cfg(all(
- target_arch = "x86_64",
- any(feature = "simd_avx2", all(feature = "simd_avx512", nightly)),
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
-))]
+#[cfg(curve25519_dalek_backend = "simd")]
pub mod vector;
#[derive(Copy, Clone)]
enum BackendKind {
- #[cfg(all(
- target_arch = "x86_64",
- feature = "simd_avx2",
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(curve25519_dalek_backend = "simd")]
Avx2,
- #[cfg(all(
- target_arch = "x86_64",
- all(feature = "simd_avx512", nightly),
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
Avx512,
Serial,
}
#[inline]
fn get_selected_backend() -> BackendKind {
- #[cfg(all(
- target_arch = "x86_64",
- all(feature = "simd_avx512", nightly),
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
{
cpufeatures::new!(cpuid_avx512, "avx512ifma", "avx512vl");
let token_avx512: cpuid_avx512::InitToken = cpuid_avx512::init();
@@ -82,12 +62,7 @@ fn get_selected_backend() -> BackendKind {
}
}
- #[cfg(all(
- target_arch = "x86_64",
- feature = "simd_avx2",
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(curve25519_dalek_backend = "simd")]
{
cpufeatures::new!(cpuid_avx2, "avx2");
let token_avx2: cpuid_avx2::InitToken = cpuid_avx2::init();
@@ -110,10 +85,10 @@ where
use crate::traits::VartimeMultiscalarMul;
match get_selected_backend() {
- #[cfg(all(target_arch = "x86_64", feature = "simd_avx2", curve25519_dalek_bits = "64", not(curve25519_dalek_backend = "fiat")))]
+ #[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 =>
self::vector::scalar_mul::pippenger::spec_avx2::Pippenger::optional_multiscalar_mul::(scalars, points),
- #[cfg(all(target_arch = "x86_64", all(feature = "simd_avx512", nightly), curve25519_dalek_bits = "64", not(curve25519_dalek_backend = "fiat")))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 =>
self::vector::scalar_mul::pippenger::spec_avx512ifma_avx512vl::Pippenger::optional_multiscalar_mul::(scalars, points),
BackendKind::Serial =>
@@ -123,19 +98,9 @@ where
#[cfg(feature = "alloc")]
pub(crate) enum VartimePrecomputedStraus {
- #[cfg(all(
- target_arch = "x86_64",
- feature = "simd_avx2",
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(curve25519_dalek_backend = "simd")]
Avx2(self::vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus),
- #[cfg(all(
- target_arch = "x86_64",
- all(feature = "simd_avx512", nightly),
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
Avx512ifma(
self::vector::scalar_mul::precomputed_straus::spec_avx512ifma_avx512vl::VartimePrecomputedStraus,
),
@@ -152,10 +117,10 @@ impl VartimePrecomputedStraus {
use crate::traits::VartimePrecomputedMultiscalarMul;
match get_selected_backend() {
- #[cfg(all(target_arch = "x86_64", feature = "simd_avx2", curve25519_dalek_bits = "64", not(curve25519_dalek_backend = "fiat")))]
+ #[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 =>
VartimePrecomputedStraus::Avx2(self::vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus::new(static_points)),
- #[cfg(all(target_arch = "x86_64", all(feature = "simd_avx512", nightly), curve25519_dalek_bits = "64", not(curve25519_dalek_backend = "fiat")))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 =>
VartimePrecomputedStraus::Avx512ifma(self::vector::scalar_mul::precomputed_straus::spec_avx512ifma_avx512vl::VartimePrecomputedStraus::new(static_points)),
BackendKind::Serial =>
@@ -179,23 +144,13 @@ impl VartimePrecomputedStraus {
use crate::traits::VartimePrecomputedMultiscalarMul;
match self {
- #[cfg(all(
- target_arch = "x86_64",
- feature = "simd_avx2",
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(curve25519_dalek_backend = "simd")]
VartimePrecomputedStraus::Avx2(inner) => inner.optional_mixed_multiscalar_mul(
static_scalars,
dynamic_scalars,
dynamic_points,
),
- #[cfg(all(
- target_arch = "x86_64",
- all(feature = "simd_avx512", nightly),
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
VartimePrecomputedStraus::Avx512ifma(inner) => inner.optional_mixed_multiscalar_mul(
static_scalars,
dynamic_scalars,
@@ -222,23 +177,13 @@ where
use crate::traits::MultiscalarMul;
match get_selected_backend() {
- #[cfg(all(
- target_arch = "x86_64",
- feature = "simd_avx2",
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 => {
self::vector::scalar_mul::straus::spec_avx2::Straus::multiscalar_mul::(
scalars, points,
)
}
- #[cfg(all(
- target_arch = "x86_64",
- all(feature = "simd_avx512", nightly),
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 => {
self::vector::scalar_mul::straus::spec_avx512ifma_avx512vl::Straus::multiscalar_mul::<
I,
@@ -262,23 +207,13 @@ where
use crate::traits::VartimeMultiscalarMul;
match get_selected_backend() {
- #[cfg(all(
- target_arch = "x86_64",
- feature = "simd_avx2",
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 => {
self::vector::scalar_mul::straus::spec_avx2::Straus::optional_multiscalar_mul::(
scalars, points,
)
}
- #[cfg(all(
- target_arch = "x86_64",
- all(feature = "simd_avx512", nightly),
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 => {
self::vector::scalar_mul::straus::spec_avx512ifma_avx512vl::Straus::optional_multiscalar_mul::<
I,
@@ -296,19 +231,9 @@ where
/// Perform constant-time, variable-base scalar multiplication.
pub fn variable_base_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint {
match get_selected_backend() {
- #[cfg(all(
- target_arch = "x86_64",
- feature = "simd_avx2",
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 => self::vector::scalar_mul::variable_base::spec_avx2::mul(point, scalar),
- #[cfg(all(
- target_arch = "x86_64",
- all(feature = "simd_avx512", nightly),
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 => {
self::vector::scalar_mul::variable_base::spec_avx512ifma_avx512vl::mul(point, scalar)
}
@@ -320,19 +245,9 @@ pub fn variable_base_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint
#[allow(non_snake_case)]
pub fn vartime_double_base_mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint {
match get_selected_backend() {
- #[cfg(all(
- target_arch = "x86_64",
- feature = "simd_avx2",
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 => self::vector::scalar_mul::vartime_double_base::spec_avx2::mul(a, A, b),
- #[cfg(all(
- target_arch = "x86_64",
- all(feature = "simd_avx512", nightly),
- curve25519_dalek_bits = "64",
- not(curve25519_dalek_backend = "fiat")
- ))]
+ #[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 => {
self::vector::scalar_mul::vartime_double_base::spec_avx512ifma_avx512vl::mul(a, A, b)
}
diff --git a/src/backend/vector/avx2/edwards.rs b/src/backend/vector/avx2/edwards.rs
index 7bb58b1ee..56d0835bb 100644
--- a/src/backend/vector/avx2/edwards.rs
+++ b/src/backend/vector/avx2/edwards.rs
@@ -41,7 +41,7 @@ use core::ops::{Add, Neg, Sub};
use subtle::Choice;
use subtle::ConditionallySelectable;
-use unsafe_target_feature::unsafe_target_feature;
+use curve25519_dalek_derive::unsafe_target_feature;
use crate::edwards;
use crate::window::{LookupTable, NafLookupTable5};
diff --git a/src/backend/vector/avx2/field.rs b/src/backend/vector/avx2/field.rs
index bdb55efa5..b593cdc5b 100644
--- a/src/backend/vector/avx2/field.rs
+++ b/src/backend/vector/avx2/field.rs
@@ -48,7 +48,7 @@ use crate::backend::vector::avx2::constants::{
P_TIMES_16_HI, P_TIMES_16_LO, P_TIMES_2_HI, P_TIMES_2_LO,
};
-use unsafe_target_feature::unsafe_target_feature;
+use curve25519_dalek_derive::unsafe_target_feature;
/// Unpack 32-bit lanes into 64-bit lanes:
/// ```ascii,no_run
diff --git a/src/backend/vector/ifma/edwards.rs b/src/backend/vector/ifma/edwards.rs
index ccfe092c8..f8605fe52 100644
--- a/src/backend/vector/ifma/edwards.rs
+++ b/src/backend/vector/ifma/edwards.rs
@@ -16,7 +16,7 @@ use core::ops::{Add, Neg, Sub};
use subtle::Choice;
use subtle::ConditionallySelectable;
-use unsafe_target_feature::unsafe_target_feature;
+use curve25519_dalek_derive::unsafe_target_feature;
use crate::edwards;
use crate::window::{LookupTable, NafLookupTable5};
diff --git a/src/backend/vector/ifma/field.rs b/src/backend/vector/ifma/field.rs
index 5928e14a2..fa8ce2dd9 100644
--- a/src/backend/vector/ifma/field.rs
+++ b/src/backend/vector/ifma/field.rs
@@ -16,7 +16,7 @@ use core::ops::{Add, Mul, Neg};
use crate::backend::serial::u64::field::FieldElement51;
-use unsafe_target_feature::unsafe_target_feature;
+use curve25519_dalek_derive::unsafe_target_feature;
/// A wrapper around `vpmadd52luq` that works on `u64x4`.
#[unsafe_target_feature("avx512ifma,avx512vl")]
diff --git a/src/backend/vector/mod.rs b/src/backend/vector/mod.rs
index d720f4acb..2839dca45 100644
--- a/src/backend/vector/mod.rs
+++ b/src/backend/vector/mod.rs
@@ -14,10 +14,9 @@
#[allow(missing_docs)]
pub mod packed_simd;
-#[cfg(feature = "simd_avx2")]
pub mod avx2;
-#[cfg(all(feature = "simd_avx512", nightly))]
+#[cfg(nightly)]
pub mod ifma;
pub mod scalar_mul;
diff --git a/src/backend/vector/packed_simd.rs b/src/backend/vector/packed_simd.rs
index 6ab5dcc9c..fe83b1865 100644
--- a/src/backend/vector/packed_simd.rs
+++ b/src/backend/vector/packed_simd.rs
@@ -11,7 +11,7 @@
//! by the callers of this code.
use core::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitXor, BitXorAssign, Sub};
-use unsafe_target_feature::unsafe_target_feature;
+use curve25519_dalek_derive::unsafe_target_feature;
macro_rules! impl_shared {
(
diff --git a/src/backend/vector/scalar_mul/pippenger.rs b/src/backend/vector/scalar_mul/pippenger.rs
index b00cb87c5..099f4f5e7 100644
--- a/src/backend/vector/scalar_mul/pippenger.rs
+++ b/src/backend/vector/scalar_mul/pippenger.rs
@@ -9,9 +9,9 @@
#![allow(non_snake_case)]
-#[unsafe_target_feature::unsafe_target_feature_specialize(
- conditional("avx2", feature = "simd_avx2"),
- conditional("avx512ifma,avx512vl", all(feature = "simd_avx512", nightly))
+#[curve25519_dalek_derive::unsafe_target_feature_specialize(
+ "avx2",
+ conditional("avx512ifma,avx512vl", nightly)
)]
pub mod spec {
diff --git a/src/backend/vector/scalar_mul/precomputed_straus.rs b/src/backend/vector/scalar_mul/precomputed_straus.rs
index 8c45c29cf..515b4040c 100644
--- a/src/backend/vector/scalar_mul/precomputed_straus.rs
+++ b/src/backend/vector/scalar_mul/precomputed_straus.rs
@@ -11,9 +11,9 @@
#![allow(non_snake_case)]
-#[unsafe_target_feature::unsafe_target_feature_specialize(
- conditional("avx2", feature = "simd_avx2"),
- conditional("avx512ifma,avx512vl", all(feature = "simd_avx512", nightly))
+#[curve25519_dalek_derive::unsafe_target_feature_specialize(
+ "avx2",
+ conditional("avx512ifma,avx512vl", nightly)
)]
pub mod spec {
diff --git a/src/backend/vector/scalar_mul/straus.rs b/src/backend/vector/scalar_mul/straus.rs
index 046bcd14c..413e6fd9a 100644
--- a/src/backend/vector/scalar_mul/straus.rs
+++ b/src/backend/vector/scalar_mul/straus.rs
@@ -11,9 +11,9 @@
#![allow(non_snake_case)]
-#[unsafe_target_feature::unsafe_target_feature_specialize(
- conditional("avx2", feature = "simd_avx2"),
- conditional("avx512ifma,avx512vl", all(feature = "simd_avx512", nightly))
+#[curve25519_dalek_derive::unsafe_target_feature_specialize(
+ "avx2",
+ conditional("avx512ifma,avx512vl", nightly)
)]
pub mod spec {
@@ -22,6 +22,7 @@ pub mod spec {
use core::borrow::Borrow;
use core::cmp::Ordering;
+ #[cfg(feature = "zeroize")]
use zeroize::Zeroizing;
#[for_target_feature("avx2")]
@@ -67,12 +68,13 @@ pub mod spec {
.map(|s| s.borrow().as_radix_16())
.collect();
// Pass ownership to a `Zeroizing` wrapper
- let scalar_digits = Zeroizing::new(scalar_digits_vec);
+ #[cfg(feature = "zeroize")]
+ let scalar_digits_vec = Zeroizing::new(scalar_digits_vec);
let mut Q = ExtendedPoint::identity();
for j in (0..64).rev() {
Q = Q.mul_by_pow_2(4);
- let it = scalar_digits.iter().zip(lookup_tables.iter());
+ let it = scalar_digits_vec.iter().zip(lookup_tables.iter());
for (s_i, lookup_table_i) in it {
// Q = Q + s_{i,j} * P_i
Q = &Q + &lookup_table_i.select(s_i[j]);
diff --git a/src/backend/vector/scalar_mul/variable_base.rs b/src/backend/vector/scalar_mul/variable_base.rs
index 2da479926..9f924f286 100644
--- a/src/backend/vector/scalar_mul/variable_base.rs
+++ b/src/backend/vector/scalar_mul/variable_base.rs
@@ -1,8 +1,8 @@
#![allow(non_snake_case)]
-#[unsafe_target_feature::unsafe_target_feature_specialize(
- conditional("avx2", feature = "simd_avx2"),
- conditional("avx512ifma,avx512vl", all(feature = "simd_avx512", nightly))
+#[curve25519_dalek_derive::unsafe_target_feature_specialize(
+ "avx2",
+ conditional("avx512ifma,avx512vl", nightly)
)]
pub mod spec {
diff --git a/src/backend/vector/scalar_mul/vartime_double_base.rs b/src/backend/vector/scalar_mul/vartime_double_base.rs
index 191572bb1..ea2af8ad4 100644
--- a/src/backend/vector/scalar_mul/vartime_double_base.rs
+++ b/src/backend/vector/scalar_mul/vartime_double_base.rs
@@ -11,9 +11,9 @@
#![allow(non_snake_case)]
-#[unsafe_target_feature::unsafe_target_feature_specialize(
- conditional("avx2", feature = "simd_avx2"),
- conditional("avx512ifma,avx512vl", all(feature = "simd_avx512", nightly))
+#[curve25519_dalek_derive::unsafe_target_feature_specialize(
+ "avx2",
+ conditional("avx512ifma,avx512vl", nightly)
)]
pub mod spec {
diff --git a/src/diagnostics.rs b/src/diagnostics.rs
new file mode 100644
index 000000000..d5becef67
--- /dev/null
+++ b/src/diagnostics.rs
@@ -0,0 +1,25 @@
+//! Build time diagnostics
+
+// auto is assumed or selected
+#[cfg(curve25519_dalek_backend = "auto")]
+compile_error!("curve25519_dalek_backend is 'auto'");
+
+// fiat was overriden
+#[cfg(curve25519_dalek_backend = "fiat")]
+compile_error!("curve25519_dalek_backend is 'fiat'");
+
+// serial was assumed or overriden
+#[cfg(curve25519_dalek_backend = "serial")]
+compile_error!("curve25519_dalek_backend is 'serial'");
+
+// simd was assumed over overriden
+#[cfg(curve25519_dalek_backend = "simd")]
+compile_error!("curve25519_dalek_backend is 'simd'");
+
+// 32 bits target_pointer_width was assumed or overriden
+#[cfg(curve25519_dalek_bits = "32")]
+compile_error!("curve25519_dalek_bits is '32'");
+
+// 64 bits target_pointer_width was assumed or overriden
+#[cfg(curve25519_dalek_bits = "64")]
+compile_error!("curve25519_dalek_bits is '64'");
diff --git a/src/lib.rs b/src/lib.rs
index f4d1d8223..98d79ae1f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -10,12 +10,9 @@
// - Henry de Valence
#![no_std]
+#![cfg_attr(all(curve25519_dalek_backend = "simd", nightly), feature(stdsimd))]
#![cfg_attr(
- all(target_arch = "x86_64", feature = "simd_avx512", nightly),
- feature(stdsimd)
-)]
-#![cfg_attr(
- all(target_arch = "x86_64", feature = "simd_avx512", nightly),
+ all(curve25519_dalek_backend = "simd", nightly),
feature(avx512_target_feature)
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg, doc_cfg_hide))]
@@ -92,3 +89,7 @@ pub(crate) mod window;
pub use crate::{
edwards::EdwardsPoint, montgomery::MontgomeryPoint, ristretto::RistrettoPoint, scalar::Scalar,
};
+
+// Build time diagnostics for validation
+#[cfg(curve25519_dalek_diagnostics = "build")]
+mod diagnostics;
diff --git a/tests/build_tests.sh b/tests/build_tests.sh
new file mode 100755
index 000000000..ac6e7819d
--- /dev/null
+++ b/tests/build_tests.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+
+function match_and_report() {
+ PATTERN=$1
+ FILE=$2
+
+ if grep -q "$PATTERN" "$FILE"; then
+ echo build OK "$FILE" : "$PATTERN"
+ else
+ echo build ERROR "$FILE" : "$PATTERN"
+ echo ">>>>>>>>>>>>>>>>>>>>>>>>>>"
+ cat "$FILE"
+ echo "<<<<<<<<<<<<<<<<<<<<<<<<<<"
+ exit 1
+ fi
+}
+
+# Assuming naively 64 bit host
+cargo clean
+OUT=build_1.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\"" cargo build > "$OUT" 2>&1
+match_and_report "curve25519_dalek_backend is 'simd'" "$OUT"
+match_and_report "curve25519_dalek_bits is '64'" "$OUT"
+
+# Override to 32 bits assuming naively 64 bit build host
+cargo clean
+OUT=build_2.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_bits=\"32\"" cargo build > "$OUT" 2>&1
+match_and_report "curve25519_dalek_backend is 'serial'" "$OUT"
+match_and_report "curve25519_dalek_bits is '32'" "$OUT"
+
+# Override to 64 bits on 32 bit target
+cargo clean
+OUT=build_3.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_bits=\"64\"" cargo build --target i686-unknown-linux-gnu > "$OUT" 2>&1
+match_and_report "curve25519_dalek_backend is 'serial'" "$OUT"
+match_and_report "curve25519_dalek_bits is '64'" "$OUT"
+
+# 32 bit target default
+cargo clean
+OUT=build_4.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\"" cargo build --target i686-unknown-linux-gnu > "$OUT" 2>&1
+match_and_report "curve25519_dalek_backend is 'serial'" "$OUT"
+match_and_report "curve25519_dalek_bits is '32'" "$OUT"
+
+# wasm 32 bit target default
+cargo clean
+OUT=build_5.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\"" cargo build --target wasm32-unknown-unknown > "$OUT" 2>&1
+match_and_report "curve25519_dalek_backend is 'serial'" "$OUT"
+match_and_report "curve25519_dalek_bits is '32'" "$OUT"
+
+# wasm 32 bit target default
+# Attempted override w/ "simd" should result "serial" addition
+cargo clean
+OUT=build_5_1.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"simd\"" cargo build --target wasm32-unknown-unknown > "$OUT" 2>&1
+# This overide must fail the compilation since "simd" is not available
+# See: issues/532
+match_and_report "Could not override curve25519_dalek_backend to simd" "$OUT"
+
+# fiat override with default 64 bit naive host assumption
+cargo clean
+OUT=build_6.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"fiat\"" cargo build > "$OUT" 2>&1
+match_and_report "curve25519_dalek_backend is 'fiat'" "$OUT"
+match_and_report "curve25519_dalek_bits is '64'" "$OUT"
+
+# fiat 32 bit override
+cargo clean
+OUT=build_7.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"fiat\" --cfg curve25519_dalek_bits=\"32\"" cargo build > "$OUT" 2>&1
+match_and_report "curve25519_dalek_backend is 'fiat'" "$OUT"
+match_and_report "curve25519_dalek_bits is '32'" "$OUT"
+
+# serial override with default 64 bit naive host assumption
+cargo clean
+OUT=build_8.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"serial\"" cargo build > "$OUT" 2>&1
+match_and_report "curve25519_dalek_backend is 'serial'" "$OUT"
+match_and_report "curve25519_dalek_bits is '64'" "$OUT"
+
+# serial 32 bit override
+cargo clean
+OUT=build_9.txt
+env RUSTFLAGS="--cfg curve25519_dalek_diagnostics=\"build\" --cfg curve25519_dalek_backend=\"serial\" --cfg curve25519_dalek_bits=\"32\"" cargo build > "$OUT" 2>&1
+match_and_report "curve25519_dalek_backend is 'serial'" "$OUT"
+match_and_report "curve25519_dalek_bits is '32'" "$OUT"