Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/testable-simd-models.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This workflow runs the tests for testable simd models.

name: Testable simd models

on:
workflow_dispatch:
merge_group:
pull_request:
branches: [ main ]
push:
paths:
- '.github/workflows/testable-simd-models.yml'
- 'testable-simd-models/**'

defaults:
run:
shell: bash

jobs:
testable-simd-models:
name: Test testable simd models
runs-on: ubuntu-latest

steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Run tests
working-directory: testable-simd-models
run: cargo test -- --test-threads=1 --nocapture

105 changes: 99 additions & 6 deletions testable-simd-models/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ pub fn phaddw(a: i16x16, b: i16x16) -> i16x16 {

### Modeling defined intrinsics semi-automatically

To model a defined intrinsic, we essentially copy the Rust code of
the intrinsic from `core::arch` and adapt it to use our underlying abstractions. The
changes needed to the code are sometimes scriptable, and indeed most
of our models were generated from a script, but some changes are still
To model a defined intrinsic, we essentially copy the Rust code of the
intrinsic from `core::arch` and adapt it to use our underlying
abstractions. The changes needed to the code are sometimes
scriptable, and indeed most of our models were generated from a script
(see the ANNEX at the bottom of this file), but some changes are still
needed by hand.

For example, let us say the intrinsic we are modeling is
Expand Down Expand Up @@ -176,8 +177,19 @@ pub fn _mm256_bsrli_epi128<const IMM8: i32>(a: __m256i) -> __m256i {
```

Thus, we then go to `core_arch/x86/models/avx2.rs`, and add this implementation.
The only change it requires here is that the `simd_shuffle` macro is a function in our model,
and we discard all the function attributes.
The only changes it requires here are that the `simd_shuffle` macro is a function in our model,
the `ZERO` constant is now a function, and we discard all the function attributes.

The exact diff between the original and edited code for this function is:

```diff
13,14c13,14
< let r: i8x32 = simd_shuffle(
< i8x32::ZERO(),
---
> let r: i8x32 = simd_shuffle!(
> i8x32::ZERO,
```

For other intrinsics, we sometimes need to make more changes. Since our model of the builtin intrinsics
is more precise concerning the type of their arguments compared to their Rust counterparts, we
Expand Down Expand Up @@ -224,3 +236,84 @@ us](https://github.com/rust-lang/stdarch/issues/1822) using a failing
test case generated from the testable model and then fixed by [our
PR](https://github.com/rust-lang/stdarch/pull/1823) in the 2025-06-30
version of `stdarch`.


## ANNEX: Extraction Script

The following Rust program is a simple script that uses the `syn` crate to process an input Rust file
containing SIMD intrinsics into one suitable for the models described in this document. This code
is provided as illustration; for each set of core libraries we wish to model and test, there will
likely be need for a similar (or extended) script to automate the modeling process.

```rust
use syn::*;
use std::fs;
use std::env;

fn extract_model(input_file_path: &str, output_file_path: &str) -> Result<()> {
let source_code = fs::read_to_string(input_file_path).expect("unable to read file");
let mut syntax_tree: File = parse_file(&source_code)?;

syntax_tree.items.retain(|item|
match item {
Item::Use(_) => false,
_ => true
}
);

// Clear attributes from the file's top-level items
for item in &mut syntax_tree.items {
match item {
Item::Const(const_item) => {
const_item.attrs.retain(|attr| attr.path().is_ident("doc"));
},
Item::Fn(item_fn) => {
item_fn.attrs.retain(|attr| attr.path().is_ident("doc"));
item_fn.block.stmts.retain(|stmt|
match stmt {
Stmt::Item(Item::ForeignMod(_)) => false,
_ => true
}
);
for stmt in &mut item_fn.block.stmts {
match stmt {
Stmt::Expr(Expr::Unsafe(u), tok) => *stmt = Stmt::Expr(Expr::Block(
ExprBlock {attrs : Vec::new(), label : None, block : u.block.clone()}), *tok),
_ => ()
}
}
},
Item::Struct(item_struct) => {
item_struct.attrs.clear();
for field in &mut item_struct.fields {
field.attrs.retain(|attr| attr.path().is_ident("doc"));
}
},
Item::Enum(item_enum) => {
item_enum.attrs.clear();
for variant in &mut item_enum.variants {
variant.attrs.retain(|attr| attr.path().is_ident("doc"));
}
},
// Add more cases for other Item types (e.g., Item::Mod, Item::Impl, etc.)
_ => {
// For other item types, if they have an 'attrs' field, clear it.
// This requires more specific matching or a helper trait.
}
}
}

let formatted_string = prettyplease::unparse(&syntax_tree);
fs::write(output_file_path, formatted_string).expect("unable to write file");

Ok(())
}

fn main() -> Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 3 {
println!("usage: modelize <path to input Rust file> <path to output Rust file>")
}
extract_model(&args[1], &args[2])
}
```
10 changes: 0 additions & 10 deletions testable-simd-models/src/abstractions/bitvec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,6 @@ impl<const N: u32> BitVec<N> {
.map(int_from_bit_slice)
.collect()
}

/// Generate a random BitVec.
pub fn rand() -> Self {
use rand::prelude::*;
let random_source: Vec<_> = {
let mut rng = rand::rng();
(0..N).map(|_| rng.random::<bool>()).collect()
};
Self::from_fn(|i| random_source[i as usize].into())
}
}

impl<const N: u32> BitVec<N> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -870,4 +870,4 @@ pub fn vcle_u32(a: uint32x2_t, b: uint32x2_t) -> uint32x2_t {

pub fn vcleq_u32(a: uint32x4_t, b: uint32x4_t) -> uint32x4_t {
simd_le(a, b)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,6 @@ pub fn psravd(a: i32x4, count: i32x4) -> i32x4 {
}

pub fn psravd256(a: i32x8, count: i32x8) -> i32x8 {
dbg!(a, count);
i32x8::from_fn(|i| {
if count[i] > 31 || count[i] < 0 {
if a[i] < 0 {
Expand Down
22 changes: 17 additions & 5 deletions testable-simd-models/src/core_arch/x86/tests/avx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ fn _mm256_movemask_ps() {
let a: BitVec<256> = BitVec::random();
assert_eq!(
super::super::models::avx::_mm256_movemask_ps(a.into()),
unsafe { upstream::_mm256_movemask_ps(a.into()) }
unsafe { upstream::_mm256_movemask_ps(a.into()) },
"Failed with input value: {:?}",
a
);
}
}
Expand All @@ -62,7 +64,9 @@ fn _mm256_movemask_pd() {
let a: BitVec<256> = BitVec::random();
assert_eq!(
super::super::models::avx::_mm256_movemask_pd(a.into()),
unsafe { upstream::_mm256_movemask_pd(a.into()) }
unsafe { upstream::_mm256_movemask_pd(a.into()) },
"Failed with input value: {:?}",
a
);
}
}
Expand All @@ -76,7 +80,10 @@ fn _mm256_testz_si256() {
let b: BitVec<256> = BitVec::random();
assert_eq!(
super::super::models::avx::_mm256_testz_si256(a.into(), b.into()),
unsafe { upstream::_mm256_testz_si256(a.into(), b.into()) }
unsafe { upstream::_mm256_testz_si256(a.into(), b.into()) },
"Failed with input values: {:?}, {:?}",
a,
b
);
}
}
Expand All @@ -90,7 +97,10 @@ fn _mm256_testc_si256() {
let b: BitVec<256> = BitVec::random();
assert_eq!(
super::super::models::avx::_mm256_testc_si256(a.into(), b.into()),
unsafe { upstream::_mm256_testc_si256(a.into(), b.into()) }
unsafe { upstream::_mm256_testc_si256(a.into(), b.into()) },
"Failed with input values: {:?}, {:?}",
a,
b
);
}
}
Expand All @@ -116,7 +126,9 @@ fn _mm256_cvtsi256_si32() {
let a: BitVec<256> = BitVec::random();
assert_eq!(
super::super::models::avx::_mm256_cvtsi256_si32(a.into()),
unsafe { upstream::_mm256_cvtsi256_si32(a.into()) }
unsafe { upstream::_mm256_cvtsi256_si32(a.into()) },
"Failed with input value: {:?}",
a
);
}
}
Expand Down
Loading
Loading