diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bccb5ccdd7..529003fc384 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: # "--asm-tests", "--test-libcore", "--extended-rand-tests", - "--extended-regex-example-tests", + "--extended-regex-example-tests --test-libcore-doctests", "--extended-regex-tests", "--test-successful-rustc --nb-parts 2 --current-part 0", "--test-successful-rustc --nb-parts 2 --current-part 1", diff --git a/build_system/src/test.rs b/build_system/src/test.rs index 3ea62b95798..2e9a67f9a8c 100644 --- a/build_system/src/test.rs +++ b/build_system/src/test.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; use std::ffi::OsStr; -use std::fs::{File, remove_dir_all}; +use std::fs::{File, read_to_string, remove_dir_all}; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::str::FromStr; +use boml::Toml; + use crate::build; use crate::config::{Channel, ConfigInfo}; use crate::utils::{ @@ -32,6 +34,7 @@ fn get_runners() -> Runners { runners.insert("--projects", ("Run the tests of popular crates", test_projects)); runners.insert("--test-libcore", ("Run libcore tests", test_libcore)); runners.insert("--test-release-libcore", ("Run libcore tests", test_release_libcore)); + runners.insert("--test-libcore-doctests", ("Run libcore doc-tests", test_libcore_doctests)); runners.insert("--alloc-tests", ("Run alloc tests", test_alloc)); runners.insert("--clean", ("Empty cargo target directory", clean)); runners.insert("--build-sysroot", ("Build sysroot", build_sysroot)); @@ -787,6 +790,67 @@ fn test_libcore_inner(env: &Env, args: &TestArg, release: bool) -> Result<(), St Ok(()) } +/// Returns the edition declared in the manifest of the given library crate, so that the doctests +/// are run with the same edition as the crate they are extracted from. +fn get_crate_edition(crate_dir: &Path) -> Result { + let manifest_path = crate_dir.join("Cargo.toml"); + let content = read_to_string(&manifest_path) + .map_err(|error| format!("Failed to read `{}`: {error:?}", manifest_path.display()))?; + let manifest = Toml::parse(&content) + .map_err(|error| format!("Failed to parse `{}`: {error:?}", manifest_path.display()))?; + manifest + .get_table("package") + .and_then(|package| package.get_string("edition")) + .map(|edition| edition.to_string()) + .map_err(|error| { + format!("Failed to get `package.edition` from `{}`: {error:?}", manifest_path.display()) + }) +} + +fn test_libcore_doctests(env: &Env, args: &TestArg) -> Result<(), String> { + // FIXME: create a function "display_if_not_quiet" or something along the line. + println!("[TEST] libcore doctests"); + + let library_dir = get_sysroot_dir().join("sysroot_src/library"); + let edition = get_crate_edition(&library_dir.join("core"))?; + // `rustdoc` is called directly instead of through `cargo test --doc` because `cargo` builds its + // own `core` and passes it with `--extern`, which then conflicts with the `core` of the sysroot + // the doctests are linked against ("duplicate lang item" errors). + let toolchain = get_toolchain()?; + let toolchain_arg = format!("+{toolchain}"); + let rustflags = split_args(&env.get("RUSTFLAGS").cloned().unwrap_or_default())?; + // `-Zunstable-options` is needed for `--test-args`. + let mut command: Vec<&dyn AsRef> = vec![ + &"rustdoc", + &toolchain_arg, + &"--test", + &"core/src/lib.rs", + &"--crate-name", + &"core", + &"--crate-type", + &"lib", + &"--edition", + &edition, + &"-Zunstable-options", + // FIXME: remove `-Zforce-unstable-if-unmarked` once the doctest of + // `core::io::ErrorKind`'s `Display` impl declares `#![feature(core_io)]` upstream: without + // it, that doctest fails to compile with `E0658` on any backend. + &"-Zforce-unstable-if-unmarked", + ]; + for flag in &rustflags { + command.push(flag); + } + // Additional arguments are forwarded to the test harness, so that a subset of the doctests can + // be run. + let test_args = + args.test_args.iter().map(|test_arg| format!("--test-args={test_arg}")).collect::>(); + for test_arg in &test_args { + command.push(test_arg); + } + run_command_with_output_and_env(&command, Some(&library_dir), Some(env))?; + Ok(()) +} + fn test_stdarch(env: &Env, args: &TestArg) -> Result<(), String> { println!("[TEST] stdarch"); let manifest_path = get_sysroot_dir().join("sysroot_src/library/stdarch/Cargo.toml"); diff --git a/src/intrinsic/simd.rs b/src/intrinsic/simd.rs index 1416f4eec9c..02f1f7efd9c 100644 --- a/src/intrinsic/simd.rs +++ b/src/intrinsic/simd.rs @@ -11,11 +11,12 @@ use rustc_codegen_ssa::diagnostics::ExpectedPointerMutability; use rustc_codegen_ssa::diagnostics::InvalidMonomorphization; use rustc_codegen_ssa::mir::operand::OperandRef; use rustc_codegen_ssa::mir::place::PlaceRef; -use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods}; +use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods, LayoutTypeCodegenMethods}; #[cfg(feature = "master")] use rustc_hir as hir; use rustc_middle::mir::BinOp; -use rustc_middle::ty::layout::HasTyCtxt; +use rustc_middle::span_bug; +use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf}; use rustc_middle::ty::{self, Ty}; use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; @@ -655,6 +656,39 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &values)); } + if name == sym::simd_arith_offset { + // This also checks that the first operand is a ptr type. + let pointee = in_elem.builtin_deref(true).unwrap_or_else(|| { + span_bug!(span, "must be called with a vector of pointer types as first argument") + }); + let layout = bx.layout_of(pointee); + // The second argument must be a ptr-sized integer. + // (We don't care about the signedness, this is wrapping anyway.) + let (_, offsets_elem) = args[1].layout.ty.simd_size_and_type(bx.tcx()); + if !matches!(offsets_elem.kind(), ty::Int(ty::IntTy::Isize) | ty::Uint(ty::UintTy::Usize)) { + span_bug!( + span, + "must be called with a vector of pointer-sized integers as second argument" + ); + } + + let pointee_type = bx.backend_type(layout); + let pointers = args[0].immediate(); + let offsets = args[1].immediate(); + let elem_type = llret_ty.dyncast_vector().expect("vector return type").get_element_type(); + let values: Vec<_> = (0..in_len) + .map(|i| { + let index = bx.context.new_rvalue_from_long(bx.usize_type, i as _); + let pointer = bx.extract_element(pointers, index); + let offset = bx.extract_element(offsets, index); + let pointer = bx.gep(pointee_type, pointer, &[offset]); + // GCC has no pointer vectors, so the lanes are `usize`. + bx.ptrtoint(pointer, elem_type) + }) + .collect(); + return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &values)); + } + #[cfg(feature = "master")] if name == sym::simd_cast || name == sym::simd_as { require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); @@ -675,20 +709,28 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return Ok(args[0].immediate()); } + #[derive(Copy, Clone)] + enum Sign { + Unsigned, + Signed, + } + use Sign::*; + enum Style { Float, - Int, + Int(Sign), Unsupported, } let in_style = match *in_elem.kind() { - ty::Int(_) | ty::Uint(_) => Style::Int, + ty::Int(_) => Style::Int(Signed), + ty::Uint(_) => Style::Int(Unsigned), ty::Float(_) => Style::Float, _ => Style::Unsupported, }; - let out_style = match *out_elem.kind() { - ty::Int(_) | ty::Uint(_) => Style::Int, + ty::Int(_) => Style::Int(Signed), + ty::Uint(_) => Style::Int(Unsigned), ty::Float(_) => Style::Float, _ => Style::Unsupported, }; @@ -707,6 +749,19 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( } ); } + (Style::Float, Style::Int(sign)) if name == sym::simd_as => { + let vector = args[0].immediate(); + let elem_type = + llret_ty.dyncast_vector().expect("vector return type").get_element_type(); + let values: Vec<_> = (0..in_len) + .map(|i| { + let index = bx.context.new_rvalue_from_long(bx.usize_type, i as _); + let value = bx.extract_element(vector, index); + bx.cast_float_to_int(matches!(sign, Sign::Signed), value, elem_type) + }) + .collect(); + return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &values)); + } _ => return Ok(bx.context.convert_vector(None, args[0].immediate(), llret_ty)), } } @@ -1310,32 +1365,28 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( (true, false) => { // FIXME(antoyo): dyncast_vector should not require a call to unqualified. let arg_type = lhs.get_type().unqualified(); - // FIXME(antoyo): this uses the same algorithm from saturating add, but add the - // negative of the right operand. Find a proper subtraction algorithm. - let rhs = bx.context.new_unary_op(None, UnaryOp::Minus, arg_type, rhs); - // FIXME(antoyo): convert lhs and rhs to unsigned. - let sum = lhs + rhs; + let difference = lhs - rhs; let vector_type = arg_type.dyncast_vector().expect("vector type"); let unit = vector_type.get_num_units(); let a = bx.context.new_rvalue_from_int(elem_ty, ((elem_width as i32) << 3) - 1); let width = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![a; unit]); + // The subtraction overflows when the operands have different signs and the result + // has a different sign than the left operand. let xor1 = lhs ^ rhs; - let xor2 = lhs ^ sum; - let and = - bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, xor1) & xor2; - let mask = and >> width; + let xor2 = lhs ^ difference; + let mask = (xor1 & xor2) >> width; let one = bx.context.new_rvalue_one(elem_ty); let ones = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![one; unit]); let shift1 = ones << width; - let shift2 = sum >> width; + let shift2 = difference >> width; let mask_min = shift1 ^ shift2; - let and1 = - bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) & sum; + let and1 = bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) + & difference; let and2 = mask & mask_min; and1 + and2 diff --git a/tests/failing-ui-tests.txt b/tests/failing-ui-tests.txt index ce614fecba2..7f96bfabedb 100644 --- a/tests/failing-ui-tests.txt +++ b/tests/failing-ui-tests.txt @@ -5,7 +5,6 @@ tests/ui/simd/issue-17170.rs tests/ui/simd/issue-39720.rs tests/ui/process/println-with-broken-pipe.rs tests/ui/simd/repr_packed.rs -tests/ui/simd/intrinsic/generic-as.rs tests/ui/simd/simd-bitmask-notpow2.rs tests/ui/codegen/StackColoring-not-blowup-stack-issue-40883.rs tests/ui/numbers-arithmetic/u128-as-f32.rs diff --git a/tests/run/simd.rs b/tests/run/simd.rs new file mode 100644 index 00000000000..e0a23fdccf8 --- /dev/null +++ b/tests/run/simd.rs @@ -0,0 +1,81 @@ +// Compiler: +// +// Run-time: +// status: 0 + +#![feature(portable_simd)] + +use std::hint::black_box; +use std::simd::prelude::*; + +fn test_saturating_add() { + let values = i32x4::from_array([i32::MIN, -2, 3, i32::MAX]); + let ones = i32x4::splat(1); + assert_eq!( + black_box(values).saturating_add(black_box(ones)).to_array(), + [i32::MIN + 1, -1, 4, i32::MAX] + ); + + let values = u32x4::from_array([0, 2, 3, u32::MAX]); + let ones = u32x4::splat(1); + assert_eq!(black_box(values).saturating_add(black_box(ones)).to_array(), [1, 3, 4, u32::MAX]); +} + +fn test_saturating_sub() { + let values = i32x4::from_array([i32::MIN, -2, 3, i32::MAX]); + let zero = i32x4::splat(0); + assert_eq!( + black_box(zero).saturating_sub(black_box(values)).to_array(), + [i32::MAX, 2, -3, i32::MIN + 1] + ); + assert_eq!(black_box(values).saturating_neg().to_array(), [i32::MAX, 2, -3, i32::MIN + 1]); + assert_eq!(black_box(values).saturating_abs().to_array(), [i32::MAX, 2, 3, i32::MAX]); + + let values = i32x4::from_array([i32::MIN, -2, 3, i32::MAX]); + let ones = i32x4::splat(1); + assert_eq!( + black_box(values).saturating_sub(black_box(ones)).to_array(), + [i32::MIN, -3, 2, i32::MAX - 1] + ); + + let values = u32x4::from_array([0, 2, 3, u32::MAX]); + let ones = u32x4::splat(1); + assert_eq!( + black_box(values).saturating_sub(black_box(ones)).to_array(), + [0, 1, 2, u32::MAX - 1] + ); +} + +fn test_float_cast() { + let floats = f32x4::from_array([1.9, -4.5, f32::INFINITY, f32::NAN]); + assert_eq!(black_box(floats).cast::().to_array(), [1, -4, i32::MAX, 0]); + + let floats = f32x4::from_array([f32::NEG_INFINITY, 1e20, -1e20, -0.0]); + assert_eq!(black_box(floats).cast::().to_array(), [i32::MIN, i32::MAX, i32::MIN, 0]); + + let floats = f32x4::from_array([-1.0, 3.7, f32::NAN, 1e20]); + assert_eq!(black_box(floats).cast::().to_array(), [0, 3, 0, u32::MAX]); + + let floats = f64x4::from_array([-1.5, 2.5, f64::NAN, f64::INFINITY]); + assert_eq!(black_box(floats).cast::().to_array(), [-1, 2, 0, i64::MAX]); +} + +fn test_arith_offset() { + let values = [10i32, 11, 12, 13, 14, 15, 16, 17]; + let indices = usizex4::from_array([7, 5, 3, 1]); + assert_eq!( + i32x4::gather_or_default(black_box(&values), black_box(indices)).to_array(), + [17, 15, 13, 11] + ); + + let mut destination = [0i32; 8]; + i32x4::from_array([1, 2, 3, 4]).scatter(black_box(&mut destination), black_box(indices)); + assert_eq!(destination, [0, 4, 0, 3, 0, 2, 0, 1]); +} + +fn main() { + test_saturating_add(); + test_saturating_sub(); + test_float_cast(); + test_arith_offset(); +}