Auto merge of #125902 - workingjubilee:rollup-f8x6iif, r=workingjubilee

Rollup of 6 pull requests

Successful merges:

 - #121062 (Change f32::midpoint to upcast to f64)
 - #125808 (Migrate `run-make/c-link-to-rust-dylib` to `rmake.rs`)
 - #125884 (Implement feature `integer_sign_cast`)
 - #125890 (Improve compiletest expected/not found formatting)
 - #125896 (compiletest: fix outdated rmake.rs comment)
 - #125898 (typo: depending from -> on)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2024-06-02 21:54:30 +00:00
commit 032af18af5
13 changed files with 216 additions and 67 deletions

View File

@ -1030,25 +1030,42 @@ impl f32 {
/// ``` /// ```
#[unstable(feature = "num_midpoint", issue = "110840")] #[unstable(feature = "num_midpoint", issue = "110840")]
pub fn midpoint(self, other: f32) -> f32 { pub fn midpoint(self, other: f32) -> f32 {
const LO: f32 = f32::MIN_POSITIVE * 2.; cfg_if! {
const HI: f32 = f32::MAX / 2.; if #[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
all(any(target_arch="riscv32", target_arch= "riscv64"), target_feature="d"),
all(target_arch = "arm", target_feature="vfp2"),
target_arch = "wasm32",
target_arch = "wasm64",
))] {
// whitelist the faster implementation to targets that have known good 64-bit float
// implementations. Falling back to the branchy code on targets that don't have
// 64-bit hardware floats or buggy implementations.
// see: https://github.com/rust-lang/rust/pull/121062#issuecomment-2123408114
((f64::from(self) + f64::from(other)) / 2.0) as f32
} else {
const LO: f32 = f32::MIN_POSITIVE * 2.;
const HI: f32 = f32::MAX / 2.;
let (a, b) = (self, other); let (a, b) = (self, other);
let abs_a = a.abs_private(); let abs_a = a.abs_private();
let abs_b = b.abs_private(); let abs_b = b.abs_private();
if abs_a <= HI && abs_b <= HI { if abs_a <= HI && abs_b <= HI {
// Overflow is impossible // Overflow is impossible
(a + b) / 2. (a + b) / 2.
} else if abs_a < LO { } else if abs_a < LO {
// Not safe to halve a // Not safe to halve a
a + (b / 2.) a + (b / 2.)
} else if abs_b < LO { } else if abs_b < LO {
// Not safe to halve b // Not safe to halve b
(a / 2.) + b (a / 2.) + b
} else { } else {
// Not safe to halve a and b // Not safe to halve a and b
(a / 2.) + (b / 2.) (a / 2.) + (b / 2.)
}
}
} }
} }

View File

@ -183,6 +183,30 @@ macro_rules! int_impl {
(self as $UnsignedT).trailing_ones() (self as $UnsignedT).trailing_ones()
} }
/// Returns the bit pattern of `self` reinterpreted as an unsigned integer of the same size.
///
/// This produces the same result as an `as` cast, but ensures that the bit-width remains
/// the same.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(integer_sign_cast)]
///
#[doc = concat!("let n = -1", stringify!($SelfT), ";")]
///
#[doc = concat!("assert_eq!(n.cast_unsigned(), ", stringify!($UnsignedT), "::MAX);")]
/// ```
#[unstable(feature = "integer_sign_cast", issue = "125882")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline(always)]
pub const fn cast_unsigned(self) -> $UnsignedT {
self as $UnsignedT
}
/// Shifts the bits to the left by a specified amount, `n`, /// Shifts the bits to the left by a specified amount, `n`,
/// wrapping the truncated bits to the end of the resulting integer. /// wrapping the truncated bits to the end of the resulting integer.
/// ///

View File

@ -184,6 +184,30 @@ macro_rules! uint_impl {
(!self).trailing_zeros() (!self).trailing_zeros()
} }
/// Returns the bit pattern of `self` reinterpreted as a signed integer of the same size.
///
/// This produces the same result as an `as` cast, but ensures that the bit-width remains
/// the same.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(integer_sign_cast)]
///
#[doc = concat!("let n = ", stringify!($SelfT), "::MAX;")]
///
#[doc = concat!("assert_eq!(n.cast_signed(), -1", stringify!($SignedT), ");")]
/// ```
#[unstable(feature = "integer_sign_cast", issue = "125882")]
#[must_use = "this returns the result of the operation, \
without modifying the original"]
#[inline(always)]
pub const fn cast_signed(self) -> $SignedT {
self as $SignedT
}
/// Shifts the bits to the left by a specified amount, `n`, /// Shifts the bits to the left by a specified amount, `n`,
/// wrapping the truncated bits to the end of the resulting integer. /// wrapping the truncated bits to the end of the resulting integer.
/// ///

View File

@ -342,7 +342,7 @@ pub unsafe trait ReverseSearcher<'a>: Searcher<'a> {
/// ///
/// `(&str)::Searcher` is not a `DoubleEndedSearcher` because /// `(&str)::Searcher` is not a `DoubleEndedSearcher` because
/// the pattern `"aa"` in the haystack `"aaa"` matches as either /// the pattern `"aa"` in the haystack `"aaa"` matches as either
/// `"[aa]a"` or `"a[aa]"`, depending from which side it is searched. /// `"[aa]a"` or `"a[aa]"`, depending on which side it is searched.
pub trait DoubleEndedSearcher<'a>: ReverseSearcher<'a> {} pub trait DoubleEndedSearcher<'a>: ReverseSearcher<'a> {}
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////

View File

@ -729,7 +729,7 @@ assume_usize_width! {
} }
macro_rules! test_float { macro_rules! test_float {
($modname: ident, $fty: ty, $inf: expr, $neginf: expr, $nan: expr, $min: expr, $max: expr, $min_pos: expr) => { ($modname: ident, $fty: ty, $inf: expr, $neginf: expr, $nan: expr, $min: expr, $max: expr, $min_pos: expr, $max_exp:expr) => {
mod $modname { mod $modname {
#[test] #[test]
fn min() { fn min() {
@ -880,6 +880,27 @@ macro_rules! test_float {
assert!(($nan as $fty).midpoint(1.0).is_nan()); assert!(($nan as $fty).midpoint(1.0).is_nan());
assert!((1.0 as $fty).midpoint($nan).is_nan()); assert!((1.0 as $fty).midpoint($nan).is_nan());
assert!(($nan as $fty).midpoint($nan).is_nan()); assert!(($nan as $fty).midpoint($nan).is_nan());
// test if large differences in magnitude are still correctly computed.
// NOTE: that because of how small x and y are, x + y can never overflow
// so (x + y) / 2.0 is always correct
// in particular, `2.pow(i)` will never be at the max exponent, so it could
// be safely doubled, while j is significantly smaller.
for i in $max_exp.saturating_sub(64)..$max_exp {
for j in 0..64u8 {
let large = <$fty>::from(2.0f32).powi(i);
// a much smaller number, such that there is no chance of overflow to test
// potential double rounding in midpoint's implementation.
let small = <$fty>::from(2.0f32).powi($max_exp - 1)
* <$fty>::EPSILON
* <$fty>::from(j);
let naive = (large + small) / 2.0;
let midpoint = large.midpoint(small);
assert_eq!(naive, midpoint);
}
}
} }
#[test] #[test]
fn rem_euclid() { fn rem_euclid() {
@ -912,7 +933,8 @@ test_float!(
f32::NAN, f32::NAN,
f32::MIN, f32::MIN,
f32::MAX, f32::MAX,
f32::MIN_POSITIVE f32::MIN_POSITIVE,
f32::MAX_EXP
); );
test_float!( test_float!(
f64, f64,
@ -922,5 +944,6 @@ test_float!(
f64::NAN, f64::NAN,
f64::MIN, f64::MIN,
f64::MAX, f64::MAX,
f64::MIN_POSITIVE f64::MIN_POSITIVE,
f64::MAX_EXP
); );

View File

@ -1,7 +1,7 @@
//! Thread local support for platforms with native TLS. //! Thread local support for platforms with native TLS.
//! //!
//! To achieve the best performance, we choose from four different types for //! To achieve the best performance, we choose from four different types for
//! the TLS variable, depending from the method of initialization used (`const` //! the TLS variable, depending on the method of initialization used (`const`
//! or lazy) and the drop requirements of the stored type: //! or lazy) and the drop requirements of the stored type:
//! //!
//! | | `Drop` | `!Drop` | //! | | `Drop` | `!Drop` |

View File

@ -57,6 +57,18 @@ pub struct Error {
pub msg: String, pub msg: String,
} }
impl Error {
pub fn render_for_expected(&self) -> String {
use colored::Colorize;
format!(
"{: <10}line {: >3}: {}",
self.kind.map(|kind| kind.to_string()).unwrap_or_default().to_uppercase(),
self.line_num,
self.msg.cyan(),
)
}
}
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum WhichLine { enum WhichLine {
ThisLine, ThisLine,

View File

@ -1,10 +1,18 @@
use std::{env, sync::Arc}; use std::{env, io::IsTerminal, sync::Arc};
use compiletest::{common::Mode, log_config, parse_config, run_tests}; use compiletest::{common::Mode, log_config, parse_config, run_tests};
fn main() { fn main() {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
// colored checks stdout by default, but for some reason only stderr is a terminal.
// compiletest *does* print many things to stdout, but it doesn't really matter.
if std::io::stderr().is_terminal()
&& matches!(std::env::var("NO_COLOR").as_deref(), Err(_) | Ok("0"))
{
colored::control::set_override(true);
}
let config = Arc::new(parse_config(env::args().collect())); let config = Arc::new(parse_config(env::args().collect()));
if config.valgrind_path.is_none() && config.force_valgrind { if config.valgrind_path.is_none() && config.force_valgrind {

View File

@ -17,10 +17,10 @@ use crate::json;
use crate::read2::{read2_abbreviated, Truncated}; use crate::read2::{read2_abbreviated, Truncated};
use crate::util::{add_dylib_path, dylib_env_var, logv, PathBufExt}; use crate::util::{add_dylib_path, dylib_env_var, logv, PathBufExt};
use crate::ColorConfig; use crate::ColorConfig;
use colored::Colorize;
use miropt_test_tools::{files_for_miropt_test, MiroptTest, MiroptTestFile}; use miropt_test_tools::{files_for_miropt_test, MiroptTest, MiroptTestFile};
use regex::{Captures, Regex}; use regex::{Captures, Regex};
use rustfix::{apply_suggestions, get_suggestions_from_json, Filter}; use rustfix::{apply_suggestions, get_suggestions_from_json, Filter};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::env; use std::env;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
@ -1493,14 +1493,22 @@ impl<'test> TestCx<'test> {
unexpected.len(), unexpected.len(),
not_found.len() not_found.len()
)); ));
println!("status: {}\ncommand: {}", proc_res.status, proc_res.cmdline); println!("status: {}\ncommand: {}\n", proc_res.status, proc_res.cmdline);
if !unexpected.is_empty() { if !unexpected.is_empty() {
println!("unexpected errors (from JSON output): {:#?}\n", unexpected); println!("{}", "--- unexpected errors (from JSON output) ---".green());
for error in &unexpected {
println!("{}", error.render_for_expected());
}
println!("{}", "---".green());
} }
if !not_found.is_empty() { if !not_found.is_empty() {
println!("not found errors (from test file): {:#?}\n", not_found); println!("{}", "--- not found errors (from test file) ---".red());
for error in &not_found {
println!("{}", error.render_for_expected());
}
println!("{}", "---\n".red());
} }
panic!(); panic!("errors differ from expected");
} }
} }
@ -3435,13 +3443,13 @@ impl<'test> TestCx<'test> {
// ``` // ```
// base_dir/ // base_dir/
// rmake.exe // rmake.exe
// scratch/ // rmake_out/
// ``` // ```
// having the executable separate from the scratch directory allows the recipes to // having the executable separate from the output artifacts directory allows the recipes to
// `remove_dir_all(scratch)` without running into permission denied issues because // `remove_dir_all($TMPDIR)` without running into permission denied issues because
// the executable is not under the `scratch/` directory. // the executable is not under the `rmake_out/` directory.
// //
// This setup diverges from legacy Makefile run-make tests. // This setup intentionally diverges from legacy Makefile run-make tests.
let base_dir = cwd.join(self.output_base_name()); let base_dir = cwd.join(self.output_base_name());
if base_dir.exists() { if base_dir.exists() {
self.aggressive_rm_rf(&base_dir).unwrap(); self.aggressive_rm_rf(&base_dir).unwrap();

View File

@ -123,12 +123,23 @@ pub fn dynamic_lib_name(name: &str) -> String {
// ``` // ```
assert!(!name.contains(char::is_whitespace), "dynamic library name cannot contain whitespace"); assert!(!name.contains(char::is_whitespace), "dynamic library name cannot contain whitespace");
let extension = dynamic_lib_extension();
if is_darwin() { if is_darwin() {
format!("lib{name}.dylib") format!("lib{name}.{extension}")
} else if is_windows() { } else if is_windows() {
format!("{name}.dll") format!("{name}.{extension}")
} else { } else {
format!("lib{name}.so") format!("lib{name}.{extension}")
}
}
pub fn dynamic_lib_extension() -> &'static str {
if is_darwin() {
"dylib"
} else if is_windows() {
"dll"
} else {
"so"
} }
} }
@ -249,16 +260,13 @@ pub fn recursive_diff(dir1: impl AsRef<Path>, dir2: impl AsRef<Path>) {
} }
let dir2 = dir2.as_ref(); let dir2 = dir2.as_ref();
for entry in fs::read_dir(dir1).unwrap() { read_dir(dir1, |entry_path| {
let entry = entry.unwrap(); let entry_name = entry_path.file_name().unwrap();
let entry_name = entry.file_name(); if entry_path.is_dir() {
let path = entry.path(); recursive_diff(&entry_path, &dir2.join(entry_name));
if path.is_dir() {
recursive_diff(&path, &dir2.join(entry_name));
} else { } else {
let path2 = dir2.join(entry_name); let path2 = dir2.join(entry_name);
let file1 = read_file(&path); let file1 = read_file(&entry_path);
let file2 = read_file(&path2); let file2 = read_file(&path2);
// We don't use `assert_eq!` because they are `Vec<u8>`, so not great for display. // We don't use `assert_eq!` because they are `Vec<u8>`, so not great for display.
@ -267,10 +275,16 @@ pub fn recursive_diff(dir1: impl AsRef<Path>, dir2: impl AsRef<Path>) {
assert!( assert!(
file1 == file2, file1 == file2,
"`{}` and `{}` have different content", "`{}` and `{}` have different content",
path.display(), entry_path.display(),
path2.display(), path2.display(),
); );
} }
});
}
pub fn read_dir<F: Fn(&Path)>(dir: impl AsRef<Path>, callback: F) {
for entry in fs::read_dir(dir).unwrap() {
callback(&entry.unwrap().path());
} }
} }

View File

@ -6,7 +6,6 @@ run-make/bare-outfile/Makefile
run-make/branch-protection-check-IBT/Makefile run-make/branch-protection-check-IBT/Makefile
run-make/c-dynamic-dylib/Makefile run-make/c-dynamic-dylib/Makefile
run-make/c-dynamic-rlib/Makefile run-make/c-dynamic-rlib/Makefile
run-make/c-link-to-rust-dylib/Makefile
run-make/c-static-dylib/Makefile run-make/c-static-dylib/Makefile
run-make/c-static-rlib/Makefile run-make/c-static-rlib/Makefile
run-make/c-unwind-abi-catch-lib-panic/Makefile run-make/c-unwind-abi-catch-lib-panic/Makefile

View File

@ -1,21 +0,0 @@
# This test checks that C linking with Rust does not encounter any errors, with dynamic libraries.
# See https://github.com/rust-lang/rust/issues/10434
# ignore-cross-compile
include ../tools.mk
all: $(TMPDIR)/$(call BIN,bar)
$(call RUN,bar)
$(call REMOVE_DYLIBS,foo)
$(call FAIL,bar)
ifdef IS_MSVC
$(TMPDIR)/$(call BIN,bar): $(call DYLIB,foo)
$(CC) bar.c $(TMPDIR)/foo.dll.lib $(call OUT_EXE,bar)
else
$(TMPDIR)/$(call BIN,bar): $(call DYLIB,foo)
$(CC) bar.c -lfoo -o $(call RUN_BINFILE,bar) -L $(TMPDIR)
endif
$(call DYLIB,foo): foo.rs
$(RUSTC) foo.rs

View File

@ -0,0 +1,41 @@
// This test checks that C linking with Rust does not encounter any errors, with dynamic libraries.
// See <https://github.com/rust-lang/rust/issues/10434>.
//@ ignore-cross-compile
use std::fs::remove_file;
use run_make_support::{
cc, dynamic_lib_extension, is_msvc, read_dir, run, run_fail, rustc, tmp_dir,
};
fn main() {
rustc().input("foo.rs").run();
if is_msvc() {
let lib = tmp_dir().join("foo.dll.lib");
cc().input("bar.c").arg(lib).out_exe("bar").run();
} else {
cc().input("bar.c")
.arg("-lfoo")
.output(tmp_dir().join("bar"))
.library_search_path(tmp_dir())
.run();
}
run("bar");
let expected_extension = dynamic_lib_extension();
read_dir(tmp_dir(), |path| {
if path.is_file()
&& path.extension().is_some_and(|ext| ext == expected_extension)
&& path.file_name().and_then(|name| name.to_str()).is_some_and(|name| {
name.ends_with(".so") || name.ends_with(".dll") || name.ends_with(".dylib")
})
{
remove_file(path).unwrap();
}
});
run_fail("bar");
}