Why do the simd-crates use a tupel rather than an array?


#1

I just had a look into the crates simd and simdty and wondered why they had chosen to use tupels like that:

struct u32x4(u32, u32, u32, u32);

Wouldn’t a newtype or type alias like

struct u32x4([u32; 4]);

or

type u32x4 = [u32; 4];

be more convenient? You could use indexing, transmuting, iterating, slicing, whatever. And - AFAIK - the internal representation is clearly defined (is it?). Or are there simd types with mixed content?

Edit: as @sfackler pointed out, a type alias wouldn’t allow alignment rules solely for the usage as a simd type.


#2

There is a (long) thread about SIMD going on over at the internals forum right now https://internals.rust-lang.org/t/getting-explicit-simd-on-stable-rust/4380


#3

That’s were I came from :wink:
But I thought this question did’t fit into the discussion (yet).


#4

The tuple formulation allows you to retrieve individual elements via tuple indexing, which does not have runtime bound checks:

let x = simd_thing.2;

vs

let x = simd_thing.0[2];

Typdefs are just aliases, not new types, so that wouldn’t work at all.


#5

I assume indexing an array (as opposed to a slice) does not have runtime bounds checks either when using constant indices? The compiler certainly has all the information it needs not to generate a compile-time check.


#6

This is true, a simple program like this doesn’t have bounds checks (doesn’t need optimizations)

fn main() {
    let x = [1, 2, 3, 4];
    let y = x[1];
}

I think the bounds checks are removed in a Mir pass.


#7

But nothing’s going to stop you from fat-fingering foo.0[4].


#8
warning: this expression will panic at run-time
  --> src/lib.rs:11:20
   |
11 |     println!("{}", foo.0[4]);
   |                    ^^^^^^^^ index out of bounds: the len is 4 but the index is 4

#9

This shouldn’t even be a warning; it should be an error.


#10

@eddyb addressed that earlier today

As always, the issue is more nuanced - see https://github.com/rust-lang/rfcs/pull/1229 for more details on this stance.

Half of it is backwards compatibility (since these warnings do not
exist in a vacuum, but rather arise from optimizations), and the other
half is that code being objectively bad is not a certainty without
potentially unbounded context.


#11

Could you elaborate on this? I know type aliases are just syntactic sugar, but what prevents us from defining simd-operations on [u32; 4]?

Not to be misunderstood: I currently consider the newtype to be the cleaner solution. Overloaded operators wouldn’t collide with other use cases (like: is [1, 2, 3] + [4, 5, 6] an addition or a concatenation?).


#12

SIMD types have different alignment requirements than normal arrays - loads that aren’t aligned to 16 bytes will fault. Calling conventions also differ I believe.


#13

For a more convenient usage, Index could be implemented:

use std::ops::Index;

#[allow(non_camel_case_types)]
struct u32x4([u32; 4]);

impl Index<usize> for u32x4 {
    type Output = u32;
    
    fn index(&self, index: usize) -> &u32 { &self.0[index] }    
}

fn main() {
    let simd = u32x4([1, 2, 3, 4]);
    
    println!("{:?}", simd[0]);
    println!("{:?}", simd.0[0]);
    println!("{:?}", simd[4]);
    println!("{:?}", simd.0[4]);
}

(I doubt usize is the best choice)

(OT: How do I enable syntax highlighting? I didn’t find a documentation -> fixed)


#14

Thanks. The alignment requirements alone would rule out the type alias as an option.


#15

You can use this:

```rust
// Rust code goes here.
```

#16

Thanks and fixed!