Skip to content

Commit d3c7c0b

Browse files
Merge branch 'main' into main
2 parents b454135 + 01e4976 commit d3c7c0b

File tree

11 files changed

+681
-6
lines changed

11 files changed

+681
-6
lines changed

.github/workflows/kani-metrics.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Kani Metrics Update
2+
3+
on:
4+
schedule:
5+
- cron: '0 0 * * 0' # Run at 00:00 UTC every Sunday
6+
workflow_dispatch:
7+
8+
defaults:
9+
run:
10+
shell: bash
11+
12+
jobs:
13+
update-kani-metrics:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout Repository
18+
uses: actions/checkout@v4
19+
with:
20+
submodules: true
21+
22+
# The Kani metrics collection uses a Python script (kani_std_analysis.py), so make sure Python is installed
23+
- name: Set up Python
24+
uses: actions/setup-python@v4
25+
with:
26+
python-version: '3.x'
27+
28+
- name: Compute Kani Metrics
29+
run: ./scripts/run-kani.sh --run metrics --path ${{github.workspace}}
30+
31+
- name: Create Pull Request
32+
uses: peter-evans/create-pull-request@v7
33+
with:
34+
commit-message: Update Kani metrics
35+
title: 'Update Kani Metrics'
36+
body: |
37+
This is an automated PR to update Kani metrics.
38+
39+
The metrics have been updated by running `./scripts/run-kani.sh --run metrics`.
40+
branch: update-kani-metrics
41+
delete-branch: true
42+
base: main

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ library/target
2626
*.rmeta
2727
*.mir
2828
Cargo.lock
29+
/doc/mdbook-metrics/target
30+
*.png
2931

3032
## Temporary files
3133
*~
@@ -48,8 +50,6 @@ package-lock.json
4850

4951
## GOTO-Transcoder
5052
goto-transcoder
51-
52-
5353
# Added by cargo
5454
#
5555
# already existing elements were commented out

doc/book.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,11 @@ runnable = false
1919

2020
[output.linkcheck]
2121

22+
[preprocessor.metrics]
23+
# Note that the manifest-path is doc/mdbook-metrics, meaning that to build this book, you need to run "mdbook build doc"
24+
# rather than "mdbook build" from inside the doc/ directory.
25+
# We choose the former because our "Build Book" Github workflow runs "mdbook build doc."
26+
command = "cargo run --manifest-path=doc/mdbook-metrics/Cargo.toml"
27+
2228
[rust]
2329
edition = "2021"

doc/mdbook-metrics/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "mdbook-kani-metrics"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
mdbook = { version = "^0.4" }
8+
serde_json = "1.0.132"

doc/mdbook-metrics/src/main.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use mdbook::book::{Book, Chapter};
2+
use mdbook::errors::Error;
3+
use mdbook::preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext};
4+
use mdbook::BookItem;
5+
use std::{env, io, path::Path, process::Command};
6+
7+
fn main() {
8+
let mut args = std::env::args().skip(1);
9+
match args.next().as_deref() {
10+
Some("supports") => {
11+
// Supports all renderers.
12+
return;
13+
}
14+
Some(arg) => {
15+
eprintln!("unknown argument: {arg}");
16+
std::process::exit(1);
17+
}
18+
None => {}
19+
}
20+
21+
if let Err(e) = handle_preprocessing() {
22+
eprintln!("{}", e);
23+
std::process::exit(1);
24+
}
25+
}
26+
27+
// Plot the Kani metrics.
28+
// The mdbook builder reads the postprocessed book from stdout,
29+
// so suppress all Command output to avoid having its output interpreted as part of the book
30+
fn run_kani_metrics_script() -> Result<(), Error> {
31+
// This will be the absolute path to the "doc/" folder
32+
let original_dir = env::current_dir()?;
33+
let tools_path = original_dir.join(Path::new("doc/src/tools"));
34+
35+
let kani_std_analysis_path = Path::new("scripts/kani-std-analysis");
36+
env::set_current_dir(kani_std_analysis_path)?;
37+
38+
Command::new("pip")
39+
.args(&["install", "-r", "requirements.txt"])
40+
.stdout(std::process::Stdio::null())
41+
.stderr(std::process::Stdio::null())
42+
.status()?;
43+
44+
Command::new("python")
45+
.args(&[
46+
"kani_std_analysis.py",
47+
"--plot-only",
48+
"--plot-dir",
49+
&tools_path.to_string_lossy(),
50+
])
51+
.stdout(std::process::Stdio::null())
52+
.stderr(std::process::Stdio::null())
53+
.status()?;
54+
55+
env::set_current_dir(&original_dir)?;
56+
57+
Ok(())
58+
}
59+
60+
struct AddKaniGraphs;
61+
62+
impl Preprocessor for AddKaniGraphs {
63+
fn name(&self) -> &str {
64+
"add-metrics"
65+
}
66+
67+
fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
68+
book.for_each_mut(|item| {
69+
if let BookItem::Chapter(ch) = item {
70+
if ch.name == "Kani" {
71+
add_graphs(ch);
72+
return;
73+
}
74+
}
75+
});
76+
Ok(book)
77+
}
78+
}
79+
80+
fn add_graphs(chapter: &mut Chapter) {
81+
let new_content = r#"
82+
## Kani Metrics
83+
84+
Note that these metrics are for x86-64 architectures.
85+
86+
## `core`
87+
![Unsafe Metrics](core_unsafe_metrics.png)
88+
89+
![Safe Abstractions Metrics](core_safe_abstractions_metrics.png)
90+
91+
![Safe Metrics](core_safe_metrics.png)
92+
"#;
93+
94+
chapter.content.push_str(new_content);
95+
}
96+
97+
pub fn handle_preprocessing() -> Result<(), Error> {
98+
run_kani_metrics_script()?;
99+
let pre = AddKaniGraphs;
100+
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
101+
102+
let processed_book = pre.run(&ctx, book)?;
103+
serde_json::to_writer(io::stdout(), &processed_book)?;
104+
105+
Ok(())
106+
}

doc/src/SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
- [Verification Tools](./tools.md)
1010
- [Kani](./tools/kani.md)
1111

12-
1312
---
1413

1514
- [Challenges](./challenges.md)
@@ -27,3 +26,4 @@
2726
- [12: Safety of `NonZero`](./challenges/0012-nonzero.md)
2827
- [13: Safety of `CStr`](./challenges/0013-cstr.md)
2928
- [14: Safety of Primitive Conversions](./challenges/0014-convert-num.md)
29+
- [15: Contracts and Tests for SIMD Intrinsics](./challenges/0015-intrinsics-simd.md)
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Challenge 15: Contracts and Tests for SIMD Intrinsics
2+
3+
- **Status:** Open
4+
- **Reward:**
5+
- **Solution:**
6+
- **Tracking Issue:** https://github.com/model-checking/verify-rust-std/issues/173
7+
- **Start date:** 2025/02/01
8+
- **End date:** 2025/08/01
9+
10+
-------------------
11+
12+
13+
## Goal
14+
15+
A number of Rust projects rely on the SIMD intrinsics provided by
16+
[core::arch](https://doc.rust-lang.org/beta/core/arch/) for
17+
performance. This includes libraries like [hashbrown]() that are used
18+
in
19+
[HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html),
20+
as well as third-party cryptographic libraries like
21+
[libcrux](https://github.com/cryspen/libcrux) and
22+
[Dalek](https://github.com/dalek-cryptography/curve25519-dalek) that
23+
are used in mainstream software projects.
24+
25+
The goal of this project is to provide testable formal specifications
26+
for the 100 most commonly used intrinsics for x86_64 and aarch64
27+
platforms, chosen specifically to cover all the intrinsics used in
28+
hashbrown and in popular cryptographic libraries.
29+
30+
For each intrinsic, the main goal is to provide contracts in the form
31+
of pre- and post-conditions, and to verify whether these contracts
32+
hold when the intrinsics are executed in Rust. A secondary goal is to
33+
use these contracts as formal specifications of the intrinsics API
34+
when doing proofs of Rust programs.
35+
36+
37+
## Motivation
38+
39+
Rust is the language of choice for new security-critical and
40+
performance-sensitive projects, and consequently a number of new
41+
cryptographic projects use Rust to build their infrastructure and
42+
trusted computing base. However, the SIMD intrinsics in Rust lack
43+
documentation and are easy to misuse, and so even the best Rust programmers
44+
need to wade through Intel or Arm assembly documentation to understand
45+
the functional behavior of these intrinsics.
46+
47+
Separately, when formally verifying cryptographic libraries, each
48+
project needs to define its own semantics for SIMD instructions.
49+
Indeed such SIMD specifications have currently been defined for
50+
cryptographic verification projects in [F*](https://github.com/hacl-star/hacl-star),
51+
[EasyCrypt](https://github.com/jasmin-lang/jasmin), and [HOL Light](https://github.com/awslabs/s2n-bignum/tree/main).
52+
This specification work is both time-consuming and error-prone,
53+
there is also no guarantee of consistency between the instruction
54+
semantics used in these different tools.
55+
56+
Consequently, we believe there is a strong need for a consistent,
57+
formal, testable specification of the SIMD intrinsics that can aid
58+
Rust developers. Furthermore, we believe that this
59+
specification should written in a way that can be used to aid formal
60+
verification of Rust programs using various proof assistants.
61+
62+
## Description
63+
64+
Consider the function [`_mm_blend_epi16`](https://doc.rust-lang.org/beta/core/arch/x86_64/fn._mm_blend_epi16.html)
65+
in [core::arch::x86_64](https://doc.rust-lang.org/beta/core/arch/x86_64/index.html):
66+
67+
```
68+
pub unsafe fn _mm_blend_epi16(
69+
a: __m128i,
70+
b: __m128i,
71+
const IMM8: i32,
72+
) -> __m128i
73+
```
74+
75+
Its description says:
76+
```
77+
Blend packed 16-bit integers from a and b using the mask IMM8.
78+
79+
The mask bits determine the selection. A clear bit selects the corresponding element of a, and a set bit the corresponding element of b.
80+
```
81+
82+
It then points to [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blend_epi16) for the C intrinsic which provides the pseudocode:
83+
```
84+
FOR j := 0 to 7
85+
i := j*16
86+
IF imm8[j]
87+
dst[i+15:i] := b[i+15:i]
88+
ELSE
89+
dst[i+15:i] := a[i+15:i]
90+
FI
91+
ENDFOR
92+
```
93+
94+
We propose to reflect the behavior of the semantics as described in
95+
Intel's documentation directly as pre- and post-conditions in Rust.
96+
97+
```
98+
#[requires(IMM8 >= 0 && IMM8 <= 255)]
99+
#[ensures(|result|
100+
forall (|j| implies(j >= 0 && j < 8,
101+
if get_bit(IMM8,j) then
102+
get_lane(result, j) == get_lane(b,j)
103+
else
104+
get_lane(result, j) == get_lane(a,j))))]
105+
pub unsafe fn _mm_blend_epi16(
106+
a: __m128i,
107+
b: __m128i,
108+
const IMM8: i32,
109+
) -> __m128i
110+
```
111+
112+
This contract can then be used to automatically generate tests
113+
for the intrinsic, which can be put in CI.
114+
115+
As a second layer of assurance, these contracts can be compiled
116+
to some verification framework and proved to be sound against
117+
a hand-written model of the intrinsics functions.
118+
119+
Finally, Rust verification toolchains can also rely on this contract
120+
to model the intrinsics library within their analyses. This would
121+
enable the verification of Rust applications that rely on SIMD intrinsics.
122+
123+
124+
### Assumptions
125+
126+
The contracts we write for the SIMD intrinsics should be well tested
127+
but, in the end, are hand-written based on the documentation
128+
of the intrinsics provided by Intel and ARM. Consequently, the
129+
user must trust that these semantics are correctly written.
130+
131+
When using the contracts within a formal verification project,
132+
the user will, as usual, have to trust that the verification
133+
tool correctly encodes the semantics of Rust and performs
134+
a sound analysis within a clearly documented model.
135+
136+
### Success Criteria
137+
138+
The goal is to annotate >= 100 intrinsics in `core::arch::x86_64` and
139+
`core::arch::aarch64` with contracts, and all these contracts will be
140+
tested comprehensively in Rust. These functions should include all the
141+
intrinsics currently used in standard libraries like
142+
[hashbrown](https://github.com/rust-lang/hashbrown) (the basis
143+
of the Rust [HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html) implementation).
144+
145+
An additional success criterion is to show that these contracts can be
146+
used by verification tools to prove properties about example code that
147+
uses them. Of particular interest is code used in cryptographic
148+
libraries, but even other standalone examples using SIMD intrinsics
149+
would be considered valuable.

0 commit comments

Comments
 (0)