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
25 changes: 25 additions & 0 deletions compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,31 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>(
ret.write_cvalue(fx, ret_lane);
}

sym::simd_splat => {
intrinsic_args!(fx, args => (value); intrinsic);

if !ret.layout().ty.is_simd() {
report_simd_type_validation_error(fx, intrinsic, span, ret.layout().ty);
return;
}
let (lane_count, lane_ty) = ret.layout().ty.simd_size_and_type(fx.tcx);

if value.layout().ty != lane_ty {
fx.tcx.dcx().span_fatal(
span,
format!(
"[simd_splat] expected element type {lane_ty:?}, got {got:?}",
got = value.layout().ty
),
);
}

for i in 0..lane_count {
let ret_lane = ret.place_lane(fx, i.into());
ret_lane.write_cvalue(fx, value);
}
}

sym::simd_neg
| sym::simd_bswap
| sym::simd_bitreverse
Expand Down
36 changes: 36 additions & 0 deletions compiler/rustc_codegen_gcc/src/intrinsic/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,42 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>(
return Ok(bx.vector_select(vector_mask, arg1, args[2].immediate()));
}

#[cfg(feature = "master")]
if name == sym::simd_splat {
let (out_len, out_ty) = require_simd2!(ret_ty, SimdReturn);

require!(
args[0].layout.ty == out_ty,
InvalidMonomorphization::ExpectedVectorElementType {
span,
name,
expected_element: out_ty,
vector_type: ret_ty,
}
);

let vec_ty = llret_ty.unqualified().dyncast_vector().expect("vector return type");
let elem_ty = vec_ty.get_element_type();

// Cast pointer type to usize (GCC does not support pointer SIMD vectors).
let value = args[0];
let scalar = if value.layout.ty.is_numeric() {
value.immediate()
} else if value.layout.ty.is_raw_ptr() {
bx.ptrtoint(value.immediate(), elem_ty)
} else {
return_error!(InvalidMonomorphization::UnsupportedOperation {
span,
name,
in_ty: ret_ty,
in_elem: value.layout.ty
});
};

let elements = vec![scalar; out_len as usize];
return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &elements));
}

// every intrinsic below takes a SIMD vector as its first argument
require_simd!(
args[0].layout.ty,
Expand Down
25 changes: 25 additions & 0 deletions compiler/rustc_codegen_llvm/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1588,6 +1588,31 @@ fn generic_simd_intrinsic<'ll, 'tcx>(
return Ok(bx.select(m_i1s, args[1].immediate(), args[2].immediate()));
}

if name == sym::simd_splat {
let (_out_len, out_ty) = require_simd!(ret_ty, SimdReturn);

require!(
args[0].layout.ty == out_ty,
InvalidMonomorphization::ExpectedVectorElementType {
span,
name,
expected_element: out_ty,
vector_type: ret_ty,
}
);

// `insertelement <N x elem> poison, elem %x, i32 0`
let poison_vec = bx.const_poison(llret_ty);
let idx0 = bx.const_i32(0);
let v0 = bx.insert_element(poison_vec, args[0].immediate(), idx0);

// `shufflevector <N x elem> v0, <N x elem> poison, <N x i32> zeroinitializer`
// The masks is all zeros, so this splats lane 0 (which has our element in it).
let splat = bx.shuffle_vector(v0, poison_vec, bx.const_null(llret_ty));

return Ok(splat);
}

// every intrinsic below takes a SIMD vector as its first argument
let (in_len, in_elem) = require_simd!(args[0].layout.ty, SimdInput);
let in_ty = args[0].layout.ty;
Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_codegen_ssa/src/mir/operand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1074,8 +1074,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
if constant_ty.is_simd() {
// However, some SIMD types do not actually use the vector ABI
// (in particular, packed SIMD types do not). Ensure we exclude those.
//
// We also have to exclude vectors of pointers because `immediate_const_vector`
// does not work for those.
let layout = bx.layout_of(constant_ty);
if let BackendRepr::SimdVector { .. } = layout.backend_repr {
let (_, element_ty) = constant_ty.simd_size_and_type(bx.tcx());
if let BackendRepr::SimdVector { .. } = layout.backend_repr
&& element_ty.is_numeric()
{
let (llval, ty) = self.immediate_const_vector(bx, constant);
return OperandRef {
val: OperandValue::Immediate(llval),
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
}
self.copy_op(&self.project_index(&input, index)?, &dest)?;
}
sym::simd_splat => {
let elem = &args[0];
let (dest, dest_len) = self.project_to_simd(&dest)?;

for i in 0..dest_len {
let place = self.project_index(&dest, i)?;
self.copy_op(elem, &place)?;
}
}
sym::simd_neg
| sym::simd_fabs
| sym::simd_ceil
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_analysis/src/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ pub(crate) fn check_intrinsic_type(
sym::simd_extract | sym::simd_extract_dyn => {
(2, 0, vec![param(0), tcx.types.u32], param(1))
}
sym::simd_splat => (2, 0, vec![param(1)], param(0)),
sym::simd_cast
| sym::simd_as
| sym::simd_cast_ptr
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2140,6 +2140,7 @@ symbols! {
simd_shr,
simd_shuffle,
simd_shuffle_const_generic,
simd_splat,
simd_sub,
simd_trunc,
simd_with_exposed_provenance,
Expand Down
7 changes: 7 additions & 0 deletions library/core/src/intrinsics/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ pub unsafe fn simd_extract_dyn<T, U>(x: T, idx: u32) -> U {
unsafe { (&raw const x).cast::<U>().add(idx as usize).read() }
}

/// Creates a vector where every lane has the provided value.
///
/// `T` must be a vector with element type `U`.
#[rustc_nounwind]
#[rustc_intrinsic]
pub const unsafe fn simd_splat<T, U>(value: U) -> T;

/// Adds two simd vectors elementwise.
///
/// `T` must be a vector of integers or floats.
Expand Down
33 changes: 33 additions & 0 deletions tests/codegen-llvm/simd/splat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//@ compile-flags: -Copt-level=3
#![crate_type = "lib"]
#![no_std]
#![feature(repr_simd, core_intrinsics)]
use core::intrinsics::simd::simd_splat;

#[path = "../../auxiliary/minisimd.rs"]
mod minisimd;
use minisimd::*;

// Test that `simd_splat` produces the canonical LLVM splat sequence.

#[no_mangle]
unsafe fn int(x: u16) -> u16x2 {
// CHECK-LABEL: int
// CHECK: start:
// CHECK-NEXT: %0 = insertelement <2 x i16> poison, i16 %x, i64 0
// CHECK-NEXT: %1 = shufflevector <2 x i16> %0, <2 x i16> poison, <2 x i32> zeroinitializer
Comment on lines +17 to +18
Copy link
Member

Choose a reason for hiding this comment

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

I remember LLVM had added a special splat syntax for producing this...

When creating a vector whose elements have the same constant value, the preferred syntax is splat (<Ty> Val). For example: “splat (i32 11)”. These vector constants must have vector type with an element type that matches the splat operand.

Copy link
Member

Choose a reason for hiding this comment

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

I don't see a way to do that in the C API, though, and I'm not sure if that is supported by the minimal LLVM version we support right now. I'm willing to r+ this Now as we can fix it later, but is there something I'm missing?

Copy link
Member

Choose a reason for hiding this comment

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

noting for posterity, nikic says:

That being insertelement poison, x + shufflevector zeroinitializer.

and we are currently in the belief that this particular pattern gets rewritten into the splat syntactic form (or vice versa, the splat form gets rewritten into this). either way, we're good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When creating a vector whose elements have the same constant value

My reading (based on some experiments) is that this means a compile-time known value. simd_splat uses some user-provided value, so the splat syntax does not apply here.

you can use splat to define literals:

define  <2 x i16> @square2(i16 %x) local_unnamed_addr #0 {
    ret <2 x i16> splat (i16 1)
}

but it won't use local variables, this fails:

define  <2 x i16> @square2(i16 %x) local_unnamed_addr #0 {
    ret <2 x i16> splat (i16 %x)
}

with

error: invalid use of function-local name
    ret <2 x i16> splat (i16 %x)
                             ^

Copy link
Member

Choose a reason for hiding this comment

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

My reading (based on some experiments) is that this means a compile-time known value. simd_splat uses some user-provided value, so the splat syntax does not apply here.

Ahhh, okay.

// CHECK-NEXT: store
// CHECK-NEXT: ret
simd_splat(x)
}

#[no_mangle]
unsafe fn float(x: f32) -> f32x4 {
// CHECK-LABEL: float
// CHECK: start:
// CHECK-NEXT: %0 = insertelement <4 x float> poison, float %x, i64 0
// CHECK-NEXT: %1 = shufflevector <4 x float> %0, <4 x float> poison, <4 x i32> zeroinitializer
// CHECK-NEXT: store
// CHECK-NEXT: ret
simd_splat(x)
}
48 changes: 48 additions & 0 deletions tests/ui/simd/intrinsic/splat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//@ run-pass
#![feature(repr_simd, core_intrinsics)]

#[path = "../../../auxiliary/minisimd.rs"]
mod minisimd;
use minisimd::*;

use std::intrinsics::simd::simd_splat;

fn main() {
unsafe {
let x: Simd<u32, 1> = simd_splat(123u32);
let y: Simd<u32, 1> = const { simd_splat(123u32) };
assert_eq!(x.into_array(), [123; 1]);
assert_eq!(x.into_array(), y.into_array());

let x: u16x2 = simd_splat(42u16);
let y: u16x2 = const { simd_splat(42u16) };
assert_eq!(x.into_array(), [42; 2]);
assert_eq!(x.into_array(), y.into_array());

let x: u128x4 = simd_splat(42u128);
let y: u128x4 = const { simd_splat(42u128) };
assert_eq!(x.into_array(), [42; 4]);
assert_eq!(x.into_array(), y.into_array());

let x: i32x4 = simd_splat(-7i32);
let y: i32x4 = const { simd_splat(-7i32) };
assert_eq!(x.into_array(), [-7; 4]);
assert_eq!(x.into_array(), y.into_array());

let x: f32x4 = simd_splat(42.0f32);
let y: f32x4 = const { simd_splat(42.0f32) };
assert_eq!(x.into_array(), [42.0; 4]);
assert_eq!(x.into_array(), y.into_array());

let x: f64x2 = simd_splat(42.0f64);
let y: f64x2 = const { simd_splat(42.0f64) };
assert_eq!(x.into_array(), [42.0; 2]);
assert_eq!(x.into_array(), y.into_array());

static ZERO: u8 = 0u8;
let x: Simd<*const u8, 2> = simd_splat(&raw const ZERO);
let y: Simd<*const u8, 2> = const { simd_splat(&raw const ZERO) };
assert_eq!(x.into_array(), [&raw const ZERO; 2]);
assert_eq!(x.into_array(), y.into_array());
}
}
Loading