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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
66 changes: 65 additions & 1 deletion build_system/src/test.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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<String, String> {
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<OsStr>> = 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::<Vec<_>>();
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");
Expand Down
87 changes: 69 additions & 18 deletions src/intrinsic/simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 });
Expand All @@ -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,
};
Expand All @@ -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)),
}
}
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion tests/failing-ui-tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 81 additions & 0 deletions tests/run/simd.rs
Original file line number Diff line number Diff line change
@@ -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::<i32>().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::<i32>().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::<u32>().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::<i64>().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();
}
Loading