Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runtime backend autodetection #523

Merged
merged 11 commits into from
Jun 11, 2023
33 changes: 14 additions & 19 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,19 @@ jobs:
- run: cargo build --target thumbv7em-none-eabi --release
- run: cargo build --target thumbv7em-none-eabi --release --features serde

build-simd-nightly:
name: Build simd backend (nightly)
test-simd-native:
name: Test simd backend (native)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
# Build with AVX2 features, then with AVX512 features
- env:
RUSTFLAGS: '--cfg curve25519_dalek_backend="simd" -C target_feature=+avx2'
run: cargo build --target x86_64-unknown-linux-gnu
- env:
RUSTFLAGS: '--cfg curve25519_dalek_backend="simd" -C target_feature=+avx512ifma'
run: cargo build --target x86_64-unknown-linux-gnu
# This will:
# 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.
RUSTFLAGS: '-C target_cpu=native'
run: cargo test --features simd --target x86_64-unknown-linux-gnu

test-simd-avx2:
name: Test simd backend (avx2)
Expand All @@ -76,8 +76,10 @@ jobs:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- env:
RUSTFLAGS: '--cfg curve25519_dalek_backend="simd" -C target_feature=+avx2'
run: cargo test --target x86_64-unknown-linux-gnu
# This will run AVX2-specific tests and run all of the normal tests
# with the AVX2 backend, even if the runner supports AVX512.
RUSTFLAGS: '-C target_feature=+avx2'
run: cargo test --no-default-features --features alloc,precomputed-tables,zeroize,simd_avx2 --target x86_64-unknown-linux-gnu

build-docs:
name: Build docs
Expand Down Expand Up @@ -131,12 +133,7 @@ jobs:
- uses: dtolnay/rust-toolchain@nightly
with:
components: clippy
- env:
RUSTFLAGS: '--cfg curve25519_dalek_backend="simd" -C target_feature=+avx2'
run: cargo clippy --target x86_64-unknown-linux-gnu
- env:
RUSTFLAGS: '--cfg curve25519_dalek_backend="simd" -C target_feature=+avx512ifma'
run: cargo clippy --target x86_64-unknown-linux-gnu
- run: cargo clippy --target x86_64-unknown-linux-gnu

rustfmt:
name: Check formatting
Expand All @@ -162,9 +159,7 @@ jobs:
- uses: dtolnay/[email protected]
- run: cargo build --no-default-features --features serde
# Also make sure the AVX2 build works
- env:
RUSTFLAGS: '--cfg curve25519_dalek_backend="simd" -C target_feature=+avx2'
run: cargo build --target x86_64-unknown-linux-gnu
- run: cargo build --target x86_64-unknown-linux-gnu

bench:
name: Check that benchmarks compile
Expand Down
17 changes: 15 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ rustdoc-args = [
"--html-in-header", "docs/assets/rustdoc-include-katex-header.html",
"--cfg", "docsrs",
]
rustc-args = ["--cfg", "curve25519_dalek_backend=\"simd\""]
features = ["serde", "rand_core", "digest", "legacy_compatibility"]

[dev-dependencies]
Expand All @@ -54,15 +53,29 @@ 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"

[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies]
fiat-crypto = "0.1.19"

[features]
default = ["alloc", "precomputed-tables", "zeroize"]
default = ["alloc", "precomputed-tables", "zeroize", "simd"]
tarcieri marked this conversation as resolved.
Show resolved Hide resolved
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"]

[profile.dev]
opt-level = 2
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
FEATURES := serde rand_core digest legacy_compatibility

export RUSTFLAGS := --cfg=curve25519_dalek_backend="simd"
export RUSTDOCFLAGS := \
--cfg docsrs \
--html-in-header docs/assets/rustdoc-include-katex-header.html
Expand Down
50 changes: 25 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ 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. |
Comment on lines +56 to +58
Copy link
Contributor

@pinkforest pinkforest May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this:

Suggested change
| `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. |
| `forbid_avx2` | | Disallows the AVX2 SIMD backend to be used, if available. |
| `forbid_avx512` | | Disallows the AVX512 SIMD backend to be used, if available. |
| `forbid_simd` | | Disallows every SIMD backend to be used, if available. |

Optionally could make the forbid as cfg as is done with the rest of the configuration.

| `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. |
Expand Down Expand Up @@ -95,30 +98,26 @@ See tracking issue: [curve25519-dalek/issues/521](https://github.com/dalek-crypt

Curve arithmetic is implemented and used by selecting one of the following backends:

| Backend | Implementation | Target backends |
| :--- | :--- | :--- |
| `[default]` | Serial formulas | `u32` <br/> `u64` |
| `simd` | [Parallel][parallel_doc], using Advanced Vector Extensions | `avx2` <br/> `avx512ifma` |
| `fiat` | Formally verified field arithmetic from [fiat-crypto] | `fiat_u32` <br/> `fiat_u64` |
| 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` |

To choose a backend other than the `[default]` serial backend, set the
To choose a backend other than the `[default]` backend, set the
environment variable:
```sh
RUSTFLAGS='--cfg curve25519_dalek_backend="BACKEND"'
```
where `BACKEND` is `simd` or `fiat`. Equivalently, you can write to
where `BACKEND` is `fiat`. Equivalently, you can write to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, if fiat is the only backend we have, perhaps we should reconsider how gating works. But I think that's something we can do in a followup PR.

`~/.cargo/config`:
```toml
[build]
rustflags = ['--cfg=curve25519_dalek_backend="BACKEND"']
```
More info [here](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags).

The `simd` backend requires extra configuration. See [the SIMD
section](#simd-target-backends).

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 the `u64` backend to
function.

## Word size for serial backends
Expand All @@ -137,7 +136,7 @@ RUSTFLAGS='--cfg curve25519_dalek_bits="SIZE"'
where `SIZE` is `32` or `64`. As in the above section, this can also be placed
in `~/.cargo/config`.

**NOTE:** The `simd` backend CANNOT be used with word size 32.
**NOTE:** Using a word size of 32 will automatically disable SIMD support.

### Cross-compilation

Expand All @@ -152,18 +151,19 @@ $ cargo build --target i686-unknown-linux-gnu

## SIMD target backends

Target backend selection within `simd` must be done manually by setting the
`RUSTFLAGS` environment variable to one of the below options:
The SIMD target backend selection is done automatically at runtime depending
on the available CPU features, provided the appropriate feature flag is enabled.

| CPU feature | `RUSTFLAGS` | Requires nightly? |
| :--- | :--- | :--- |
| avx2 | `-C target_feature=+avx2` | no |
| avx512ifma | `-C target_feature=+avx512ifma` | yes |
Comment on lines -158 to -161
Copy link
Contributor

@tarcieri tarcieri Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed all of this documentation is removed, but the newly introduced crate features aren't equivalent and perhaps this should still be documented.

Namely these flags bypass autodetection and force the use of SIMD features.

Copy link
Contributor

@pinkforest pinkforest Jun 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I can take care of documenting that - just need to re-organise a bit to make room for the vendored dep first
Also need to do a lot more testing now

You can also specify an appropriate `-C target_feature` to build a binary
which assumes the required SIMD instructions are always available.

Or you can use `-C target_cpu=native` if you don't know what to set.
| Backend | Feature flag | `RUSTFLAGS` | Requires nightly? |
| :--- | :--- | :--- | :--- |
| avx2 | `simd_avx2` | `-C target_feature=+avx2` | no |
| avx512 | `simd_avx512` | `-C target_feature=+avx512ifma,+avx512vl` | yes |

The AVX512 backend requires Rust nightly. If enabled and when compiled on a non-nightly
compiler it will fall back to using the AVX2 backend.
The AVX512 backend requires Rust nightly. When compiled on a non-nightly
compiler it will always be disabled.

# Documentation

Expand Down Expand Up @@ -243,16 +243,16 @@ The implementation is memory-safe, and contains no significant
`unsafe` code. The SIMD backend uses `unsafe` internally to call SIMD
intrinsics. These are marked `unsafe` only because invoking them on an
inappropriate CPU would cause `SIGILL`, but the entire backend is only
compiled with appropriate `target_feature`s, so this cannot occur.
invoked when the appropriate CPU features are detected at runtime, or
when the whole program is compiled with the appropriate `target_feature`s.

# Performance

Benchmarks are run using [`criterion.rs`][criterion]:

```sh
cargo bench --features "rand_core"
# Uses avx2 or ifma only if compiled for an appropriate target.
export RUSTFLAGS='--cfg curve25519_dalek_backend="simd" -C target_cpu=native'
export RUSTFLAGS='-C target_cpu=native'
cargo +nightly bench --features "rand_core"
```

Expand Down Expand Up @@ -294,7 +294,7 @@ universe's beauty, but also his deep hatred of the Daleks. Rusty destroys the
other Daleks and departs the ship, determined to track down and bring an end
to the Dalek race.*

`curve25519-dalek` is authored by Isis Agora Lovecruft and Henry de Valence.
`curve25519-dalek` is authored by Isis Agora Lovecruft and Henry de Valence.

Portions of this library were originally a port of [Adam Langley's
Golang ed25519 library](https://github.com/agl/ed25519), which was in
Expand Down
7 changes: 7 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ fn main() {
{
println!("cargo:rustc-cfg=nightly");
}

let rustc_version = rustc_version::version().expect("failed to detect rustc version");
if rustc_version.major == 1 && rustc_version.minor <= 64 {
// Old versions of Rust complain when you have an `unsafe fn` and you use `unsafe {}` inside,
// 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");
}
}

// Deterministic cfg(curve25519_dalek_bits) when this is not explicitly set.
Expand Down
Loading