Skip to content

Add dynamic swizzles for 128-bit u8/i8 vectors#265

Closed
LaurenzV wants to merge 1 commit into
mainfrom
laurenz/swizzle
Closed

Add dynamic swizzles for 128-bit u8/i8 vectors#265
LaurenzV wants to merge 1 commit into
mainfrom
laurenz/swizzle

Conversation

@LaurenzV

@LaurenzV LaurenzV commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Swizzles are a whole can of worm and I do not intend to figure out how we should implement this generically right now. 😄 I think in the long term, we will mostly want to mirror portable_simd where we have swizzle for static swizzles (unfortunately this is currently a bit hard to implement due to current limitations of const generics) and swizzle_dyn for dynamic ones.

In any case, for now I just want to have a simple method we can use in vello to convert RGBA vectors into BGRA during packing. We know the indices statically so that would be the best, but as mentioned this one's harder to implement so for now I decided to just go with dynamic swizzles instead. In order to keep things simple, I'm limiting this implementation

  1. Is only done for 128-bit vectors, since doing it for vectors that exceed the native bit-width of the level is probably a bit tricky.
  2. Only for u8/i8, since there's a discussion to be had on how to handle indexing for larger types.

So despite the complexity of swizzles, I hope adding this is non-controversial. The only controversial part is probably how to deal with OOB indices, but in the interest of getting the best performance I decided to not explicitly zero out invalid ones on x86, instead documenting that the result for those is unspecified.

@LaurenzV LaurenzV marked this pull request as draft July 1, 2026 18:36
@LaurenzV LaurenzV marked this pull request as ready for review July 1, 2026 18:42
@LaurenzV LaurenzV requested a review from Shnatsel July 1, 2026 18:42

@Shnatsel Shnatsel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The semver hazard is a concern. The rest is less critical.

But I am excited to see this, because this gives us a real edge over std::simd which requires -Z build-std for dynamic swizzles to perform well: https://doc.rust-lang.org/stable/std/simd/struct.Simd.html#method.swizzle_dyn

And we also can get better performance by not adhering to the "out of bounds is 0" rule, making it opt-in via a bitmask for the users that need it instead.

Comment on lines +1168 to +1174
fn swizzle_dyn_i8x16(self, a: i8x16<Self>, idxs: u8x16<Self>) -> i8x16<Self> {
let mut dest = [Default::default(); 16];
for (i, idx) in idxs.val.0.iter().copied().enumerate() {
dest[i] = a.val.0.get(idx as usize).copied().unwrap_or(0);
}
dest.simd_into(self)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fallback implementations are usually manually unrolled so that platforms we don't support via intrinsics would have a better shot at autovectorization, see e.g.

fn select_i8x16(self, a: mask8x16<Self>, b: i8x16<Self>, c: i8x16<Self>) -> i8x16<Self> {
[
if a.val.0[0usize] != 0 {
b[0usize]
} else {
c[0usize]
},
if a.val.0[1usize] != 0 {
b[1usize]
} else {
c[1usize]
},
if a.val.0[2usize] != 0 {
b[2usize]
} else {
c[2usize]
},
if a.val.0[3usize] != 0 {
b[3usize]
} else {
c[3usize]
},
if a.val.0[4usize] != 0 {
b[4usize]
} else {
c[4usize]
},
if a.val.0[5usize] != 0 {
b[5usize]
} else {
c[5usize]
},
if a.val.0[6usize] != 0 {
b[6usize]
} else {
c[6usize]
},
if a.val.0[7usize] != 0 {
b[7usize]
} else {
c[7usize]
},
if a.val.0[8usize] != 0 {
b[8usize]
} else {
c[8usize]
},
if a.val.0[9usize] != 0 {
b[9usize]
} else {
c[9usize]
},
if a.val.0[10usize] != 0 {
b[10usize]
} else {
c[10usize]
},
if a.val.0[11usize] != 0 {
b[11usize]
} else {
c[11usize]
},
if a.val.0[12usize] != 0 {
b[12usize]
} else {
c[12usize]
},
if a.val.0[13usize] != 0 {
b[13usize]
} else {
c[13usize]
},
if a.val.0[14usize] != 0 {
b[14usize]
} else {
c[14usize]
},
if a.val.0[15usize] != 0 {
b[15usize]
} else {
c[15usize]
},
]
.simd_into(self)
}
#[inline(always)]
fn min_i8x16(self, a: i8x16<Self>, b: i8x16<Self>) -> i8x16<Self> {
[
i8::min(a[0usize], b[0usize]),
i8::min(a[1usize], b[1usize]),
i8::min(a[2usize], b[2usize]),
i8::min(a[3usize], b[3usize]),
i8::min(a[4usize], b[4usize]),
i8::min(a[5usize], b[5usize]),
i8::min(a[6usize], b[6usize]),
i8::min(a[7usize], b[7usize]),
i8::min(a[8usize], b[8usize]),
i8::min(a[9usize], b[9usize]),
i8::min(a[10usize], b[10usize]),
i8::min(a[11usize], b[11usize]),
i8::min(a[12usize], b[12usize]),
i8::min(a[13usize], b[13usize]),
i8::min(a[14usize], b[14usize]),
i8::min(a[15usize], b[15usize]),
]
.simd_into(self)

I'd prefer to have this fully unrolled style applied here as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Separately, instead of get().unwrap_or(0) I would prefer get((idx % 16) as usize ) which should be easier for the compiler to autovectorize and matches the current x86 behavior. But that's ultimately up to you, I'm not going to block this PR on that.

Comment on lines +1779 to +1786
fn swizzle_dyn_u8x16(self, a: u8x16<Self>, idxs: u8x16<Self>) -> u8x16<Self> {
let mut dest = [Default::default(); 16];
for (i, idx) in idxs.val.0.iter().copied().enumerate() {
dest[i] = a.val.0.get(idx as usize).copied().unwrap_or(0);
}
dest.simd_into(self)
}
#[inline(always)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same here

Comment on lines +159 to +163
/// Dynamic swizzle for byte vectors.
pub trait SimdSwizzleDyn<S: Simd>: SimdBase<S> + Seal {
fn swizzle_dyn(self, idxs: crate::u8x16<S>) -> Self;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The way this trait is defined locks us into u8x16 vectors for indices. Making it more general later is a semver-breaking change.

Something like SimdSwizzleDyn<Idxs, S> or an associated type Indices would be more future-proof, implemented today with u8x16<S> only for some types.

Indices sounds like it would be useful on SimdBase but I guess we can move that to SimdBade later once the implementation matures.

@Shnatsel

Shnatsel commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What we could easily provide for all vector sizes is swizzling within 128-bit blocks, which maps very naturally to all widths.

@LaurenzV

LaurenzV commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #266.

@LaurenzV LaurenzV closed this Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants