makepad/libs/relax/compiler/tests/vectorize_maps.rs
Admin 27a0a4d3aa makepad run / Relax: loop vectorisation and bounds-check folding; item checks under other errors
- codegen: bounds checks after a counter step fold, min-of-lengths bounds both;
  a stream vectoriser for u8/f32/i32 loops (zip, offset index, pointer/null/i<n
  exits); unrolled f32 lane accumulators become vector accumulators
- check: one built-in attribute safety table; E0392/E0207/E0740 judged when the
  item has other errors; Copy impl field bounds must hold

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:17:21 +02:00

333 lines
12 KiB
Rust

// Element maps that O3 vectorises (codegen loops.rs vectorize_stream_maps): f32 and 32-bit
// integer loops over iterator zips and raw-pointer streams with a loop-invariant scalar, every
// length 0..=40 at several start offsets (unaligned vector loads, remainders of every size), and
// destination streams that overlap the source streams at every distance from -9 to 9 elements
// (the runtime distance check must keep the scalar order); each result is compared with a
// reference whose per-element step is a call (never widened); called often enough to tier up.
use std::hint::black_box;
#[inline(never)]
fn mul_add(y: &mut [f32], x: &[f32], k: f32) {
for (o, v) in y.iter_mut().zip(x.iter()) {
*o += *v * k;
}
}
#[inline(never)]
fn mul_add_ref(y: &mut [f32], x: &[f32], k: f32) {
for i in 0..y.len().min(x.len()) {
let t = black_box(x[i] * k);
y[i] = y[i] + t;
}
}
#[inline(never)]
fn scale_neg(y: &mut [f32], x: &[f32], k: f32) {
for (o, v) in y.iter_mut().zip(x.iter()) {
*o = -(*v) * k - 1.5;
}
}
#[inline(never)]
fn scale_neg_ref(y: &mut [f32], x: &[f32], k: f32) {
for i in 0..y.len().min(x.len()) {
y[i] = black_box(-x[i] * k) - 1.5;
}
}
#[inline(never)]
fn mix_i32(a: &mut [i32], b: &[i32], k: i32) {
for (o, v) in a.iter_mut().zip(b.iter()) {
*o = o.wrapping_add(v.wrapping_mul(k)) ^ 0x55;
}
}
#[inline(never)]
fn mix_i32_ref(a: &mut [i32], b: &[i32], k: i32) {
for i in 0..a.len().min(b.len()) {
a[i] = a[i].wrapping_add(black_box(b[i].wrapping_mul(k))) ^ 0x55;
}
}
/// a raw-pointer stream: dst[i] = src[i] * 2 + 1 for n elements, the two streams may overlap
#[inline(never)]
unsafe fn stream(dst: *mut f32, src: *const f32, n: usize) {
let mut d = dst;
let mut s = src;
let end = src.add(n);
while s != end {
*d = *s * 2.0 + 1.0;
d = d.add(1);
s = s.add(1);
}
}
#[inline(never)]
unsafe fn stream_ref(dst: *mut f32, src: *const f32, n: usize) {
for i in 0..n {
let v = black_box(*src.add(i) * 2.0);
*dst.add(i) = v + 1.0;
}
}
/// index loops: `a[i] += b[i] * c[i]`, displaced reads, and the in-place shifted forms
#[inline(never)]
fn idx_mad(a: &mut [f32], b: &[f32], c: &[f32]) {
let n = a.len().min(b.len()).min(c.len());
let (a, b, c) = (&mut a[..n], &b[..n], &c[..n]);
for i in 0..n {
a[i] += b[i] * c[i];
}
}
#[inline(never)]
fn idx_mad_ref(a: &mut [f32], b: &[f32], c: &[f32]) {
let n = a.len().min(b.len()).min(c.len());
for i in 0..n {
let t = black_box(b[i] * c[i]);
a[i] = a[i] + t;
}
}
#[inline(never)]
fn idx_disp(a: &mut [f32], b: &[f32], c: &[f32], k: f32) {
let n = a.len().min(b.len()).min(c.len());
for i in 1..n.saturating_sub(1) {
a[i] = b[i - 1] + c[i + 1] * k;
}
}
#[inline(never)]
fn idx_disp_ref(a: &mut [f32], b: &[f32], c: &[f32], k: f32) {
let n = a.len().min(b.len()).min(c.len());
for i in 1..n.saturating_sub(1) {
a[i] = b[i - 1] + black_box(c[i + 1] * k);
}
}
/// `v[i] = v[i + 1] * 2` reads ahead of its writes (vector-safe); `v[i + 1] = v[i] * 2 + 1` feeds
/// each result into the next element (must stay in order)
#[inline(never)]
fn shift_down(v: &mut [i32]) {
for i in 0..v.len().saturating_sub(1) {
v[i] = v[i + 1].wrapping_mul(3);
}
}
#[inline(never)]
fn shift_down_ref(v: &mut [i32]) {
for i in 0..v.len().saturating_sub(1) {
v[i] = black_box(v[i + 1].wrapping_mul(3));
}
}
#[inline(never)]
fn carry_up(v: &mut [i32]) {
for i in 0..v.len().saturating_sub(1) {
v[i + 1] = v[i].wrapping_mul(3) ^ 1;
}
}
#[inline(never)]
fn carry_up_ref(v: &mut [i32]) {
for i in 0..v.len().saturating_sub(1) {
v[i + 1] = black_box(v[i].wrapping_mul(3)) ^ 1;
}
}
/// the PNG "up" defilter: three zipped byte streams
#[inline(never)]
fn up_defilter(prev_row: &[u8], raw: &[u8], current: &mut [u8]) {
for ((filt, recon), up) in raw.iter().zip(current).zip(prev_row) {
*recon = (*filt).wrapping_add(*up)
}
}
#[inline(never)]
fn up_defilter_ref(prev_row: &[u8], raw: &[u8], current: &mut [u8]) {
for i in 0..prev_row.len().min(raw.len()).min(current.len()) {
current[i] = black_box(raw[i].wrapping_add(prev_row[i]));
}
}
/// eight lane accumulators over chunks (the array version of a SIMD dot product)
#[inline(never)]
fn dot_lanes(a: &[f32], b: &[f32]) -> f32 {
let mut acc = [0.0f32; 8];
for (x, y) in a.chunks_exact(8).zip(b.chunks_exact(8)) {
for l in 0..8 {
acc[l] += x[l] * y[l];
}
}
((acc[0] + acc[4]) + (acc[1] + acc[5])) + ((acc[2] + acc[6]) + (acc[3] + acc[7]))
}
#[inline(never)]
fn dot_lanes_ref(a: &[f32], b: &[f32]) -> f32 {
let mut acc = [0.0f32; 8];
let n = a.len().min(b.len()) / 8 * 8;
let mut i = 0;
while i < n {
for l in 0..8 {
let t = black_box(a[i + l] * b[i + l]);
acc[l] = acc[l] + t;
}
i += 8;
}
((acc[0] + acc[4]) + (acc[1] + acc[5])) + ((acc[2] + acc[6]) + (acc[3] + acc[7]))
}
/// lane accumulators with an early exit and a subtract
#[inline(never)]
fn diff_lanes(a: &[f32], b: &[f32], limit: f32) -> (f32, f32) {
let mut acc = [1.0f32; 4];
let mut rounds = 0.0f32;
for (x, y) in a.chunks_exact(4).zip(b.chunks_exact(4)) {
for l in 0..4 {
acc[l] -= x[l] * y[l];
}
rounds += 1.0;
if acc[0] < limit {
break;
}
}
(acc[0] + acc[1] + acc[2] + acc[3], rounds)
}
#[inline(never)]
fn diff_lanes_ref(a: &[f32], b: &[f32], limit: f32) -> (f32, f32) {
let mut acc = [1.0f32; 4];
let mut rounds = 0.0f32;
let n = a.len().min(b.len()) / 4 * 4;
let mut i = 0;
while i < n {
for l in 0..4 {
let t = black_box(a[i + l] * b[i + l]);
acc[l] = acc[l] - t;
}
rounds += 1.0;
i += 4;
if acc[0] < limit {
break;
}
}
(acc[0] + acc[1] + acc[2] + acc[3], rounds)
}
/// the index bounded by `min` of two lengths: no slicing first
#[inline(never)]
fn idx_sq(out: &mut [f32], a: &[f32]) {
for i in 0..out.len().min(a.len()) {
out[i] = a[i] * a[i];
}
}
#[inline(never)]
fn idx_sq_ref(out: &mut [f32], a: &[f32]) {
for i in 0..out.len().min(a.len()) {
out[i] = black_box(a[i] * a[i]);
}
}
fn bits(v: &[f32]) -> Vec<u32> {
v.iter().map(|x| x.to_bits()).collect()
}
#[test]
fn vectorized_maps() {
let src: Vec<f32> = (0..64).map(|i| (i as f32) * 0.37 - 5.0).collect();
let isrc: Vec<i32> = (0..64).map(|i| (i as i32).wrapping_mul(0x9E37_79B1u32 as i32)).collect();
let mut fails = 0;
for rep in 0..4000u32 {
let k = 0.25 + (rep % 7) as f32;
let ki = 3 + (rep % 5) as i32;
for n in 0..41usize {
for lo in 0..3usize {
let hi = (lo + n).min(64);
let mut y1 = src.clone();
let mut y2 = src.clone();
mul_add(&mut y1[lo..hi], &src[lo..hi], black_box(k));
mul_add_ref(&mut y2[lo..hi], &src[lo..hi], k);
fails += (bits(&y1) != bits(&y2)) as u32;
let mut y1 = vec![0.0f32; 64];
let mut y2 = vec![0.0f32; 64];
scale_neg(&mut y1[lo..hi], &src[lo..hi], black_box(k));
scale_neg_ref(&mut y2[lo..hi], &src[lo..hi], k);
fails += (bits(&y1) != bits(&y2)) as u32;
let mut a1 = isrc.clone();
let mut a2 = isrc.clone();
mix_i32(&mut a1[lo..hi], &isrc[lo..hi], black_box(ki));
mix_i32_ref(&mut a2[lo..hi], &isrc[lo..hi], ki);
fails += (a1 != a2) as u32;
}
for lo in 0..3usize {
let mut y1 = vec![3.0f32; 64];
let mut y2 = vec![3.0f32; 64];
idx_sq(&mut y1[lo..], &src[..n]);
idx_sq_ref(&mut y2[lo..], &src[..n]);
fails += (bits(&y1) != bits(&y2)) as u32;
let mut y1 = vec![3.0f32; 64];
let mut y2 = vec![3.0f32; 64];
idx_sq(&mut y1[..n], &src[lo..]);
idx_sq_ref(&mut y2[..n], &src[lo..]);
fails += (bits(&y1) != bits(&y2)) as u32;
}
for lo in 0..3usize {
let hi = (lo + n).min(64);
let mut y1 = src.clone();
let mut y2 = src.clone();
idx_mad(&mut y1[lo..hi], &src[lo..hi], &src[3..]);
idx_mad_ref(&mut y2[lo..hi], &src[lo..hi], &src[3..]);
fails += (bits(&y1) != bits(&y2)) as u32;
let mut y1 = vec![1.0f32; 64];
let mut y2 = vec![1.0f32; 64];
idx_disp(&mut y1[lo..hi], &src[lo..hi], &src[1 + lo..], black_box(k));
idx_disp_ref(&mut y2[lo..hi], &src[lo..hi], &src[1 + lo..], k);
fails += (bits(&y1) != bits(&y2)) as u32;
let mut a1 = isrc.clone();
let mut a2 = isrc.clone();
shift_down(&mut a1[lo..hi]);
shift_down_ref(&mut a2[lo..hi]);
fails += (a1 != a2) as u32;
let mut a1 = isrc.clone();
let mut a2 = isrc.clone();
carry_up(&mut a1[lo..hi]);
carry_up_ref(&mut a2[lo..hi]);
fails += (a1 != a2) as u32;
}
{
let mut fa: Vec<f32> = (0..64).map(|i| ((i * 37 + rep as usize) % 101) as f32 * 0.31 - 12.0).collect();
let fb: Vec<f32> = (0..64).map(|i| ((i * 11 + 5) % 89) as f32 * 0.17 - 7.0).collect();
if rep % 7 == 0 {
fa[(rep as usize) % 64] = f32::NAN;
}
if rep % 11 == 0 {
fa[(rep as usize * 3) % 64] = f32::INFINITY;
}
fails += (dot_lanes(&fa[..n], &fb).to_bits() != dot_lanes_ref(&fa[..n], &fb).to_bits()) as u32;
let lim = -((rep % 50) as f32) * 3.0;
let (s1, r1) = diff_lanes(&fa[..n], &fb, lim);
let (s2, r2) = diff_lanes_ref(&fa[..n], &fb, lim);
fails += (s1.to_bits() != s2.to_bits() || r1 != r2) as u32;
}
{
let a: Vec<u8> = (0..64u32).map(|i| (i * 37 + rep) as u8).collect();
let b: Vec<u8> = (0..64u32).map(|i| (i * 11 + 5) as u8).collect();
let mut o1 = vec![7u8; 64];
let mut o2 = vec![7u8; 64];
up_defilter(&a[..n], &b[1..n.max(1)], &mut o1[2..]);
up_defilter_ref(&a[..n], &b[1..n.max(1)], &mut o2[2..]);
fails += (o1 != o2) as u32;
}
// overlapping streams inside one buffer
for shift in -9i32..=9 {
let base = 12usize;
let mut b1 = src.clone();
let mut b2 = src.clone();
unsafe {
let s1 = b1.as_ptr().add(base);
let d1 = b1.as_mut_ptr().add((base as i32 + shift) as usize);
stream(d1, s1, n);
let s2 = b2.as_ptr().add(base);
let d2 = b2.as_mut_ptr().add((base as i32 + shift) as usize);
// the reference is the same loop in element order
for i in 0..n {
let v = black_box(*s2.add(i) * 2.0);
*d2.add(i) = v + 1.0;
}
}
fails += (bits(&b1) != bits(&b2)) as u32;
}
let mut d = vec![0.0f32; 64];
let mut e = vec![0.0f32; 64];
unsafe {
stream(d.as_mut_ptr(), src.as_ptr(), n);
stream_ref(e.as_mut_ptr(), src.as_ptr(), n);
}
fails += (bits(&d) != bits(&e)) as u32;
}
}
println!("| fails {}", fails);
assert_eq!(fails, 0);
}