Clean up backend features and vendor curve25519_dalek_derive (#531)

* Vendor import unsafe_target_features as curve25519-dalek-derive

Co-authored-by: Jan Bujak <jan@parity.io>

* Remove feature gates from avx2/ifma

* Add buildtime compile diagnostics about backend selection

* Add build script tests

* Documentation changes

* Disable simd related features unless simd was determined via build

* Add note and test about the override warning when unsuccesful

* Reduce complexity in build gating via compile_error

---------

Co-authored-by: Jan Bujak <jan@parity.io>
Co-authored-by: Michael Rosenberg <michael@mrosenberg.pub>
This commit is contained in:
pinkforest(she/her) 2023-06-22 05:46:27 +00:00 committed by GitHub
parent e111b5d913
commit e429bde88d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1124 additions and 193 deletions

View File

@ -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

4
.gitignore vendored
View File

@ -1,6 +1,8 @@
*/target/*
target
Cargo.lock
*/Cargo.lock
build*.txt
*~
\#*
.\#*

View File

@ -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

View File

@ -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
See tracking issue: [curve25519-dalek/issues/521](https://github.com/dalek-cryptography/curve25519-dalek/issues/521)
# Backends
Curve arithmetic is implemented and used by selecting one of the following backends:
Curve arithmetic is implemented and used by one of the following backends:
| Backend | Implementation | Target backends |
| :--- | :--- | :--- |
| `[default]` | Automatic runtime backend selection (either serial or SIMD) | `u32` <br/> `u64` <br/> `avx2` <br/> `avx512` |
| `fiat` | Formally verified field arithmetic from [fiat-crypto] | `fiat_u32` <br/> `fiat_u64` |
| 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 |
To choose a backend other than the `[default]` backend, set the
environment variable:
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.
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.
## 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

View File

@ -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.

View File

@ -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"] }

View File

@ -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<B>(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 <http://www.apache.org/licenses/LICENSE-2.0>)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
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.

View File

@ -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<Self> {
let lit = input.parse()?;
input.parse::<syn::Token![,]>()?;
let attr = input.parse()?;
Ok(Conditional { lit, attr })
}
}
impl syn::parse::Parse for SpecializeArg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::conditional) {
input.parse::<kw::conditional>()?;
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<SpecializeArg, syn::Token![,]>);
impl syn::parse::Parse for SpecializeArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
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<Vec<String>>,
) -> 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<syn::Type>)>,
) -> 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 {
<Self as #trait_ident #outer_ty_generics> ::#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()
}
}

View File

@ -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<const N: u32>(b: u32) -> u32 {
N - b
}
#[unsafe_target_feature("sse2")]
fn function_with_where_clause<T>(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<const N: u32>(self) -> u32 {
self.a - N
}
#[cfg(feature = "dummy")]
fn member_function_with_cfg() {}
}
struct StructWithGenerics<T>
where
T: Copy + core::ops::Sub,
{
a: T,
}
#[unsafe_target_feature("sse2")]
impl<T> StructWithGenerics<T>
where
T: Copy + core::ops::Sub,
{
#[inline]
fn member_function(&self, b: T) -> T::Output {
self.a - b
}
}
struct StructWithGenericsNoWhere<T: Copy + core::ops::Sub> {
a: T,
}
#[unsafe_target_feature("sse2")]
impl<T: Copy + core::ops::Sub> StructWithGenericsNoWhere<T> {
#[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);
}

View File

@ -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::<I, J>(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::<I, J>(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::<I, J>(
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::<I, J>(
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)
}

View File

@ -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};

View File

@ -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

View File

@ -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};

View File

@ -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")]

View File

@ -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;

View File

@ -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 {
(

View File

@ -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 {

View File

@ -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 {

View File

@ -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]);

View File

@ -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 {

View File

@ -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 {

25
src/diagnostics.rs Normal file
View File

@ -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'");

View File

@ -10,12 +10,9 @@
// - Henry de Valence <hdevalence@hdevalence.ca>
#![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;

88
tests/build_tests.sh Executable file
View File

@ -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"