Skip to content

Commit

Permalink
Ban non-array SIMD
Browse files Browse the repository at this point in the history
  • Loading branch information
scottmcm committed Sep 10, 2024
1 parent 712463d commit d2309c2
Show file tree
Hide file tree
Showing 111 changed files with 826 additions and 1,113 deletions.
4 changes: 2 additions & 2 deletions compiler/rustc_error_codes/src/error_codes/E0074.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This will cause an error:
#![feature(repr_simd)]
#[repr(simd)]
struct Bad<T>(T, T, T, T);
struct Bad<T>([T; 4]);
```

This will not:
Expand All @@ -20,5 +20,5 @@ This will not:
#![feature(repr_simd)]
#[repr(simd)]
struct Good(u32, u32, u32, u32);
struct Good([u32; 4]);
```
18 changes: 12 additions & 6 deletions compiler/rustc_error_codes/src/error_codes/E0075.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
A `#[simd]` attribute was applied to an empty tuple struct.
A `#[simd]` attribute was applied to an empty or multi-field struct.

Erroneous code example:
Erroneous code examples:

```compile_fail,E0075
#![feature(repr_simd)]
Expand All @@ -9,15 +9,21 @@ Erroneous code example:
struct Bad; // error!
```

The `#[simd]` attribute can only be applied to non empty tuple structs, because
it doesn't make sense to try to use SIMD operations when there are no values to
operate on.
```compile_fail,E0075
#![feature(repr_simd)]
#[repr(simd)]
struct Bad([u32; 1], [u32; 1]); // error!
```

The `#[simd]` attribute can only be applied to a single-field struct, because
the one field must be the array of values in the vector.

Fixed example:

```
#![feature(repr_simd)]
#[repr(simd)]
struct Good(u32); // ok!
struct Good([u32; 2]); // ok!
```
10 changes: 5 additions & 5 deletions compiler/rustc_error_codes/src/error_codes/E0076.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
All types in a tuple struct aren't the same when using the `#[simd]`
The type of the field in a tuple struct isn't an array when using the `#[simd]`
attribute.

Erroneous code example:
Expand All @@ -7,18 +7,18 @@ Erroneous code example:
#![feature(repr_simd)]
#[repr(simd)]
struct Bad(u16, u32, u32 u32); // error!
struct Bad(u16); // error!
```

When using the `#[simd]` attribute to automatically use SIMD operations in tuple
struct, the types in the struct must all be of the same type, or the compiler
will trigger this error.
structs, if you want a single-lane vector then the field must be a 1-element
array, or the compiler will trigger this error.

Fixed example:

```
#![feature(repr_simd)]
#[repr(simd)]
struct Good(u32, u32, u32, u32); // ok!
struct Good([u16; 1]); // ok!
```
4 changes: 2 additions & 2 deletions compiler/rustc_error_codes/src/error_codes/E0077.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Erroneous code example:
#![feature(repr_simd)]
#[repr(simd)]
struct Bad(String); // error!
struct Bad([String; 2]); // error!
```

When using the `#[simd]` attribute on a tuple struct, the elements in the tuple
Expand All @@ -19,5 +19,5 @@ Fixed example:
#![feature(repr_simd)]
#[repr(simd)]
struct Good(u32, u32, u32, u32); // ok!
struct Good([u32; 4]); // ok!
```
4 changes: 2 additions & 2 deletions compiler/rustc_error_codes/src/error_codes/E0511.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ The generic type has to be a SIMD type. Example:
#[repr(simd)]
#[derive(Copy, Clone)]
struct i32x2(i32, i32);
struct i32x2([i32; 2]);
extern "rust-intrinsic" {
fn simd_add<T>(a: T, b: T) -> T;
}
unsafe { simd_add(i32x2(0, 0), i32x2(1, 2)); } // ok!
unsafe { simd_add(i32x2([0, 0]), i32x2([1, 2])); } // ok!
```
42 changes: 22 additions & 20 deletions compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1064,20 +1064,29 @@ fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot be empty").emit();
return;
}
let e = fields[FieldIdx::ZERO].ty(tcx, args);
if !fields.iter().all(|f| f.ty(tcx, args) == e) {
struct_span_code_err!(tcx.dcx(), sp, E0076, "SIMD vector should be homogeneous")
.with_span_label(sp, "SIMD elements must have the same type")

let array_field = &fields[FieldIdx::ZERO];
let array_ty = array_field.ty(tcx, args);
let ty::Array(element_ty, len_const) = array_ty.kind() else {
struct_span_code_err!(
tcx.dcx(),
sp,
E0076,
"SIMD vector's only field must be an array"
)
.with_span_label(tcx.def_span(array_field.did), "not an array")
.emit();
return;
};

if let Some(second_field) = fields.get(FieldIdx::from_u32(1)) {
struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot have multiple fields")
.with_span_label(tcx.def_span(second_field.did), "excess field")
.emit();
return;
}

let len = if let ty::Array(_ty, c) = e.kind() {
c.try_eval_target_usize(tcx, tcx.param_env(def.did()))
} else {
Some(fields.len() as u64)
};
if let Some(len) = len {
if let Some(len) = len_const.try_eval_target_usize(tcx, tcx.param_env(def.did())) {
if len == 0 {
struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot be empty").emit();
return;
Expand All @@ -1097,16 +1106,9 @@ fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
// These are scalar types which directly match a "machine" type
// Yes: Integers, floats, "thin" pointers
// No: char, "fat" pointers, compound types
match e.kind() {
ty::Param(_) => (), // pass struct<T>(T, T, T, T) through, let monomorphization catch errors
ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::RawPtr(_, _) => (), // struct(u8, u8, u8, u8) is ok
ty::Array(t, _) if matches!(t.kind(), ty::Param(_)) => (), // pass struct<T>([T; N]) through, let monomorphization catch errors
ty::Array(t, _clen)
if matches!(
t.kind(),
ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::RawPtr(_, _)
) =>
{ /* struct([f32; 4]) is ok */ }
match element_ty.kind() {
ty::Param(_) => (), // pass struct<T>([T; 4]) through, let monomorphization catch errors
ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::RawPtr(_, _) => (), // struct([u8; 4]) is ok
_ => {
struct_span_code_err!(
tcx.dcx(),
Expand Down
38 changes: 15 additions & 23 deletions compiler/rustc_middle/src/ty/sty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,29 +1091,21 @@ impl<'tcx> Ty<'tcx> {
}

pub fn simd_size_and_type(self, tcx: TyCtxt<'tcx>) -> (u64, Ty<'tcx>) {
match self.kind() {
Adt(def, args) => {
assert!(def.repr().simd(), "`simd_size_and_type` called on non-SIMD type");
let variant = def.non_enum_variant();
let f0_ty = variant.fields[FieldIdx::ZERO].ty(tcx, args);

match f0_ty.kind() {
// If the first field is an array, we assume it is the only field and its
// elements are the SIMD components.
Array(f0_elem_ty, f0_len) => {
// FIXME(repr_simd): https://github.com/rust-lang/rust/pull/78863#discussion_r522784112
// The way we evaluate the `N` in `[T; N]` here only works since we use
// `simd_size_and_type` post-monomorphization. It will probably start to ICE
// if we use it in generic code. See the `simd-array-trait` ui test.
(f0_len.eval_target_usize(tcx, ParamEnv::empty()), *f0_elem_ty)
}
// Otherwise, the fields of this Adt are the SIMD components (and we assume they
// all have the same type).
_ => (variant.fields.len() as u64, f0_ty),
}
}
_ => bug!("`simd_size_and_type` called on invalid type"),
}
let Adt(def, args) = self.kind() else {
bug!("`simd_size_and_type` called on invalid type")
};
assert!(def.repr().simd(), "`simd_size_and_type` called on non-SIMD type");
let variant = def.non_enum_variant();
assert_eq!(variant.fields.len(), 1);
let field_ty = variant.fields[FieldIdx::ZERO].ty(tcx, args);
let Array(f0_elem_ty, f0_len) = field_ty.kind() else {
bug!("Simd type has non-array field type {field_ty:?}")
};
// FIXME(repr_simd): https://github.com/rust-lang/rust/pull/78863#discussion_r522784112
// The way we evaluate the `N` in `[T; N]` here only works since we use
// `simd_size_and_type` post-monomorphization. It will probably start to ICE
// if we use it in generic code. See the `simd-array-trait` ui test.
(f0_len.eval_target_usize(tcx, ParamEnv::empty()), *f0_elem_ty)
}

#[inline]
Expand Down
4 changes: 2 additions & 2 deletions library/alloc/benches/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,10 @@ reverse!(reverse_u32, u32, |x| x as u32);
reverse!(reverse_u64, u64, |x| x as u64);
reverse!(reverse_u128, u128, |x| x as u128);
#[repr(simd)]
struct F64x4(f64, f64, f64, f64);
struct F64x4([f64; 4]);
reverse!(reverse_simd_f64x4, F64x4, |x| {
let x = x as f64;
F64x4(x, x, x, x)
F64x4([x, x, x, x])
});

macro_rules! rotate {
Expand Down
31 changes: 17 additions & 14 deletions tests/assembly/asm/aarch64-types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,39 @@ trait Sized {}
#[lang = "copy"]
trait Copy {}

// Do we really need to use no_core for this?!?
impl<T: Copy, const N: usize> Copy for [T; N] {}

type ptr = *mut u8;

#[repr(simd)]
pub struct i8x8(i8, i8, i8, i8, i8, i8, i8, i8);
pub struct i8x8([i8; 8]);
#[repr(simd)]
pub struct i16x4(i16, i16, i16, i16);
pub struct i16x4([i16; 4]);
#[repr(simd)]
pub struct i32x2(i32, i32);
pub struct i32x2([i32; 2]);
#[repr(simd)]
pub struct i64x1(i64);
pub struct i64x1([i64; 1]);
#[repr(simd)]
pub struct f16x4(f16, f16, f16, f16);
pub struct f16x4([f16; 4]);
#[repr(simd)]
pub struct f32x2(f32, f32);
pub struct f32x2([f32; 2]);
#[repr(simd)]
pub struct f64x1(f64);
pub struct f64x1([f64; 1]);
#[repr(simd)]
pub struct i8x16(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8);
pub struct i8x16([i8; 16]);
#[repr(simd)]
pub struct i16x8(i16, i16, i16, i16, i16, i16, i16, i16);
pub struct i16x8([i16; 8]);
#[repr(simd)]
pub struct i32x4(i32, i32, i32, i32);
pub struct i32x4([i32; 4]);
#[repr(simd)]
pub struct i64x2(i64, i64);
pub struct i64x2([i64; 2]);
#[repr(simd)]
pub struct f16x8(f16, f16, f16, f16, f16, f16, f16, f16);
pub struct f16x8([f16; 8]);
#[repr(simd)]
pub struct f32x4(f32, f32, f32, f32);
pub struct f32x4([f32; 4]);
#[repr(simd)]
pub struct f64x2(f64, f64);
pub struct f64x2([f64; 2]);

impl Copy for i8 {}
impl Copy for i16 {}
Expand Down
5 changes: 4 additions & 1 deletion tests/assembly/asm/arm-modifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ trait Sized {}
#[lang = "copy"]
trait Copy {}

// Do we really need to use no_core for this?!?
impl<T: Copy, const N: usize> Copy for [T; N] {}

#[repr(simd)]
pub struct f32x4(f32, f32, f32, f32);
pub struct f32x4([f32; 4]);

impl Copy for i32 {}
impl Copy for f32 {}
Expand Down
27 changes: 15 additions & 12 deletions tests/assembly/asm/arm-types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,35 @@ trait Sized {}
#[lang = "copy"]
trait Copy {}

// Do we really need to use no_core for this?!?
impl<T: Copy, const N: usize> Copy for [T; N] {}

type ptr = *mut u8;

#[repr(simd)]
pub struct i8x8(i8, i8, i8, i8, i8, i8, i8, i8);
pub struct i8x8([i8; 8]);
#[repr(simd)]
pub struct i16x4(i16, i16, i16, i16);
pub struct i16x4([i16; 4]);
#[repr(simd)]
pub struct i32x2(i32, i32);
pub struct i32x2([i32; 2]);
#[repr(simd)]
pub struct i64x1(i64);
pub struct i64x1([i64; 1]);
#[repr(simd)]
pub struct f16x4(f16, f16, f16, f16);
pub struct f16x4([f16; 4]);
#[repr(simd)]
pub struct f32x2(f32, f32);
pub struct f32x2([f32; 2]);
#[repr(simd)]
pub struct i8x16(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8);
pub struct i8x16([i8; 16]);
#[repr(simd)]
pub struct i16x8(i16, i16, i16, i16, i16, i16, i16, i16);
pub struct i16x8([i16; 8]);
#[repr(simd)]
pub struct i32x4(i32, i32, i32, i32);
pub struct i32x4([i32; 4]);
#[repr(simd)]
pub struct i64x2(i64, i64);
pub struct i64x2([i64; 2]);
#[repr(simd)]
pub struct f16x8(f16, f16, f16, f16, f16, f16, f16, f16);
pub struct f16x8([f16; 8]);
#[repr(simd)]
pub struct f32x4(f32, f32, f32, f32);
pub struct f32x4([f32; 4]);

impl Copy for i8 {}
impl Copy for i16 {}
Expand Down
Loading

0 comments on commit d2309c2

Please sign in to comment.