diff options
| author | The Miri Cronjob Bot <miri@cron.bot> | 2025-06-04 05:02:17 +0000 |
|---|---|---|
| committer | The Miri Cronjob Bot <miri@cron.bot> | 2025-06-04 05:02:17 +0000 |
| commit | db720882b2e193edb002019e16397088175d21a2 (patch) | |
| tree | efd0e5b7d344ea8f0bbc6e661bed96c17366dac2 /src/tools | |
| parent | beff63bb98effefbc9204ff52a66a92545120786 (diff) | |
| parent | 7742d0e230b602a0a73d38b32de47449f3306450 (diff) | |
| download | rust-db720882b2e193edb002019e16397088175d21a2.tar.gz rust-db720882b2e193edb002019e16397088175d21a2.zip | |
Merge from rustc
Diffstat (limited to 'src/tools')
99 files changed, 3459 insertions, 667 deletions
diff --git a/src/tools/clippy/clippy_lints/src/disallowed_types.rs b/src/tools/clippy/clippy_lints/src/disallowed_types.rs index d0b2f0c8407..821bb25d2ce 100644 --- a/src/tools/clippy/clippy_lints/src/disallowed_types.rs +++ b/src/tools/clippy/clippy_lints/src/disallowed_types.rs @@ -106,8 +106,8 @@ impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]); impl<'tcx> LateLintPass<'tcx> for DisallowedTypes { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { if let ItemKind::Use(path, UseKind::Single(_)) = &item.kind { - for res in &path.res { - self.check_res_emit(cx, res, item.span); + if let Some(res) = path.res.type_ns { + self.check_res_emit(cx, &res, item.span); } } } diff --git a/src/tools/clippy/clippy_lints/src/legacy_numeric_constants.rs b/src/tools/clippy/clippy_lints/src/legacy_numeric_constants.rs index 01b49403cac..b3c63f022d3 100644 --- a/src/tools/clippy/clippy_lints/src/legacy_numeric_constants.rs +++ b/src/tools/clippy/clippy_lints/src/legacy_numeric_constants.rs @@ -51,7 +51,9 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { // so lint on the `use` statement directly. if let ItemKind::Use(path, kind @ (UseKind::Single(_) | UseKind::Glob)) = item.kind && !item.span.in_external_macro(cx.sess().source_map()) - && let Some(def_id) = path.res[0].opt_def_id() + // use `present_items` because it could be in either type_ns or value_ns + && let Some(res) = path.res.present_items().next() + && let Some(def_id) = res.opt_def_id() && self.msrv.meets(cx, msrvs::NUMERIC_ASSOCIATED_CONSTANTS) { let module = if is_integer_module(cx, def_id) { diff --git a/src/tools/clippy/clippy_lints/src/macro_use.rs b/src/tools/clippy/clippy_lints/src/macro_use.rs index 98ad1f6a160..c1a26c5a9c7 100644 --- a/src/tools/clippy/clippy_lints/src/macro_use.rs +++ b/src/tools/clippy/clippy_lints/src/macro_use.rs @@ -100,10 +100,7 @@ impl LateLintPass<'_> for MacroUseImports { && let hir_id = item.hir_id() && let attrs = cx.tcx.hir_attrs(hir_id) && let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use)) - && let Some(id) = path.res.iter().find_map(|res| match res { - Res::Def(DefKind::Mod, id) => Some(id), - _ => None, - }) + && let Some(Res::Def(DefKind::Mod, id)) = path.res.type_ns && !id.is_local() { for kid in cx.tcx.module_children(id) { diff --git a/src/tools/clippy/clippy_lints/src/min_ident_chars.rs b/src/tools/clippy/clippy_lints/src/min_ident_chars.rs index 00ea9bba0d1..99f01c8001a 100644 --- a/src/tools/clippy/clippy_lints/src/min_ident_chars.rs +++ b/src/tools/clippy/clippy_lints/src/min_ident_chars.rs @@ -131,8 +131,9 @@ impl Visitor<'_> for IdentVisitor<'_, '_> { // If however the identifier is different, this means it is an alias (`use foo::bar as baz`). In // this case, we need to emit the warning for `baz`. if let Some(imported_item_path) = usenode - && let Some(Res::Def(_, imported_item_defid)) = imported_item_path.res.first() - && cx.tcx.item_name(*imported_item_defid).as_str() == str + // use `present_items` because it could be in any of type_ns, value_ns, macro_ns + && let Some(Res::Def(_, imported_item_defid)) = imported_item_path.res.value_ns + && cx.tcx.item_name(imported_item_defid).as_str() == str { return; } diff --git a/src/tools/clippy/clippy_lints/src/missing_enforced_import_rename.rs b/src/tools/clippy/clippy_lints/src/missing_enforced_import_rename.rs index a1e621cc9f6..eeea6dfd5f4 100644 --- a/src/tools/clippy/clippy_lints/src/missing_enforced_import_rename.rs +++ b/src/tools/clippy/clippy_lints/src/missing_enforced_import_rename.rs @@ -72,7 +72,8 @@ impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]); impl LateLintPass<'_> for ImportRename { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let ItemKind::Use(path, UseKind::Single(_)) = &item.kind { - for &res in &path.res { + // use `present_items` because it could be in any of type_ns, value_ns, macro_ns + for res in path.res.present_items() { if let Res::Def(_, id) = res && let Some(name) = self.renames.get(&id) // Remove semicolon since it is not present for nested imports diff --git a/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs b/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs index 7b381fac5f1..3828aff4164 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_pub_crate.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::HasSession; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Item, ItemKind}; +use rustc_hir::{Item, ItemKind, UseKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::impl_lint_pass; @@ -49,7 +49,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate { if cx.tcx.visibility(item.owner_id.def_id) == ty::Visibility::Restricted(CRATE_DEF_ID.to_def_id()) && !cx.effective_visibilities.is_exported(item.owner_id.def_id) && self.is_exported.last() == Some(&false) - && is_not_macro_export(item) + && !is_ignorable_export(item) && !item.span.in_external_macro(cx.sess().source_map()) { let span = item @@ -86,18 +86,17 @@ impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate { } } -fn is_not_macro_export<'tcx>(item: &'tcx Item<'tcx>) -> bool { - if let ItemKind::Use(path, _) = item.kind { - if path - .res - .iter() - .all(|res| matches!(res, Res::Def(DefKind::Macro(MacroKind::Bang), _))) - { - return false; +// We ignore macro exports. And `ListStem` uses, which aren't interesting. +fn is_ignorable_export<'tcx>(item: &'tcx Item<'tcx>) -> bool { + if let ItemKind::Use(path, kind) = item.kind { + let ignore = matches!(path.res.macro_ns, Some(Res::Def(DefKind::Macro(MacroKind::Bang), _))) + || kind == UseKind::ListStem; + if ignore { + return true; } } else if let ItemKind::Macro(..) = item.kind { - return false; + return true; } - true + false } diff --git a/src/tools/clippy/clippy_lints/src/unused_trait_names.rs b/src/tools/clippy/clippy_lints/src/unused_trait_names.rs index 14ac65cf4df..610cec7b8c8 100644 --- a/src/tools/clippy/clippy_lints/src/unused_trait_names.rs +++ b/src/tools/clippy/clippy_lints/src/unused_trait_names.rs @@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedTraitNames { // Ignore imports that already use Underscore && ident.name != kw::Underscore // Only check traits - && let Some(Res::Def(DefKind::Trait, _)) = path.res.first() + && let Some(Res::Def(DefKind::Trait, _)) = path.res.type_ns && cx.tcx.maybe_unused_trait_imports(()).contains(&item.owner_id.def_id) // Only check this import if it is visible to its module only (no pub, pub(crate), ...) && let module = cx.tcx.parent_module_from_def_id(item.owner_id.def_id) diff --git a/src/tools/clippy/clippy_lints/src/wildcard_imports.rs b/src/tools/clippy/clippy_lints/src/wildcard_imports.rs index 45a5dbabeb4..467811c586b 100644 --- a/src/tools/clippy/clippy_lints/src/wildcard_imports.rs +++ b/src/tools/clippy/clippy_lints/src/wildcard_imports.rs @@ -169,8 +169,8 @@ impl LateLintPass<'_> for WildcardImports { format!("{import_source_snippet}::{imports_string}") }; - // Glob imports always have a single resolution. - let (lint, message) = if let Res::Def(DefKind::Enum, _) = use_path.res[0] { + // Glob imports always have a single resolution. Enums are in the value namespace. + let (lint, message) = if let Some(Res::Def(DefKind::Enum, _)) = use_path.res.value_ns { (ENUM_GLOB_USE, "usage of wildcard import for enum variants") } else { (WILDCARD_IMPORTS, "usage of wildcard import") diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs index 9d7f3086b05..f37a609497e 100644 --- a/src/tools/clippy/clippy_utils/src/paths.rs +++ b/src/tools/clippy/clippy_utils/src/paths.rs @@ -306,10 +306,13 @@ fn local_item_child_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, ns: PathNS, n let item = tcx.hir_item(item_id); if let ItemKind::Use(path, UseKind::Single(ident)) = item.kind { if ident.name == name { - path.res - .iter() - .find(|res| ns.matches(res.ns())) - .and_then(Res::opt_def_id) + let opt_def_id = |ns: Option<Res>| ns.and_then(|res| res.opt_def_id()); + match ns { + PathNS::Type => opt_def_id(path.res.type_ns), + PathNS::Value => opt_def_id(path.res.value_ns), + PathNS::Macro => opt_def_id(path.res.macro_ns), + PathNS::Arbitrary => unreachable!(), + } } else { None } diff --git a/src/tools/clippy/tests/ui/indexing_slicing_index.rs b/src/tools/clippy/tests/ui/indexing_slicing_index.rs index cfa1c2f7c75..ab6a8235008 100644 --- a/src/tools/clippy/tests/ui/indexing_slicing_index.rs +++ b/src/tools/clippy/tests/ui/indexing_slicing_index.rs @@ -68,7 +68,7 @@ fn main() { // This should be linted, since `suppress-restriction-lint-in-const` default is false. const { &ARR[idx4()] }; //~^ ERROR: indexing may panic - //~| ERROR: evaluation of `main + //~| ERROR: index out of bounds let y = &x; // Ok, referencing shouldn't affect this lint. See the issue 6021 diff --git a/src/tools/clippy/tests/ui/indexing_slicing_index.stderr b/src/tools/clippy/tests/ui/indexing_slicing_index.stderr index 50ee9b9edc7..8e24b898ed5 100644 --- a/src/tools/clippy/tests/ui/indexing_slicing_index.stderr +++ b/src/tools/clippy/tests/ui/indexing_slicing_index.stderr @@ -9,11 +9,11 @@ LL | const REF: &i32 = &ARR[idx()]; // This should be linted, since `suppress-re = note: `-D clippy::indexing-slicing` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::indexing_slicing)]` -error[E0080]: evaluation of `main::{constant#3}` failed +error[E0080]: index out of bounds: the length is 2 but the index is 4 --> tests/ui/indexing_slicing_index.rs:69:14 | LL | const { &ARR[idx4()] }; - | ^^^^^^^^^^^ index out of bounds: the length is 2 but the index is 4 + | ^^^^^^^^^^^ evaluation of `main::{constant#3}` failed here note: erroneous constant encountered --> tests/ui/indexing_slicing_index.rs:69:5 diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index dcc74b099d6..20ea239b7e5 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -3,7 +3,7 @@ use std::time::Duration; use std::{cmp, iter}; use rand::RngCore; -use rustc_abi::{Align, ExternAbi, FieldIdx, FieldsShape, Size, Variants}; +use rustc_abi::{Align, CanonAbi, ExternAbi, FieldIdx, FieldsShape, Size, Variants}; use rustc_apfloat::Float; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; use rustc_hir::Safety; @@ -18,7 +18,7 @@ use rustc_middle::ty::{self, Binder, FloatTy, FnSig, IntTy, Ty, TyCtxt, UintTy}; use rustc_session::config::CrateType; use rustc_span::{Span, Symbol}; use rustc_symbol_mangling::mangle_internal_symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::*; @@ -936,11 +936,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn check_callconv<'a>( &self, fn_abi: &FnAbi<'tcx, Ty<'tcx>>, - exp_abi: Conv, + exp_abi: CanonAbi, ) -> InterpResult<'a, ()> { if fn_abi.conv != exp_abi { throw_ub_format!( - "calling a function with calling convention {exp_abi} using caller calling convention {}", + r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#, fn_abi.conv ); } @@ -973,7 +973,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn check_abi_and_shim_symbol_clash( &mut self, abi: &FnAbi<'tcx, Ty<'tcx>>, - exp_abi: Conv, + exp_abi: CanonAbi, link_name: Symbol, ) -> InterpResult<'tcx, ()> { self.check_callconv(abi, exp_abi)?; @@ -998,7 +998,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn check_shim<'a, const N: usize>( &mut self, abi: &FnAbi<'tcx, Ty<'tcx>>, - exp_abi: Conv, + exp_abi: CanonAbi, link_name: Symbol, args: &'a [OpTy<'tcx>], ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { @@ -1098,7 +1098,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn check_shim_variadic<'a, const N: usize>( &mut self, abi: &FnAbi<'tcx, Ty<'tcx>>, - exp_abi: Conv, + exp_abi: CanonAbi, link_name: Symbol, args: &'a [OpTy<'tcx>], ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])> diff --git a/src/tools/miri/src/shims/aarch64.rs b/src/tools/miri/src/shims/aarch64.rs index 7cccc9e51d8..44ad5081ad5 100644 --- a/src/tools/miri/src/shims/aarch64.rs +++ b/src/tools/miri/src/shims/aarch64.rs @@ -1,7 +1,8 @@ +use rustc_abi::CanonAbi; use rustc_middle::mir::BinOp; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::*; @@ -19,7 +20,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let unprefixed_name = link_name.as_str().strip_prefix("llvm.aarch64.").unwrap(); match unprefixed_name { "isb" => { - let [arg] = this.check_shim(abi, Conv::C, link_name, args)?; + let [arg] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let arg = this.read_scalar(arg)?.to_i32()?; match arg { // SY ("full system scope") @@ -37,7 +38,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `left` input, the second half of the output from the `right` input. // https://developer.arm.com/architectures/instruction-sets/intrinsics/vpmaxq_u8 "neon.umaxp.v16i8" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index 9f3bc06771f..ab11553df63 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -1,8 +1,8 @@ -use rustc_abi::Size; +use rustc_abi::{CanonAbi, Size}; use rustc_middle::ty::layout::LayoutOf as _; use rustc_middle::ty::{self, Instance, Ty}; use rustc_span::{BytePos, Loc, Symbol, hygiene}; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::*; @@ -16,7 +16,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [flags] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [flags] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let flags = this.read_scalar(flags)?.to_u64()?; if flags != 0 { @@ -38,7 +38,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let ptr_ty = this.machine.layouts.mut_raw_ptr.ty; let ptr_layout = this.layout_of(ptr_ty)?; - let [flags, buf] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [flags, buf] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let flags = this.read_scalar(flags)?.to_u64()?; let buf_place = this.deref_pointer_as(buf, ptr_layout)?; @@ -118,7 +118,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let [ptr, flags] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [ptr, flags] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let flags = this.read_scalar(flags)?.to_u64()?; @@ -190,7 +190,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let this = self.eval_context_mut(); let [ptr, flags, name_ptr, filename_ptr] = - this.check_shim(abi, Conv::Rust, link_name, args)?; + this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let flags = this.read_scalar(flags)?.to_u64()?; if flags != 0 { diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index b08b522d279..ae04ca018ab 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -2,7 +2,7 @@ use std::collections::hash_map::Entry; use std::io::Write; use std::path::Path; -use rustc_abi::{Align, AlignFromBytesError, Size}; +use rustc_abi::{Align, AlignFromBytesError, CanonAbi, Size}; use rustc_apfloat::Float; use rustc_ast::expand::allocator::alloc_error_handler_name; use rustc_hir::def::DefKind; @@ -12,7 +12,7 @@ use rustc_middle::mir::interpret::AllocInit; use rustc_middle::ty::{Instance, Ty}; use rustc_middle::{mir, ty}; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use self::helpers::{ToHost, ToSoft}; use super::alloc::EvalContextExt as _; @@ -250,7 +250,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // When adding a new shim, you should follow the following pattern: // ``` // "shim_name" => { - // let [arg1, arg2, arg3] = this.check_shim(abi, Conv::::C , link_name, args)?; + // let [arg1, arg2, arg3] = this.check_shim(abi, CanonAbi::C , link_name, args)?; // let result = this.shim_name(arg1, arg2, arg3)?; // this.write_scalar(result, dest)?; // } @@ -288,16 +288,16 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // Miri-specific extern functions "miri_start_unwind" => { - let [payload] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [payload] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; this.handle_miri_start_unwind(payload)?; return interp_ok(EmulateItemResult::NeedsUnwind); } "miri_run_provenance_gc" => { - let [] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; this.run_provenance_gc(); } "miri_get_alloc_id" => { - let [ptr] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [ptr] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let ptr = this.read_pointer(ptr)?; let (alloc_id, _, _) = this.ptr_get_alloc_id(ptr, 0).map_err_kind(|_e| { err_machine_stop!(TerminationInfo::Abort(format!( @@ -307,7 +307,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_u64(alloc_id.0.get()), dest)?; } "miri_print_borrow_state" => { - let [id, show_unnamed] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [id, show_unnamed] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let id = this.read_scalar(id)?.to_u64()?; let show_unnamed = this.read_scalar(show_unnamed)?.to_bool()?; if let Some(id) = std::num::NonZero::new(id).map(AllocId) @@ -321,7 +321,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "miri_pointer_name" => { // This associates a name to a tag. Very useful for debugging, and also makes // tests more strict. - let [ptr, nth_parent, name] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [ptr, nth_parent, name] = + this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let ptr = this.read_pointer(ptr)?; let nth_parent = this.read_scalar(nth_parent)?.to_u8()?; let name = this.read_immediate(name)?; @@ -334,7 +335,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.give_pointer_debug_name(ptr, nth_parent, &name)?; } "miri_static_root" => { - let [ptr] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [ptr] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let ptr = this.read_pointer(ptr)?; let (alloc_id, offset, _) = this.ptr_get_alloc_id(ptr, 0)?; if offset != Size::ZERO { @@ -345,7 +346,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.machine.static_roots.push(alloc_id); } "miri_host_to_target_path" => { - let [ptr, out, out_size] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [ptr, out, out_size] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let ptr = this.read_pointer(ptr)?; let out = this.read_pointer(out)?; let out_size = this.read_scalar(out_size)?.to_target_usize(this)?; @@ -381,7 +382,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Writes some bytes to the interpreter's stdout/stderr. See the // README for details. "miri_write_to_stdout" | "miri_write_to_stderr" => { - let [msg] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [msg] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let msg = this.read_immediate(msg)?; let msg = this.read_byte_slice(&msg)?; // Note: we're ignoring errors writing to host stdout/stderr. @@ -395,7 +396,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { "miri_promise_symbolic_alignment" => { use rustc_abi::AlignFromBytesError; - let [ptr, align] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [ptr, align] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let ptr = this.read_pointer(ptr)?; let align = this.read_target_usize(align)?; if !align.is_power_of_two() { @@ -436,12 +437,12 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Aborting the process. "exit" => { - let [code] = this.check_shim(abi, Conv::C, link_name, args)?; + let [code] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let code = this.read_scalar(code)?.to_i32()?; throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false }); } "abort" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; throw_machine_stop!(TerminationInfo::Abort( "the program aborted execution".to_owned() )) @@ -449,7 +450,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Standard C allocation "malloc" => { - let [size] = this.check_shim(abi, Conv::C, link_name, args)?; + let [size] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let size = this.read_target_usize(size)?; if size <= this.max_size_of_val().bytes() { let res = this.malloc(size, AllocInit::Uninit)?; @@ -463,7 +464,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "calloc" => { - let [items, elem_size] = this.check_shim(abi, Conv::C, link_name, args)?; + let [items, elem_size] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let items = this.read_target_usize(items)?; let elem_size = this.read_target_usize(elem_size)?; if let Some(size) = this.compute_size_in_bytes(Size::from_bytes(elem_size), items) { @@ -478,12 +479,12 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "free" => { - let [ptr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; this.free(ptr)?; } "realloc" => { - let [old_ptr, new_size] = this.check_shim(abi, Conv::C, link_name, args)?; + let [old_ptr, new_size] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let old_ptr = this.read_pointer(old_ptr)?; let new_size = this.read_target_usize(new_size)?; if new_size <= this.max_size_of_val().bytes() { @@ -503,7 +504,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { let default = |ecx: &mut MiriInterpCx<'tcx>| { // Only call `check_shim` when `#[global_allocator]` isn't used. When that // macro is used, we act like no shim exists, so that the exported function can run. - let [size, align] = ecx.check_shim(abi, Conv::Rust, link_name, args)?; + let [size, align] = ecx.check_shim(abi, CanonAbi::Rust, link_name, args)?; let size = ecx.read_target_usize(size)?; let align = ecx.read_target_usize(align)?; @@ -536,7 +537,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { return this.emulate_allocator(|this| { // See the comment for `__rust_alloc` why `check_shim` is only called in the // default case. - let [size, align] = this.check_shim(abi, Conv::Rust, link_name, args)?; + let [size, align] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let size = this.read_target_usize(size)?; let align = this.read_target_usize(align)?; @@ -558,7 +559,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // See the comment for `__rust_alloc` why `check_shim` is only called in the // default case. let [ptr, old_size, align] = - ecx.check_shim(abi, Conv::Rust, link_name, args)?; + ecx.check_shim(abi, CanonAbi::Rust, link_name, args)?; let ptr = ecx.read_pointer(ptr)?; let old_size = ecx.read_target_usize(old_size)?; let align = ecx.read_target_usize(align)?; @@ -589,7 +590,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // See the comment for `__rust_alloc` why `check_shim` is only called in the // default case. let [ptr, old_size, align, new_size] = - this.check_shim(abi, Conv::Rust, link_name, args)?; + this.check_shim(abi, CanonAbi::Rust, link_name, args)?; let ptr = this.read_pointer(ptr)?; let old_size = this.read_target_usize(old_size)?; let align = this.read_target_usize(align)?; @@ -613,7 +614,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // C memory handling functions "memcmp" => { - let [left, right, n] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, n] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let left = this.read_pointer(left)?; let right = this.read_pointer(right)?; let n = Size::from_bytes(this.read_target_usize(n)?); @@ -637,7 +638,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(Scalar::from_i32(result), dest)?; } "memrchr" => { - let [ptr, val, num] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr, val, num] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let val = this.read_scalar(val)?.to_i32()?; let num = this.read_target_usize(num)?; @@ -663,7 +664,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "memchr" => { - let [ptr, val, num] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr, val, num] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let val = this.read_scalar(val)?.to_i32()?; let num = this.read_target_usize(num)?; @@ -686,7 +687,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } } "strlen" => { - let [ptr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; // This reads at least 1 byte, so we are already enforcing that this is a valid pointer. let n = this.read_c_str(ptr)?.len(); @@ -696,7 +697,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; } "wcslen" => { - let [ptr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; // This reads at least 1 byte, so we are already enforcing that this is a valid pointer. let n = this.read_wchar_t_str(ptr)?.len(); @@ -706,7 +707,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; } "memcpy" => { - let [ptr_dest, ptr_src, n] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr_dest, ptr_src, n] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr_dest = this.read_pointer(ptr_dest)?; let ptr_src = this.read_pointer(ptr_src)?; let n = this.read_target_usize(n)?; @@ -720,7 +721,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_pointer(ptr_dest, dest)?; } "strcpy" => { - let [ptr_dest, ptr_src] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr_dest, ptr_src] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr_dest = this.read_pointer(ptr_dest)?; let ptr_src = this.read_pointer(ptr_src)?; @@ -751,7 +752,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { | "erff" | "erfcf" => { - let [f] = this.check_shim(abi, Conv::C , link_name, args)?; + let [f] = this.check_shim(abi, CanonAbi::C , link_name, args)?; let f = this.read_scalar(f)?.to_f32()?; // Using host floats (but it's fine, these operations do not have guaranteed precision). let f_host = f.to_host(); @@ -789,7 +790,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { | "atan2f" | "fdimf" => { - let [f1, f2] = this.check_shim(abi, Conv::C , link_name, args)?; + let [f1, f2] = this.check_shim(abi, CanonAbi::C , link_name, args)?; let f1 = this.read_scalar(f1)?.to_f32()?; let f2 = this.read_scalar(f2)?.to_f32()?; // underscore case for windows, here and below @@ -828,7 +829,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { | "erf" | "erfc" => { - let [f] = this.check_shim(abi, Conv::C , link_name, args)?; + let [f] = this.check_shim(abi, CanonAbi::C , link_name, args)?; let f = this.read_scalar(f)?.to_f64()?; // Using host floats (but it's fine, these operations do not have guaranteed precision). let f_host = f.to_host(); @@ -866,7 +867,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { | "atan2" | "fdim" => { - let [f1, f2] = this.check_shim(abi, Conv::C , link_name, args)?; + let [f1, f2] = this.check_shim(abi, CanonAbi::C , link_name, args)?; let f1 = this.read_scalar(f1)?.to_f64()?; let f2 = this.read_scalar(f2)?.to_f64()?; // underscore case for windows, here and below @@ -895,7 +896,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { | "ldexp" | "scalbn" => { - let [x, exp] = this.check_shim(abi, Conv::C , link_name, args)?; + let [x, exp] = this.check_shim(abi, CanonAbi::C , link_name, args)?; // For radix-2 (binary) systems, `ldexp` and `scalbn` are the same. let x = this.read_scalar(x)?.to_f64()?; let exp = this.read_scalar(exp)?.to_i32()?; @@ -905,7 +906,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "lgammaf_r" => { - let [x, signp] = this.check_shim(abi, Conv::C, link_name, args)?; + let [x, signp] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let x = this.read_scalar(x)?.to_f32()?; let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?; @@ -921,7 +922,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "lgamma_r" => { - let [x, signp] = this.check_shim(abi, Conv::C, link_name, args)?; + let [x, signp] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let x = this.read_scalar(x)?.to_f64()?; let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?; @@ -939,7 +940,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // LLVM intrinsics "llvm.prefetch" => { - let [p, rw, loc, ty] = this.check_shim(abi, Conv::C, link_name, args)?; + let [p, rw, loc, ty] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let _ = this.read_pointer(p)?; let rw = this.read_scalar(rw)?.to_i32()?; @@ -966,7 +967,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { // Used to implement the x86 `_mm{,256,512}_popcnt_epi{8,16,32,64}` and wasm // `{i,u}8x16_popcnt` functions. name if name.starts_with("llvm.ctpop.v") => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (op, op_len) = this.project_to_simd(op)?; let (dest, dest_len) = this.project_to_simd(dest)?; @@ -1002,7 +1003,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } // FIXME: Move this to an `arm` submodule. "llvm.arm.hint" if this.tcx.sess.target.arch == "arm" => { - let [arg] = this.check_shim(abi, Conv::C, link_name, args)?; + let [arg] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let arg = this.read_scalar(arg)?.to_i32()?; // Note that different arguments might have different target feature requirements. match arg { diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs index 0e7cf7153f5..690b5295681 100644 --- a/src/tools/miri/src/shims/unix/android/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::shims::unix::android::thread::prctl; use crate::shims::unix::linux_like::epoll::EvalContextExt as _; @@ -25,29 +26,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // epoll, eventfd "epoll_create1" => { - let [flag] = this.check_shim(abi, Conv::C, link_name, args)?; + let [flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.epoll_create1(flag)?; this.write_scalar(result, dest)?; } "epoll_ctl" => { - let [epfd, op, fd, event] = this.check_shim(abi, Conv::C, link_name, args)?; + let [epfd, op, fd, event] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } "epoll_wait" => { let [epfd, events, maxevents, timeout] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.epoll_wait(epfd, events, maxevents, timeout, dest)?; } "eventfd" => { - let [val, flag] = this.check_shim(abi, Conv::C, link_name, args)?; + let [val, flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } // Miscellaneous "__errno" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/android/thread.rs index aa3a05ead85..5d17d6c8517 100644 --- a/src/tools/miri/src/shims/unix/android/thread.rs +++ b/src/tools/miri/src/shims/unix/android/thread.rs @@ -1,7 +1,7 @@ -use rustc_abi::Size; +use rustc_abi::{CanonAbi, Size}; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::helpers::check_min_vararg_count; use crate::shims::unix::thread::{EvalContextExt as _, ThreadNameResult}; @@ -16,7 +16,7 @@ pub fn prctl<'tcx>( args: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { - let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::C, link_name, args)?; + let ([op], varargs) = ecx.check_shim_variadic(abi, CanonAbi::C, link_name, args)?; // FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch. let pr_set_name = 15; diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 026aa1f9503..9106ef94c43 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -1,11 +1,11 @@ use std::ffi::OsStr; use std::str; -use rustc_abi::{ExternAbi, Size}; +use rustc_abi::{CanonAbi, ExternAbi, Size}; use rustc_middle::ty::Ty; use rustc_middle::ty::layout::LayoutOf; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use self::shims::unix::android::foreign_items as android; use self::shims::unix::freebsd::foreign_items as freebsd; @@ -334,7 +334,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "fcntl" => { let ([fd_num, cmd], varargs) = - this.check_shim_variadic(abi, Conv::C, link_name, args)?; + this.check_shim_variadic(abi, CanonAbi::C, link_name, args)?; let result = this.fcntl(fd_num, cmd, varargs)?; this.write_scalar(result, dest)?; } @@ -387,7 +387,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `open` is variadic, the third argument is only present when the second argument // has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set let ([path_raw, flag], varargs) = - this.check_shim_variadic(abi, Conv::C, link_name, args)?; + this.check_shim_variadic(abi, CanonAbi::C, link_name, args)?; let result = this.open(path_raw, flag, varargs)?; this.write_scalar(result, dest)?; } @@ -701,20 +701,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Allocation "posix_memalign" => { - let [memptr, align, size] = this.check_shim(abi, Conv::C, link_name, args)?; + let [memptr, align, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.posix_memalign(memptr, align, size)?; this.write_scalar(result, dest)?; } "mmap" => { let [addr, length, prot, flags, fd, offset] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?; let ptr = this.mmap(addr, length, prot, flags, fd, offset)?; this.write_scalar(ptr, dest)?; } "munmap" => { - let [addr, length] = this.check_shim(abi, Conv::C, link_name, args)?; + let [addr, length] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.munmap(addr, length)?; this.write_scalar(result, dest)?; } @@ -722,7 +722,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "reallocarray" => { // Currently this function does not exist on all Unixes, e.g. on macOS. this.check_target_os(&["linux", "freebsd", "android"], link_name)?; - let [ptr, nmemb, size] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr, nmemb, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let nmemb = this.read_target_usize(nmemb)?; let size = this.read_target_usize(size)?; @@ -745,14 +745,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "aligned_alloc" => { // This is a C11 function, we assume all Unixes have it. // (MSVC explicitly does not support this.) - let [align, size] = this.check_shim(abi, Conv::C, link_name, args)?; + let [align, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let res = this.aligned_alloc(align, size)?; this.write_pointer(res, dest)?; } // Dynamic symbol loading "dlsym" => { - let [handle, symbol] = this.check_shim(abi, Conv::C, link_name, args)?; + let [handle, symbol] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.read_target_usize(handle)?; let symbol = this.read_pointer(symbol)?; let name = this.read_c_str(symbol)?; @@ -768,7 +768,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Thread-local storage "pthread_key_create" => { - let [key, dtor] = this.check_shim(abi, Conv::C, link_name, args)?; + let [key, dtor] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let key_place = this.deref_pointer_as(key, this.libc_ty_layout("pthread_key_t"))?; let dtor = this.read_pointer(dtor)?; @@ -796,21 +796,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } "pthread_key_delete" => { - let [key] = this.check_shim(abi, Conv::C, link_name, args)?; + let [key] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; this.machine.tls.delete_tls_key(key)?; // Return success (0) this.write_null(dest)?; } "pthread_getspecific" => { - let [key] = this.check_shim(abi, Conv::C, link_name, args)?; + let [key] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; let active_thread = this.active_thread(); let ptr = this.machine.tls.load_tls(key, active_thread, this)?; this.write_scalar(ptr, dest)?; } "pthread_setspecific" => { - let [key, new_ptr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [key, new_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let key = this.read_scalar(key)?.to_bits(key.layout.size)?; let active_thread = this.active_thread(); let new_data = this.read_scalar(new_ptr)?; @@ -822,156 +822,157 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Synchronization primitives "pthread_mutexattr_init" => { - let [attr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_mutexattr_init(attr)?; this.write_null(dest)?; } "pthread_mutexattr_settype" => { - let [attr, kind] = this.check_shim(abi, Conv::C, link_name, args)?; + let [attr, kind] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.pthread_mutexattr_settype(attr, kind)?; this.write_scalar(result, dest)?; } "pthread_mutexattr_destroy" => { - let [attr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_mutexattr_destroy(attr)?; this.write_null(dest)?; } "pthread_mutex_init" => { - let [mutex, attr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [mutex, attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_mutex_init(mutex, attr)?; this.write_null(dest)?; } "pthread_mutex_lock" => { - let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?; + let [mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_mutex_lock(mutex, dest)?; } "pthread_mutex_trylock" => { - let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?; + let [mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.pthread_mutex_trylock(mutex)?; this.write_scalar(result, dest)?; } "pthread_mutex_unlock" => { - let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?; + let [mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.pthread_mutex_unlock(mutex)?; this.write_scalar(result, dest)?; } "pthread_mutex_destroy" => { - let [mutex] = this.check_shim(abi, Conv::C, link_name, args)?; + let [mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_mutex_destroy(mutex)?; this.write_int(0, dest)?; } "pthread_rwlock_rdlock" => { - let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; + let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_rwlock_rdlock(rwlock, dest)?; } "pthread_rwlock_tryrdlock" => { - let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; + let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.pthread_rwlock_tryrdlock(rwlock)?; this.write_scalar(result, dest)?; } "pthread_rwlock_wrlock" => { - let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; + let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_rwlock_wrlock(rwlock, dest)?; } "pthread_rwlock_trywrlock" => { - let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; + let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.pthread_rwlock_trywrlock(rwlock)?; this.write_scalar(result, dest)?; } "pthread_rwlock_unlock" => { - let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; + let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_rwlock_unlock(rwlock)?; this.write_null(dest)?; } "pthread_rwlock_destroy" => { - let [rwlock] = this.check_shim(abi, Conv::C, link_name, args)?; + let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_rwlock_destroy(rwlock)?; this.write_null(dest)?; } "pthread_condattr_init" => { - let [attr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_condattr_init(attr)?; this.write_null(dest)?; } "pthread_condattr_setclock" => { - let [attr, clock_id] = this.check_shim(abi, Conv::C, link_name, args)?; + let [attr, clock_id] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.pthread_condattr_setclock(attr, clock_id)?; this.write_scalar(result, dest)?; } "pthread_condattr_getclock" => { - let [attr, clock_id] = this.check_shim(abi, Conv::C, link_name, args)?; + let [attr, clock_id] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_condattr_getclock(attr, clock_id)?; this.write_null(dest)?; } "pthread_condattr_destroy" => { - let [attr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_condattr_destroy(attr)?; this.write_null(dest)?; } "pthread_cond_init" => { - let [cond, attr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [cond, attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_cond_init(cond, attr)?; this.write_null(dest)?; } "pthread_cond_signal" => { - let [cond] = this.check_shim(abi, Conv::C, link_name, args)?; + let [cond] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_cond_signal(cond)?; this.write_null(dest)?; } "pthread_cond_broadcast" => { - let [cond] = this.check_shim(abi, Conv::C, link_name, args)?; + let [cond] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_cond_broadcast(cond)?; this.write_null(dest)?; } "pthread_cond_wait" => { - let [cond, mutex] = this.check_shim(abi, Conv::C, link_name, args)?; + let [cond, mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_cond_wait(cond, mutex, dest)?; } "pthread_cond_timedwait" => { - let [cond, mutex, abstime] = this.check_shim(abi, Conv::C, link_name, args)?; + let [cond, mutex, abstime] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_cond_timedwait(cond, mutex, abstime, dest)?; } "pthread_cond_destroy" => { - let [cond] = this.check_shim(abi, Conv::C, link_name, args)?; + let [cond] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_cond_destroy(cond)?; this.write_null(dest)?; } // Threading "pthread_create" => { - let [thread, attr, start, arg] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread, attr, start, arg] = + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.pthread_create(thread, attr, start, arg)?; this.write_null(dest)?; } "pthread_join" => { - let [thread, retval] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread, retval] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let res = this.pthread_join(thread, retval)?; this.write_scalar(res, dest)?; } "pthread_detach" => { - let [thread] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let res = this.pthread_detach(thread)?; this.write_scalar(res, dest)?; } "pthread_self" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let res = this.pthread_self()?; this.write_scalar(res, dest)?; } "sched_yield" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.sched_yield()?; this.write_null(dest)?; } "nanosleep" => { - let [req, rem] = this.check_shim(abi, Conv::C, link_name, args)?; + let [req, rem] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.nanosleep(req, rem)?; this.write_scalar(result, dest)?; } "sched_getaffinity" => { // Currently this function does not exist on all Unixes, e.g. on macOS. this.check_target_os(&["linux", "freebsd", "android"], link_name)?; - let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::C, link_name, args)?; + let [pid, cpusetsize, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let pid = this.read_scalar(pid)?.to_u32()?; let cpusetsize = this.read_target_usize(cpusetsize)?; let mask = this.read_pointer(mask)?; @@ -1008,7 +1009,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "sched_setaffinity" => { // Currently this function does not exist on all Unixes, e.g. on macOS. this.check_target_os(&["linux", "freebsd", "android"], link_name)?; - let [pid, cpusetsize, mask] = this.check_shim(abi, Conv::C, link_name, args)?; + let [pid, cpusetsize, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let pid = this.read_scalar(pid)?.to_u32()?; let cpusetsize = this.read_target_usize(cpusetsize)?; let mask = this.read_pointer(mask)?; @@ -1048,12 +1049,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Miscellaneous "isatty" => { - let [fd] = this.check_shim(abi, Conv::C, link_name, args)?; + let [fd] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.isatty(fd)?; this.write_scalar(result, dest)?; } "pthread_atfork" => { - let [prepare, parent, child] = this.check_shim(abi, Conv::C, link_name, args)?; + let [prepare, parent, child] = + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.read_pointer(prepare)?; this.read_pointer(parent)?; this.read_pointer(child)?; @@ -1067,7 +1069,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &["linux", "macos", "freebsd", "illumos", "solaris", "android"], link_name, )?; - let [buf, bufsize] = this.check_shim(abi, Conv::C, link_name, args)?; + let [buf, bufsize] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let buf = this.read_pointer(buf)?; let bufsize = this.read_target_usize(bufsize)?; @@ -1085,7 +1087,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "strerror_r" => { - let [errnum, buf, buflen] = this.check_shim(abi, Conv::C, link_name, args)?; + let [errnum, buf, buflen] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.strerror_r(errnum, buf, buflen)?; this.write_scalar(result, dest)?; } @@ -1097,7 +1099,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &["linux", "freebsd", "illumos", "solaris", "android"], link_name, )?; - let [ptr, len, flags] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr, len, flags] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let len = this.read_target_usize(len)?; let _flags = this.read_scalar(flags)?.to_i32()?; @@ -1109,7 +1111,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // This function is non-standard but exists with the same signature and // same behavior (eg never fails) on FreeBSD and Solaris/Illumos. this.check_target_os(&["freebsd", "illumos", "solaris"], link_name)?; - let [ptr, len] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; let len = this.read_target_usize(len)?; this.gen_random(ptr, len)?; @@ -1133,12 +1135,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { link_name, )?; // This function looks and behaves excatly like miri_start_unwind. - let [payload] = this.check_shim(abi, Conv::C, link_name, args)?; + let [payload] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.handle_miri_start_unwind(payload)?; return interp_ok(EmulateItemResult::NeedsUnwind); } "getuid" | "geteuid" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // For now, just pretend we always have this fixed UID. this.write_int(UID, dest)?; } @@ -1146,7 +1148,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_attr_getguardsize" if this.frame_in_std() => { - let [_attr, guard_size] = this.check_shim(abi, Conv::C, link_name, args)?; + let [_attr, guard_size] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let guard_size_layout = this.machine.layouts.usize; let guard_size = this.deref_pointer_as(guard_size, guard_size_layout)?; this.write_scalar( @@ -1159,11 +1161,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "pthread_attr_init" | "pthread_attr_destroy" if this.frame_in_std() => { - let [_] = this.check_shim(abi, Conv::C, link_name, args)?; + let [_] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } "pthread_attr_setstacksize" if this.frame_in_std() => { - let [_, _] = this.check_shim(abi, Conv::C, link_name, args)?; + let [_, _] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } @@ -1171,7 +1173,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // We don't support "pthread_attr_setstack", so we just pretend all stacks have the same values here. // Hence we can mostly ignore the input `attr_place`. let [attr_place, addr_place, size_place] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; let _attr_place = this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?; let addr_place = this.deref_pointer_as(addr_place, this.machine.layouts.usize)?; @@ -1191,18 +1193,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "signal" | "sigaltstack" if this.frame_in_std() => { - let [_, _] = this.check_shim(abi, Conv::C, link_name, args)?; + let [_, _] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } "sigaction" | "mprotect" if this.frame_in_std() => { - let [_, _, _] = this.check_shim(abi, Conv::C, link_name, args)?; + let [_, _, _] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } "getpwuid_r" | "__posix_getpwuid_r" if this.frame_in_std() => { // getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish let [uid, pwd, buf, buflen, result] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.check_no_isolation("`getpwuid_r`")?; let uid = this.read_scalar(uid)?.to_u32()?; diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs index 533a741fea3..42502d5bf09 100644 --- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use super::sync::EvalContextExt as _; use crate::shims::unix::*; @@ -23,7 +24,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // Threading "pthread_setname_np" => { - let [thread, name] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread, name] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let max_len = u64::MAX; // FreeBSD does not seem to have a limit. let res = match this.pthread_setname_np( this.read_scalar(thread)?, @@ -38,7 +39,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread, name, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // FreeBSD's pthread_getname_np uses strlcpy, which truncates the resulting value, // but always adds a null terminator (except for zero-sized buffers). // https://github.com/freebsd/freebsd-src/blob/c2d93a803acef634bd0eede6673aeea59e90c277/lib/libthr/thread/thr_info.c#L119-L144 @@ -59,7 +60,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "cpuset_getaffinity" => { // The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically. let [level, which, id, set_size, mask] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; let level = this.read_scalar(level)?.to_i32()?; let which = this.read_scalar(which)?.to_i32()?; @@ -123,7 +124,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Synchronization primitives "_umtx_op" => { let [obj, op, val, uaddr, uaddr2] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this._umtx_op(obj, op, val, uaddr, uaddr2, dest)?; } @@ -131,29 +132,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // For those, we both intercept `func` and `call@FBSD_1.0` symbols cases // since freebsd 12 the former form can be expected. "stat" | "stat@FBSD_1.0" => { - let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; + let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_stat(path, buf)?; this.write_scalar(result, dest)?; } "lstat" | "lstat@FBSD_1.0" => { - let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; + let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } "fstat" | "fstat@FBSD_1.0" => { - let [fd, buf] = this.check_shim(abi, Conv::C, link_name, args)?; + let [fd, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_fstat(fd, buf)?; this.write_scalar(result, dest)?; } "readdir_r" | "readdir_r@FBSD_1.0" => { - let [dirp, entry, result] = this.check_shim(abi, Conv::C, link_name, args)?; + let [dirp, entry, result] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_readdir_r(dirp, entry, result)?; this.write_scalar(result, dest)?; } // Miscellaneous "__error" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } @@ -161,7 +162,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_attr_get_np" if this.frame_in_std() => { - let [_thread, _attr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [_thread, _attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index 51c2434d68a..aeaff1cb13a 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use self::shims::unix::linux::mem::EvalContextExt as _; use self::shims::unix::linux_like::epoll::EvalContextExt as _; @@ -36,47 +37,48 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // File related shims "readdir64" => { - let [dirp] = this.check_shim(abi, Conv::C, link_name, args)?; + let [dirp] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.linux_solarish_readdir64("dirent64", dirp)?; this.write_scalar(result, dest)?; } "sync_file_range" => { - let [fd, offset, nbytes, flags] = this.check_shim(abi, Conv::C, link_name, args)?; + let [fd, offset, nbytes, flags] = + this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.sync_file_range(fd, offset, nbytes, flags)?; this.write_scalar(result, dest)?; } "statx" => { let [dirfd, pathname, flags, mask, statxbuf] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.linux_statx(dirfd, pathname, flags, mask, statxbuf)?; this.write_scalar(result, dest)?; } // epoll, eventfd "epoll_create1" => { - let [flag] = this.check_shim(abi, Conv::C, link_name, args)?; + let [flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.epoll_create1(flag)?; this.write_scalar(result, dest)?; } "epoll_ctl" => { - let [epfd, op, fd, event] = this.check_shim(abi, Conv::C, link_name, args)?; + let [epfd, op, fd, event] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } "epoll_wait" => { let [epfd, events, maxevents, timeout] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.epoll_wait(epfd, events, maxevents, timeout, dest)?; } "eventfd" => { - let [val, flag] = this.check_shim(abi, Conv::C, link_name, args)?; + let [val, flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } // Threading "pthread_setname_np" => { - let [thread, name] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread, name] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let res = match this.pthread_setname_np( this.read_scalar(thread)?, this.read_scalar(name)?, @@ -91,7 +93,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread, name, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // The function's behavior isn't portable between platforms. // In case of glibc, the length of the output buffer must // be not shorter than TASK_COMM_LEN. @@ -114,7 +116,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "gettid" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.linux_gettid()?; this.write_scalar(result, dest)?; } @@ -127,34 +129,34 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Miscellaneous "mmap64" => { let [addr, length, prot, flags, fd, offset] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; let offset = this.read_scalar(offset)?.to_i64()?; let ptr = this.mmap(addr, length, prot, flags, fd, offset.into())?; this.write_scalar(ptr, dest)?; } "mremap" => { let ([old_address, old_size, new_size, flags], _) = - this.check_shim_variadic(abi, Conv::C, link_name, args)?; + this.check_shim_variadic(abi, CanonAbi::C, link_name, args)?; let ptr = this.mremap(old_address, old_size, new_size, flags)?; this.write_scalar(ptr, dest)?; } "__xpg_strerror_r" => { - let [errnum, buf, buflen] = this.check_shim(abi, Conv::C, link_name, args)?; + let [errnum, buf, buflen] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.strerror_r(errnum, buf, buflen)?; this.write_scalar(result, dest)?; } "__errno_location" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } "__libc_current_sigrtmin" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_int(SIGRTMIN, dest)?; } "__libc_current_sigrtmax" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_int(SIGRTMAX, dest)?; } @@ -162,7 +164,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. "pthread_getattr_np" if this.frame_in_std() => { - let [_thread, _attr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [_thread, _attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_null(dest)?; } diff --git a/src/tools/miri/src/shims/unix/linux_like/syscall.rs b/src/tools/miri/src/shims/unix/linux_like/syscall.rs index 22c6dc97507..d42d6b9073e 100644 --- a/src/tools/miri/src/shims/unix/linux_like/syscall.rs +++ b/src/tools/miri/src/shims/unix/linux_like/syscall.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::helpers::check_min_vararg_count; use crate::shims::unix::linux_like::eventfd::EvalContextExt as _; @@ -14,7 +15,7 @@ pub fn syscall<'tcx>( args: &[OpTy<'tcx>], dest: &MPlaceTy<'tcx>, ) -> InterpResult<'tcx> { - let ([op], varargs) = ecx.check_shim_variadic(abi, Conv::C, link_name, args)?; + let ([op], varargs) = ecx.check_shim_variadic(abi, CanonAbi::C, link_name, args)?; // The syscall variadic function is legal to call with more arguments than needed, // extra arguments are simply ignored. The important check is that when we use an // argument, we have to also check all arguments *before* it to ensure that they diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 0281bb9f71d..ae921a013a4 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use super::sync::{EvalContextExt as _, MacOsFutexTimeout}; use crate::shims::unix::*; @@ -34,64 +35,64 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // errno "__error" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } // File related shims "close$NOCANCEL" => { - let [result] = this.check_shim(abi, Conv::C, link_name, args)?; + let [result] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.close(result)?; this.write_scalar(result, dest)?; } "stat" | "stat64" | "stat$INODE64" => { - let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; + let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_stat(path, buf)?; this.write_scalar(result, dest)?; } "lstat" | "lstat64" | "lstat$INODE64" => { - let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; + let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } "fstat" | "fstat64" | "fstat$INODE64" => { - let [fd, buf] = this.check_shim(abi, Conv::C, link_name, args)?; + let [fd, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_fstat(fd, buf)?; this.write_scalar(result, dest)?; } "opendir$INODE64" => { - let [name] = this.check_shim(abi, Conv::C, link_name, args)?; + let [name] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.opendir(name)?; this.write_scalar(result, dest)?; } "readdir_r" | "readdir_r$INODE64" => { - let [dirp, entry, result] = this.check_shim(abi, Conv::C, link_name, args)?; + let [dirp, entry, result] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_readdir_r(dirp, entry, result)?; this.write_scalar(result, dest)?; } "realpath$DARWIN_EXTSN" => { - let [path, resolved_path] = this.check_shim(abi, Conv::C, link_name, args)?; + let [path, resolved_path] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.realpath(path, resolved_path)?; this.write_scalar(result, dest)?; } "ioctl" => { let ([fd_num, cmd], varargs) = - this.check_shim_variadic(abi, Conv::C, link_name, args)?; + this.check_shim_variadic(abi, CanonAbi::C, link_name, args)?; let result = this.ioctl(fd_num, cmd, varargs)?; this.write_scalar(result, dest)?; } // Environment related shims "_NSGetEnviron" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let environ = this.machine.env_vars.unix().environ(); this.write_pointer(environ, dest)?; } // Random data generation "CCRandomGenerateBytes" => { - let [bytes, count] = this.check_shim(abi, Conv::C, link_name, args)?; + let [bytes, count] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let bytes = this.read_pointer(bytes)?; let count = this.read_target_usize(count)?; let success = this.eval_libc_i32("kCCSuccess"); @@ -101,28 +102,28 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Time related shims "mach_absolute_time" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.mach_absolute_time()?; this.write_scalar(result, dest)?; } "mach_timebase_info" => { - let [info] = this.check_shim(abi, Conv::C, link_name, args)?; + let [info] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.mach_timebase_info(info)?; this.write_scalar(result, dest)?; } // Access to command-line arguments "_NSGetArgc" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_pointer(this.machine.argc.expect("machine must be initialized"), dest)?; } "_NSGetArgv" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.write_pointer(this.machine.argv.expect("machine must be initialized"), dest)?; } "_NSGetExecutablePath" => { - let [buf, bufsize] = this.check_shim(abi, Conv::C, link_name, args)?; + let [buf, bufsize] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.check_no_isolation("`_NSGetExecutablePath`")?; let buf_ptr = this.read_pointer(buf)?; @@ -147,7 +148,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Thread-local storage "_tlv_atexit" => { - let [dtor, data] = this.check_shim(abi, Conv::C, link_name, args)?; + let [dtor, data] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let dtor = this.read_pointer(dtor)?; let dtor = this.get_ptr_fn(dtor)?.as_instance()?; let data = this.read_scalar(data)?; @@ -157,13 +158,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Querying system information "pthread_get_stackaddr_np" => { - let [thread] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.read_target_usize(thread)?; let stack_addr = Scalar::from_uint(this.machine.stack_addr, this.pointer_size()); this.write_scalar(stack_addr, dest)?; } "pthread_get_stacksize_np" => { - let [thread] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.read_target_usize(thread)?; let stack_size = Scalar::from_uint(this.machine.stack_size, this.pointer_size()); this.write_scalar(stack_size, dest)?; @@ -171,7 +172,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Threading "pthread_setname_np" => { - let [name] = this.check_shim(abi, Conv::C, link_name, args)?; + let [name] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // The real implementation has logic in two places: // * in userland at https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1178-L1200, @@ -198,7 +199,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread, name, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // The function's behavior isn't portable between platforms. // In case of macOS, a truncated name (due to a too small buffer) @@ -225,7 +226,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Synchronization primitives "os_sync_wait_on_address" => { let [addr_op, value_op, size_op, flags_op] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_sync_wait_on_address( addr_op, value_op, @@ -237,7 +238,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "os_sync_wait_on_address_with_deadline" => { let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_sync_wait_on_address( addr_op, value_op, @@ -249,7 +250,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "os_sync_wait_on_address_with_timeout" => { let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_sync_wait_on_address( addr_op, value_op, @@ -261,36 +262,36 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "os_sync_wake_by_address_any" => { let [addr_op, size_op, flags_op] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_sync_wake_by_address( addr_op, size_op, flags_op, /* all */ false, dest, )?; } "os_sync_wake_by_address_all" => { let [addr_op, size_op, flags_op] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_sync_wake_by_address( addr_op, size_op, flags_op, /* all */ true, dest, )?; } "os_unfair_lock_lock" => { - let [lock_op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_unfair_lock_lock(lock_op)?; } "os_unfair_lock_trylock" => { - let [lock_op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_unfair_lock_trylock(lock_op, dest)?; } "os_unfair_lock_unlock" => { - let [lock_op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_unfair_lock_unlock(lock_op)?; } "os_unfair_lock_assert_owner" => { - let [lock_op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_unfair_lock_assert_owner(lock_op)?; } "os_unfair_lock_assert_not_owner" => { - let [lock_op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.os_unfair_lock_assert_not_owner(lock_op)?; } diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs index 21d4f41f485..e3d15b89be6 100644 --- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::shims::unix::foreign_items::EvalContextExt as _; use crate::shims::unix::linux_like::epoll::EvalContextExt as _; @@ -26,32 +27,32 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // epoll, eventfd (NOT available on Solaris!) "epoll_create1" => { this.assert_target_os("illumos", "epoll_create1"); - let [flag] = this.check_shim(abi, Conv::C, link_name, args)?; + let [flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.epoll_create1(flag)?; this.write_scalar(result, dest)?; } "epoll_ctl" => { this.assert_target_os("illumos", "epoll_ctl"); - let [epfd, op, fd, event] = this.check_shim(abi, Conv::C, link_name, args)?; + let [epfd, op, fd, event] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.epoll_ctl(epfd, op, fd, event)?; this.write_scalar(result, dest)?; } "epoll_wait" => { this.assert_target_os("illumos", "epoll_wait"); let [epfd, events, maxevents, timeout] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; this.epoll_wait(epfd, events, maxevents, timeout, dest)?; } "eventfd" => { this.assert_target_os("illumos", "eventfd"); - let [val, flag] = this.check_shim(abi, Conv::C, link_name, args)?; + let [val, flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } // Threading "pthread_setname_np" => { - let [thread, name] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread, name] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // THREAD_NAME_MAX allows a thread name of 31+1 length // https://github.com/illumos/illumos-gate/blob/7671517e13b8123748eda4ef1ee165c6d9dba7fe/usr/src/uts/common/sys/thread.h#L613 let max_len = 32; @@ -69,7 +70,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_scalar(res, dest)?; } "pthread_getname_np" => { - let [thread, name, len] = this.check_shim(abi, Conv::C, link_name, args)?; + let [thread, name, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // See https://illumos.org/man/3C/pthread_getname_np for the error codes. let res = match this.pthread_getname_np( this.read_scalar(thread)?, @@ -86,22 +87,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // File related shims "stat" | "stat64" => { - let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; + let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_stat(path, buf)?; this.write_scalar(result, dest)?; } "lstat" | "lstat64" => { - let [path, buf] = this.check_shim(abi, Conv::C, link_name, args)?; + let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_lstat(path, buf)?; this.write_scalar(result, dest)?; } "fstat" | "fstat64" => { - let [fd, buf] = this.check_shim(abi, Conv::C, link_name, args)?; + let [fd, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.macos_fbsd_solarish_fstat(fd, buf)?; this.write_scalar(result, dest)?; } "readdir" => { - let [dirp] = this.check_shim(abi, Conv::C, link_name, args)?; + let [dirp] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.linux_solarish_readdir64("dirent", dirp)?; this.write_scalar(result, dest)?; } @@ -109,20 +110,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Sockets and pipes "__xnet_socketpair" => { let [domain, type_, protocol, sv] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.socketpair(domain, type_, protocol, sv)?; this.write_scalar(result, dest)?; } // Miscellaneous "___errno" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let errno_place = this.last_error_place()?; this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; } "stack_getbounds" => { - let [stack] = this.check_shim(abi, Conv::C, link_name, args)?; + let [stack] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let stack = this.deref_pointer_as(stack, this.libc_ty_layout("stack_t"))?; this.write_int_fields_named( @@ -140,7 +141,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "pset_info" => { - let [pset, tpe, cpus, list] = this.check_shim(abi, Conv::C, link_name, args)?; + let [pset, tpe, cpus, list] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // We do not need to handle the current process cpu mask, available_parallelism // implementation pass null anyway. We only care for the number of // cpus. @@ -169,7 +170,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "__sysconf_xpg7" => { - let [val] = this.check_shim(abi, Conv::C, link_name, args)?; + let [val] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.sysconf(val)?; this.write_scalar(result, dest)?; } diff --git a/src/tools/miri/src/shims/wasi/foreign_items.rs b/src/tools/miri/src/shims/wasi/foreign_items.rs index 90de62b9e57..8d92d0f3381 100644 --- a/src/tools/miri/src/shims/wasi/foreign_items.rs +++ b/src/tools/miri/src/shims/wasi/foreign_items.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::shims::alloc::EvalContextExt as _; use crate::*; @@ -22,12 +23,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match link_name.as_str() { // Allocation "posix_memalign" => { - let [memptr, align, size] = this.check_shim(abi, Conv::C, link_name, args)?; + let [memptr, align, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let result = this.posix_memalign(memptr, align, size)?; this.write_scalar(result, dest)?; } "aligned_alloc" => { - let [align, size] = this.check_shim(abi, Conv::C, link_name, args)?; + let [align, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let res = this.aligned_alloc(align, size)?; this.write_pointer(res, dest)?; } diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index 98099e07b2e..10f6df67ad4 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -2,10 +2,10 @@ use std::ffi::OsStr; use std::path::{self, Path, PathBuf}; use std::{io, iter, str}; -use rustc_abi::{Align, Size}; +use rustc_abi::{Align, CanonAbi, Size, X86Call}; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use self::shims::windows::handle::{Handle, PseudoHandle}; use crate::shims::os_str::bytes_to_os_str; @@ -140,7 +140,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // https://github.com/rust-lang/rust/blob/fb00adbdb69266f10df95a4527b767b0ad35ea48/compiler/rustc_target/src/spec/mod.rs#L2766-L2768, // x86-32 Windows uses a different calling convention than other Windows targets // for the "system" ABI. - let sys_conv = if this.tcx.sess.target.arch == "x86" { Conv::X86Stdcall } else { Conv::C }; + let sys_conv = if this.tcx.sess.target.arch == "x86" { + CanonAbi::X86(X86Call::Stdcall) + } else { + CanonAbi::C + }; // See `fn emulate_foreign_item_inner` in `shims/foreign_items.rs` for the general pattern. @@ -856,7 +860,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ); } // This function looks and behaves excatly like miri_start_unwind. - let [payload] = this.check_shim(abi, Conv::C, link_name, args)?; + let [payload] = this.check_shim(abi, CanonAbi::C, link_name, args)?; this.handle_miri_start_unwind(payload)?; return interp_ok(EmulateItemResult::NeedsUnwind); } diff --git a/src/tools/miri/src/shims/x86/aesni.rs b/src/tools/miri/src/shims/x86/aesni.rs index c6784db67fb..7191284b5a3 100644 --- a/src/tools/miri/src/shims/x86/aesni.rs +++ b/src/tools/miri/src/shims/x86/aesni.rs @@ -1,7 +1,8 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_middle::ty::layout::LayoutOf as _; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::*; @@ -26,7 +27,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `state` with the corresponding 128-bit key of `key`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdec_si128 "aesdec" | "aesdec.256" | "aesdec.512" => { - let [state, key] = this.check_shim(abi, Conv::C, link_name, args)?; + let [state, key] = this.check_shim(abi, CanonAbi::C, link_name, args)?; aes_round(this, state, key, dest, |state, key| { let key = aes::Block::from(key.to_le_bytes()); let mut state = aes::Block::from(state.to_le_bytes()); @@ -42,7 +43,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `state` with the corresponding 128-bit key of `key`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdeclast_si128 "aesdeclast" | "aesdeclast.256" | "aesdeclast.512" => { - let [state, key] = this.check_shim(abi, Conv::C, link_name, args)?; + let [state, key] = this.check_shim(abi, CanonAbi::C, link_name, args)?; aes_round(this, state, key, dest, |state, key| { let mut state = aes::Block::from(state.to_le_bytes()); @@ -66,7 +67,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `state` with the corresponding 128-bit key of `key`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenc_si128 "aesenc" | "aesenc.256" | "aesenc.512" => { - let [state, key] = this.check_shim(abi, Conv::C, link_name, args)?; + let [state, key] = this.check_shim(abi, CanonAbi::C, link_name, args)?; aes_round(this, state, key, dest, |state, key| { let key = aes::Block::from(key.to_le_bytes()); let mut state = aes::Block::from(state.to_le_bytes()); @@ -82,7 +83,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `state` with the corresponding 128-bit key of `key`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenclast_si128 "aesenclast" | "aesenclast.256" | "aesenclast.512" => { - let [state, key] = this.check_shim(abi, Conv::C, link_name, args)?; + let [state, key] = this.check_shim(abi, CanonAbi::C, link_name, args)?; aes_round(this, state, key, dest, |state, key| { let mut state = aes::Block::from(state.to_le_bytes()); // `aes::hazmat::cipher_round` does the following operations: @@ -102,7 +103,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Used to implement the _mm_aesimc_si128 function. // Performs the AES InvMixColumns operation on `op` "aesimc" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // Transmute to `u128` let op = op.transmute(this.machine.layouts.u128, this)?; let dest = dest.transmute(this.machine.layouts.u128, this)?; diff --git a/src/tools/miri/src/shims/x86/avx.rs b/src/tools/miri/src/shims/x86/avx.rs index 3aeb2b429da..37539fec748 100644 --- a/src/tools/miri/src/shims/x86/avx.rs +++ b/src/tools/miri/src/shims/x86/avx.rs @@ -1,9 +1,10 @@ +use rustc_abi::CanonAbi; use rustc_apfloat::ieee::{Double, Single}; use rustc_middle::mir; use rustc_middle::ty::Ty; use rustc_middle::ty::layout::LayoutOf as _; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use super::{ FloatBinOp, FloatUnaryOp, bin_op_simd_float_all, conditional_dot_product, convert_float_to_int, @@ -33,7 +34,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // matches the IEEE min/max operations, while x86 has different // semantics. "min.ps.256" | "max.ps.256" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "min.ps.256" => FloatBinOp::Min, @@ -45,7 +46,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Used to implement _mm256_min_pd and _mm256_max_pd functions. "min.pd.256" | "max.pd.256" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "min.pd.256" => FloatBinOp::Min, @@ -58,21 +59,21 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Used to implement the _mm256_round_ps function. // Rounds the elements of `op` according to `rounding`. "round.ps.256" => { - let [op, rounding] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?; round_all::<rustc_apfloat::ieee::Single>(this, op, rounding, dest)?; } // Used to implement the _mm256_round_pd function. // Rounds the elements of `op` according to `rounding`. "round.pd.256" => { - let [op, rounding] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?; round_all::<rustc_apfloat::ieee::Double>(this, op, rounding, dest)?; } // Used to implement _mm256_{rcp,rsqrt}_ps functions. // Performs the operations on all components of `op`. "rcp.ps.256" | "rsqrt.ps.256" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "rcp.ps.256" => FloatUnaryOp::Rcp, @@ -84,7 +85,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Used to implement the _mm256_dp_ps function. "dp.ps.256" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; conditional_dot_product(this, left, right, imm, dest)?; } @@ -92,7 +93,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Horizontally add/subtract adjacent floating point values // in `left` and `right`. "hadd.ps.256" | "hadd.pd.256" | "hsub.ps.256" | "hsub.pd.256" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "hadd.ps.256" | "hadd.pd.256" => mir::BinOp::Add, @@ -107,7 +108,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // and `right`. For each component, returns 0 if false or u32::MAX // if true. "cmp.ps.256" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?; @@ -119,7 +120,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // and `right`. For each component, returns 0 if false or u64::MAX // if true. "cmp.pd.256" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?; @@ -130,7 +131,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // and _mm256_cvttpd_epi32 functions. // Converts packed f32/f64 to packed i32. "cvt.ps2dq.256" | "cvtt.ps2dq.256" | "cvt.pd2dq.256" | "cvtt.pd2dq.256" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let rnd = match unprefixed_name { // "current SSE rounding mode", assume nearest @@ -148,7 +149,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // sequence of 4-element arrays, and we shuffle each of these arrays, where // `control` determines which element of the current `data` array is written. "vpermilvar.ps" | "vpermilvar.ps.256" => { - let [data, control] = this.check_shim(abi, Conv::C, link_name, args)?; + let [data, control] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (data, data_len) = this.project_to_simd(data)?; let (control, control_len) = this.project_to_simd(control)?; @@ -181,7 +182,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // where `right` determines which element of the current `left` array is // written. "vpermilvar.pd" | "vpermilvar.pd.256" => { - let [data, control] = this.check_shim(abi, Conv::C, link_name, args)?; + let [data, control] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (data, data_len) = this.project_to_simd(data)?; let (control, control_len) = this.project_to_simd(control)?; @@ -213,7 +214,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // For each 128-bit element of `dest`, copies one from `left`, `right` or // zero, according to `imm`. "vperm2f128.ps.256" | "vperm2f128.pd.256" | "vperm2f128.si.256" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; assert_eq!(dest.layout, left.layout); assert_eq!(dest.layout, right.layout); @@ -256,7 +257,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // is one, it is loaded from `ptr.wrapping_add(i)`, otherwise zero is // loaded. "maskload.ps" | "maskload.pd" | "maskload.ps.256" | "maskload.pd.256" => { - let [ptr, mask] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?; mask_load(this, ptr, mask, dest)?; } @@ -266,7 +267,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // is one, it is stored into `ptr.wapping_add(i)`. // Unlike SSE2's _mm_maskmoveu_si128, these are not non-temporal stores. "maskstore.ps" | "maskstore.pd" | "maskstore.ps.256" | "maskstore.pd.256" => { - let [ptr, mask, value] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr, mask, value] = this.check_shim(abi, CanonAbi::C, link_name, args)?; mask_store(this, ptr, mask, value)?; } @@ -276,7 +277,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // the data crosses a cache line, but for Miri this is just a regular // unaligned read. "ldu.dq.256" => { - let [src_ptr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [src_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let src_ptr = this.read_pointer(src_ptr)?; let dest = dest.force_mplace(this)?; @@ -288,7 +289,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Tests `op & mask == 0`, `op & mask == mask` or // `op & mask != 0 && op & mask != mask` "ptestz.256" | "ptestc.256" | "ptestnzc.256" => { - let [op, mask] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (all_zero, masked_set) = test_bits_masked(this, op, mask)?; let res = match unprefixed_name { @@ -311,7 +312,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "vtestz.pd.256" | "vtestc.pd.256" | "vtestnzc.pd.256" | "vtestz.pd" | "vtestc.pd" | "vtestnzc.pd" | "vtestz.ps.256" | "vtestc.ps.256" | "vtestnzc.ps.256" | "vtestz.ps" | "vtestc.ps" | "vtestnzc.ps" => { - let [op, mask] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (direct, negated) = test_high_bits_masked(this, op, mask)?; let res = match unprefixed_name { @@ -333,7 +334,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // compiler, making these functions no-ops. // The only thing that needs to be ensured is the correct calling convention. - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; } _ => return interp_ok(EmulateItemResult::NotSupported), } diff --git a/src/tools/miri/src/shims/x86/avx2.rs b/src/tools/miri/src/shims/x86/avx2.rs index c79899285cd..5dfe5cc2c54 100644 --- a/src/tools/miri/src/shims/x86/avx2.rs +++ b/src/tools/miri/src/shims/x86/avx2.rs @@ -1,8 +1,9 @@ +use rustc_abi::CanonAbi; use rustc_middle::mir; use rustc_middle::ty::Ty; use rustc_middle::ty::layout::LayoutOf as _; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use super::{ ShiftOp, horizontal_bin_op, int_abs, mask_load, mask_store, mpsadbw, packssdw, packsswb, @@ -28,7 +29,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Used to implement the _mm256_abs_epi{8,16,32} functions. // Calculates the absolute value of packed 8/16/32-bit integers. "pabs.b" | "pabs.w" | "pabs.d" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; int_abs(this, op, dest)?; } @@ -36,7 +37,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Horizontally add / add with saturation / subtract adjacent 16/32-bit // integer values in `left` and `right`. "phadd.w" | "phadd.sw" | "phadd.d" | "phsub.w" | "phsub.sw" | "phsub.d" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (which, saturating) = match unprefixed_name { "phadd.w" | "phadd.d" => (mir::BinOp::Add, false), @@ -57,7 +58,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { | "gather.d.pd.256" | "gather.q.pd" | "gather.q.pd.256" | "gather.d.ps" | "gather.d.ps.256" | "gather.q.ps" | "gather.q.ps.256" => { let [src, slice, offsets, mask, scale] = - this.check_shim(abi, Conv::C, link_name, args)?; + this.check_shim(abi, CanonAbi::C, link_name, args)?; assert_eq!(dest.layout, src.layout); @@ -114,7 +115,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // intermediate signed 32-bit integers. Horizontally add adjacent pairs of // intermediate 32-bit integers, and pack the results in `dest`. "pmadd.wd" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -150,7 +151,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // the saturating sum of the products with indices `2*i` and `2*i+1` // produces the output at index `i`. "pmadd.ub.sw" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -184,7 +185,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // is one, it is loaded from `ptr.wrapping_add(i)`, otherwise zero is // loaded. "maskload.d" | "maskload.q" | "maskload.d.256" | "maskload.q.256" => { - let [ptr, mask] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?; mask_load(this, ptr, mask, dest)?; } @@ -194,7 +195,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // is one, it is stored into `ptr.wapping_add(i)`. // Unlike SSE2's _mm_maskmoveu_si128, these are not non-temporal stores. "maskstore.d" | "maskstore.q" | "maskstore.d.256" | "maskstore.q.256" => { - let [ptr, mask, value] = this.check_shim(abi, Conv::C, link_name, args)?; + let [ptr, mask, value] = this.check_shim(abi, CanonAbi::C, link_name, args)?; mask_store(this, ptr, mask, value)?; } @@ -205,7 +206,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // offsets specified in `imm`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mpsadbw_epu8 "mpsadbw" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; mpsadbw(this, left, right, imm, dest)?; } @@ -216,7 +217,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 1 and then taking the bits `1..=16`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mulhrs_epi16 "pmul.hr.sw" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; pmulhrsw(this, left, right, dest)?; } @@ -224,7 +225,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Converts two 16-bit integer vectors to a single 8-bit integer // vector with signed saturation. "packsswb" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; packsswb(this, left, right, dest)?; } @@ -232,7 +233,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Converts two 32-bit integer vectors to a single 16-bit integer // vector with signed saturation. "packssdw" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; packssdw(this, left, right, dest)?; } @@ -240,7 +241,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Converts two 16-bit signed integer vectors to a single 8-bit // unsigned integer vector with saturation. "packuswb" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; packuswb(this, left, right, dest)?; } @@ -248,7 +249,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Concatenates two 32-bit signed integer vectors and converts // the result to a 16-bit unsigned integer vector with saturation. "packusdw" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; packusdw(this, left, right, dest)?; } @@ -257,7 +258,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Shuffles `left` using the three low bits of each element of `right` // as indices. "permd" | "permps" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -277,7 +278,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Used to implement the _mm256_permute2x128_si256 function. // Shuffles 128-bit blocks of `a` and `b` using `imm` as pattern. "vperm2i128" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; assert_eq!(left.layout.size.bits(), 256); assert_eq!(right.layout.size.bits(), 256); @@ -314,7 +315,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // in `dest`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_sad_epu8 "psad.bw" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -346,7 +347,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Shuffles bytes from `left` using `right` as pattern. // Each 128-bit block is shuffled independently. "pshuf.b" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -377,7 +378,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // is writen to the corresponding output element. // Basically, we multiply `left` with `right.signum()`. "psign.b" | "psign.w" | "psign.d" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; psign(this, left, right, dest)?; } @@ -391,7 +392,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // is copied to remaining bits. "psll.w" | "psrl.w" | "psra.w" | "psll.d" | "psrl.d" | "psra.d" | "psll.q" | "psrl.q" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "psll.w" | "psll.d" | "psll.q" => ShiftOp::Left, @@ -406,7 +407,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // (except _mm{,256}_srav_epi64, which are not available in AVX2). "psllv.d" | "psllv.d.256" | "psllv.q" | "psllv.q.256" | "psrlv.d" | "psrlv.d.256" | "psrlv.q" | "psrlv.q.256" | "psrav.d" | "psrav.d.256" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "psllv.d" | "psllv.d.256" | "psllv.q" | "psllv.q.256" => ShiftOp::Left, diff --git a/src/tools/miri/src/shims/x86/bmi.rs b/src/tools/miri/src/shims/x86/bmi.rs index 8af59df0a68..80b1b2e16e6 100644 --- a/src/tools/miri/src/shims/x86/bmi.rs +++ b/src/tools/miri/src/shims/x86/bmi.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::*; @@ -34,7 +35,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(EmulateItemResult::NotSupported); } - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let left = this.read_scalar(left)?; let right = this.read_scalar(right)?; diff --git a/src/tools/miri/src/shims/x86/gfni.rs b/src/tools/miri/src/shims/x86/gfni.rs index a91c74283fd..f83ce560c84 100644 --- a/src/tools/miri/src/shims/x86/gfni.rs +++ b/src/tools/miri/src/shims/x86/gfni.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::*; @@ -30,14 +31,14 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // See `affine_transform` for details. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8affine_ "vgf2p8affineqb.128" | "vgf2p8affineqb.256" | "vgf2p8affineqb.512" => { - let [left, right, imm8] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm8] = this.check_shim(abi, CanonAbi::C, link_name, args)?; affine_transform(this, left, right, imm8, dest, /* inverse */ false)?; } // Used to implement the `_mm{, 256, 512}_gf2p8affineinv_epi64_epi8` functions. // See `affine_transform` for details. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8affineinv "vgf2p8affineinvqb.128" | "vgf2p8affineinvqb.256" | "vgf2p8affineinvqb.512" => { - let [left, right, imm8] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm8] = this.check_shim(abi, CanonAbi::C, link_name, args)?; affine_transform(this, left, right, imm8, dest, /* inverse */ true)?; } // Used to implement the `_mm{, 256, 512}_gf2p8mul_epi8` functions. @@ -46,7 +47,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // polynomial representation with the reduction polynomial x^8 + x^4 + x^3 + x + 1. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8mul "vgf2p8mulb.128" | "vgf2p8mulb.256" | "vgf2p8mulb.512" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; let (dest, dest_len) = this.project_to_simd(dest)?; diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs index ac59cc2dfeb..7dee8ddd23c 100644 --- a/src/tools/miri/src/shims/x86/mod.rs +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -1,11 +1,11 @@ -use rustc_abi::Size; +use rustc_abi::{CanonAbi, Size}; use rustc_apfloat::Float; use rustc_apfloat::ieee::Single; use rustc_middle::ty::Ty; use rustc_middle::ty::layout::LayoutOf as _; use rustc_middle::{mir, ty}; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use self::helpers::bool_to_simd_element; use crate::*; @@ -46,7 +46,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(EmulateItemResult::NotSupported); } - let [cb_in, a, b] = this.check_shim(abi, Conv::C, link_name, args)?; + let [cb_in, a, b] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let op = if unprefixed_name.starts_with("add") { mir::BinOp::AddWithOverflow } else { @@ -68,7 +68,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if is_u64 && this.tcx.sess.target.arch != "x86_64" { return interp_ok(EmulateItemResult::NotSupported); } - let [c_in, a, b, out] = this.check_shim(abi, Conv::C, link_name, args)?; + let [c_in, a, b, out] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let out = this.deref_pointer_as( out, if is_u64 { this.machine.layouts.u64 } else { this.machine.layouts.u32 }, @@ -85,7 +85,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // the instruction behaves like a no-op, so it is always safe to call the // intrinsic. "sse2.pause" => { - let [] = this.check_shim(abi, Conv::C, link_name, args)?; + let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?; // Only exhibit the spin-loop hint behavior when SSE2 is enabled. if this.tcx.sess.unstable_target_features.contains(&Symbol::intern("sse2")) { this.yield_active_thread(); @@ -104,7 +104,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { len = 8; } - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; pclmulqdq(this, left, right, imm, dest, len)?; } diff --git a/src/tools/miri/src/shims/x86/sha.rs b/src/tools/miri/src/shims/x86/sha.rs index 23c83553f3b..d37fad3e6c7 100644 --- a/src/tools/miri/src/shims/x86/sha.rs +++ b/src/tools/miri/src/shims/x86/sha.rs @@ -4,9 +4,10 @@ //! //! [RustCrypto's sha256 module]: https://github.com/RustCrypto/hashes/blob/6be8466247e936c415d8aafb848697f39894a386/sha2/src/sha256/soft.rs +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::*; @@ -52,7 +53,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match unprefixed_name { // Used to implement the _mm_sha256rnds2_epu32 function. "256rnds2" => { - let [a, b, k] = this.check_shim(abi, Conv::C, link_name, args)?; + let [a, b, k] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (a_reg, a_len) = this.project_to_simd(a)?; let (b_reg, b_len) = this.project_to_simd(b)?; @@ -73,7 +74,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Used to implement the _mm_sha256msg1_epu32 function. "256msg1" => { - let [a, b] = this.check_shim(abi, Conv::C, link_name, args)?; + let [a, b] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (a_reg, a_len) = this.project_to_simd(a)?; let (b_reg, b_len) = this.project_to_simd(b)?; @@ -91,7 +92,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Used to implement the _mm_sha256msg2_epu32 function. "256msg2" => { - let [a, b] = this.check_shim(abi, Conv::C, link_name, args)?; + let [a, b] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (a_reg, a_len) = this.project_to_simd(a)?; let (b_reg, b_len) = this.project_to_simd(b)?; diff --git a/src/tools/miri/src/shims/x86/sse.rs b/src/tools/miri/src/shims/x86/sse.rs index fd7aba2437a..1ec15d609c6 100644 --- a/src/tools/miri/src/shims/x86/sse.rs +++ b/src/tools/miri/src/shims/x86/sse.rs @@ -1,7 +1,8 @@ +use rustc_abi::CanonAbi; use rustc_apfloat::ieee::Single; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use super::{ FloatBinOp, FloatUnaryOp, bin_op_simd_float_all, bin_op_simd_float_first, unary_op_ps, @@ -33,7 +34,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Performs the operations on the first component of `left` and // `right` and copies the remaining components from `left`. "min.ss" | "max.ss" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "min.ss" => FloatBinOp::Min, @@ -49,7 +50,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // matches the IEEE min/max operations, while x86 has different // semantics. "min.ps" | "max.ps" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "min.ps" => FloatBinOp::Min, @@ -63,7 +64,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Performs the operations on the first component of `op` and // copies the remaining components from `op`. "rcp.ss" | "rsqrt.ss" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "rcp.ss" => FloatUnaryOp::Rcp, @@ -76,7 +77,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Used to implement _mm_{sqrt,rcp,rsqrt}_ps functions. // Performs the operations on all components of `op`. "rcp.ps" | "rsqrt.ps" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "rcp.ps" => FloatUnaryOp::Rcp, @@ -95,7 +96,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_ss are SSE functions // with hard-coded operations. "cmp.ss" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?; @@ -111,7 +112,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_ps are SSE functions // with hard-coded operations. "cmp.ps" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?; @@ -124,7 +125,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "comieq.ss" | "comilt.ss" | "comile.ss" | "comigt.ss" | "comige.ss" | "comineq.ss" | "ucomieq.ss" | "ucomilt.ss" | "ucomile.ss" | "ucomigt.ss" | "ucomige.ss" | "ucomineq.ss" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -152,7 +153,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // _mm_cvtss_si64 and _mm_cvttss_si64 functions. // Converts the first component of `op` from f32 to i32/i64. "cvtss2si" | "cvttss2si" | "cvtss2si64" | "cvttss2si64" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (op, _) = this.project_to_simd(op)?; let op = this.read_immediate(&this.project_index(&op, 0)?)?; @@ -180,7 +181,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // are copied from `left`. // https://www.felixcloutier.com/x86/cvtsi2ss "cvtsi2ss" | "cvtsi642ss" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (dest, dest_len) = this.project_to_simd(dest)?; diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs index e0695b7cb7b..d6052f83077 100644 --- a/src/tools/miri/src/shims/x86/sse2.rs +++ b/src/tools/miri/src/shims/x86/sse2.rs @@ -1,7 +1,8 @@ +use rustc_abi::CanonAbi; use rustc_apfloat::ieee::Double; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use super::{ FloatBinOp, ShiftOp, bin_op_simd_float_all, bin_op_simd_float_first, convert_float_to_int, @@ -40,7 +41,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // intermediate signed 32-bit integers. Horizontally add adjacent pairs of // intermediate 32-bit integers, and pack the results in `dest`. "pmadd.wd" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -78,7 +79,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sad_epu8 "psad.bw" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -116,7 +117,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // is copied to remaining bits. "psll.w" | "psrl.w" | "psra.w" | "psll.d" | "psrl.d" | "psra.d" | "psll.q" | "psrl.q" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "psll.w" | "psll.d" | "psll.q" => ShiftOp::Left, @@ -131,7 +132,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // and _mm_cvttpd_epi32 functions. // Converts packed f32/f64 to packed i32. "cvtps2dq" | "cvttps2dq" | "cvtpd2dq" | "cvttpd2dq" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (op_len, _) = op.layout.ty.simd_size_and_type(*this.tcx); let (dest_len, _) = dest.layout.ty.simd_size_and_type(*this.tcx); @@ -168,7 +169,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Converts two 16-bit integer vectors to a single 8-bit integer // vector with signed saturation. "packsswb.128" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; packsswb(this, left, right, dest)?; } @@ -176,7 +177,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Converts two 16-bit signed integer vectors to a single 8-bit // unsigned integer vector with saturation. "packuswb.128" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; packuswb(this, left, right, dest)?; } @@ -184,7 +185,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Converts two 32-bit integer vectors to a single 16-bit integer // vector with signed saturation. "packssdw.128" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; packssdw(this, left, right, dest)?; } @@ -194,7 +195,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // matches the IEEE min/max operations, while x86 has different // semantics. "min.sd" | "max.sd" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "min.sd" => FloatBinOp::Min, @@ -210,7 +211,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // matches the IEEE min/max operations, while x86 has different // semantics. "min.pd" | "max.pd" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "min.pd" => FloatBinOp::Min, @@ -229,7 +230,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_sd are SSE2 functions // with hard-coded operations. "cmp.sd" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?; @@ -245,7 +246,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_pd are SSE2 functions // with hard-coded operations. "cmp.pd" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?; @@ -258,7 +259,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "comieq.sd" | "comilt.sd" | "comile.sd" | "comigt.sd" | "comige.sd" | "comineq.sd" | "ucomieq.sd" | "ucomilt.sd" | "ucomile.sd" | "ucomigt.sd" | "ucomige.sd" | "ucomineq.sd" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -286,7 +287,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // _mm_cvtsd_si64 and _mm_cvttsd_si64 functions. // Converts the first component of `op` from f64 to i32/i64. "cvtsd2si" | "cvttsd2si" | "cvtsd2si64" | "cvttsd2si64" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (op, _) = this.project_to_simd(op)?; let op = this.read_immediate(&this.project_index(&op, 0)?)?; @@ -312,7 +313,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Converts the first f64/f32 from `right` to f32/f64 and copies // the remaining elements from `left` "cvtsd2ss" | "cvtss2sd" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, _) = this.project_to_simd(right)?; diff --git a/src/tools/miri/src/shims/x86/sse3.rs b/src/tools/miri/src/shims/x86/sse3.rs index 60b7764a01e..ebf3cb5c3ee 100644 --- a/src/tools/miri/src/shims/x86/sse3.rs +++ b/src/tools/miri/src/shims/x86/sse3.rs @@ -1,7 +1,8 @@ +use rustc_abi::CanonAbi; use rustc_middle::mir; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use super::horizontal_bin_op; use crate::*; @@ -25,7 +26,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Horizontally add/subtract adjacent floating point values // in `left` and `right`. "hadd.ps" | "hadd.pd" | "hsub.ps" | "hsub.pd" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let which = match unprefixed_name { "hadd.ps" | "hadd.pd" => mir::BinOp::Add, @@ -41,7 +42,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // the data crosses a cache line, but for Miri this is just a regular // unaligned read. "ldu.dq" => { - let [src_ptr] = this.check_shim(abi, Conv::C, link_name, args)?; + let [src_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let src_ptr = this.read_pointer(src_ptr)?; let dest = dest.force_mplace(this)?; diff --git a/src/tools/miri/src/shims/x86/sse41.rs b/src/tools/miri/src/shims/x86/sse41.rs index 93d689a3044..6797039cf56 100644 --- a/src/tools/miri/src/shims/x86/sse41.rs +++ b/src/tools/miri/src/shims/x86/sse41.rs @@ -1,6 +1,7 @@ +use rustc_abi::CanonAbi; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use super::{conditional_dot_product, mpsadbw, packusdw, round_all, round_first, test_bits_masked}; use crate::*; @@ -27,7 +28,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // bits `4..=5` if `imm`, and `i`th bit specifies whether element // `i` is zeroed. "insertps" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -62,7 +63,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Concatenates two 32-bit signed integer vectors and converts // the result to a 16-bit unsigned integer vector with saturation. "packusdw" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; packusdw(this, left, right, dest)?; } @@ -72,7 +73,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // products, and conditionally stores the sum in `dest` using the low // 4 bits of `imm`. "dpps" | "dppd" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; conditional_dot_product(this, left, right, imm, dest)?; } @@ -80,14 +81,14 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // functions. Rounds the first element of `right` according to `rounding` // and copies the remaining elements from `left`. "round.ss" => { - let [left, right, rounding] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?; round_first::<rustc_apfloat::ieee::Single>(this, left, right, rounding, dest)?; } // Used to implement the _mm_floor_ps, _mm_ceil_ps and _mm_round_ps // functions. Rounds the elements of `op` according to `rounding`. "round.ps" => { - let [op, rounding] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?; round_all::<rustc_apfloat::ieee::Single>(this, op, rounding, dest)?; } @@ -95,14 +96,14 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // functions. Rounds the first element of `right` according to `rounding` // and copies the remaining elements from `left`. "round.sd" => { - let [left, right, rounding] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?; round_first::<rustc_apfloat::ieee::Double>(this, left, right, rounding, dest)?; } // Used to implement the _mm_floor_pd, _mm_ceil_pd and _mm_round_pd // functions. Rounds the elements of `op` according to `rounding`. "round.pd" => { - let [op, rounding] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?; round_all::<rustc_apfloat::ieee::Double>(this, op, rounding, dest)?; } @@ -110,7 +111,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Find the minimum unsinged 16-bit integer in `op` and // returns its value and position. "phminposuw" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (op, op_len) = this.project_to_simd(op)?; let (dest, dest_len) = this.project_to_simd(dest)?; @@ -144,7 +145,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // offsets specified in `imm`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mpsadbw_epu8 "mpsadbw" => { - let [left, right, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; mpsadbw(this, left, right, imm, dest)?; } @@ -153,7 +154,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Tests `(op & mask) == 0`, `(op & mask) == mask` or // `(op & mask) != 0 && (op & mask) != mask` "ptestz" | "ptestc" | "ptestnzc" => { - let [op, mask] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (all_zero, masked_set) = test_bits_masked(this, op, mask)?; let res = match unprefixed_name { diff --git a/src/tools/miri/src/shims/x86/sse42.rs b/src/tools/miri/src/shims/x86/sse42.rs index 66bff328626..830513f0291 100644 --- a/src/tools/miri/src/shims/x86/sse42.rs +++ b/src/tools/miri/src/shims/x86/sse42.rs @@ -1,9 +1,9 @@ -use rustc_abi::Size; +use rustc_abi::{CanonAbi, Size}; use rustc_middle::mir; use rustc_middle::ty::Ty; use rustc_middle::ty::layout::LayoutOf as _; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use crate::*; @@ -223,7 +223,7 @@ fn deconstruct_args<'tcx>( }; if is_explicit { - let [str1, len1, str2, len2, imm] = ecx.check_shim(abi, Conv::C, link_name, args)?; + let [str1, len1, str2, len2, imm] = ecx.check_shim(abi, CanonAbi::C, link_name, args)?; let imm = ecx.read_scalar(imm)?.to_u8()?; let default_len = default_len::<u32>(imm); @@ -236,7 +236,7 @@ fn deconstruct_args<'tcx>( interp_ok((str1, str2, Some((len1, len2)), imm)) } else { - let [str1, str2, imm] = ecx.check_shim(abi, Conv::C, link_name, args)?; + let [str1, str2, imm] = ecx.check_shim(abi, CanonAbi::C, link_name, args)?; let imm = ecx.read_scalar(imm)?.to_u8()?; let array_layout = array_layout_fn(ecx, imm)?; @@ -386,7 +386,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // search for a null terminator (see `deconstruct_args` for more details). // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=924,925 "pcmpistriz128" | "pcmpistris128" => { - let [str1, str2, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [str1, str2, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let imm = this.read_scalar(imm)?.to_u8()?; let str = if unprefixed_name == "pcmpistris128" { str1 } else { str2 }; @@ -406,7 +406,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // than 16 for byte-sized operands or 8 for word-sized operands. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=1046,1047 "pcmpestriz128" | "pcmpestris128" => { - let [_, len1, _, len2, imm] = this.check_shim(abi, Conv::C, link_name, args)?; + let [_, len1, _, len2, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let len = if unprefixed_name == "pcmpestris128" { len1 } else { len2 }; let len = this.read_scalar(len)?.to_i32()?; let imm = this.read_scalar(imm)?.to_u8()?; @@ -433,7 +433,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { return interp_ok(EmulateItemResult::NotSupported); } - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let left = this.read_scalar(left)?; let right = this.read_scalar(right)?; diff --git a/src/tools/miri/src/shims/x86/ssse3.rs b/src/tools/miri/src/shims/x86/ssse3.rs index f3e9ac0e5dc..310d6b8f765 100644 --- a/src/tools/miri/src/shims/x86/ssse3.rs +++ b/src/tools/miri/src/shims/x86/ssse3.rs @@ -1,7 +1,8 @@ +use rustc_abi::CanonAbi; use rustc_middle::mir; use rustc_middle::ty::Ty; use rustc_span::Symbol; -use rustc_target::callconv::{Conv, FnAbi}; +use rustc_target::callconv::FnAbi; use super::{horizontal_bin_op, int_abs, pmulhrsw, psign}; use crate::*; @@ -24,7 +25,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Used to implement the _mm_abs_epi{8,16,32} functions. // Calculates the absolute value of packed 8/16/32-bit integers. "pabs.b.128" | "pabs.w.128" | "pabs.d.128" => { - let [op] = this.check_shim(abi, Conv::C, link_name, args)?; + let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?; int_abs(this, op, dest)?; } @@ -32,7 +33,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Shuffles bytes from `left` using `right` as pattern. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_epi8 "pshuf.b.128" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -61,7 +62,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // integer values in `left` and `right`. "phadd.w.128" | "phadd.sw.128" | "phadd.d.128" | "phsub.w.128" | "phsub.sw.128" | "phsub.d.128" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (which, saturating) = match unprefixed_name { "phadd.w.128" | "phadd.d.128" => (mir::BinOp::Add, false), @@ -80,7 +81,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // produces the output at index `i`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maddubs_epi16 "pmadd.ub.sw.128" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; let (left, left_len) = this.project_to_simd(left)?; let (right, right_len) = this.project_to_simd(right)?; @@ -115,7 +116,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // 1 and then taking the bits `1..=16`. // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhrs_epi16 "pmul.hr.sw.128" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; pmulhrsw(this, left, right, dest)?; } @@ -125,7 +126,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // is writen to the corresponding output element. // Basically, we multiply `left` with `right.signum()`. "psign.b.128" | "psign.w.128" | "psign.d.128" => { - let [left, right] = this.check_shim(abi, Conv::C, link_name, args)?; + let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?; psign(this, left, right, dest)?; } diff --git a/src/tools/miri/tests/fail/const-ub-checks.rs b/src/tools/miri/tests/fail/const-ub-checks.rs index 9cc8b91ff50..004bbe9f5ea 100644 --- a/src/tools/miri/tests/fail/const-ub-checks.rs +++ b/src/tools/miri/tests/fail/const-ub-checks.rs @@ -1,7 +1,7 @@ const UNALIGNED_READ: () = unsafe { let x = &[0u8; 4]; let ptr = x.as_ptr().cast::<u32>(); - ptr.read(); //~ERROR: evaluation of constant value failed + ptr.read(); //~ERROR: accessing memory based on pointer with alignment 1, but alignment 4 is required }; fn main() { diff --git a/src/tools/miri/tests/fail/const-ub-checks.stderr b/src/tools/miri/tests/fail/const-ub-checks.stderr index e6b302dd28c..9bac524bd45 100644 --- a/src/tools/miri/tests/fail/const-ub-checks.stderr +++ b/src/tools/miri/tests/fail/const-ub-checks.stderr @@ -1,8 +1,8 @@ -error[E0080]: evaluation of constant value failed +error[E0080]: accessing memory based on pointer with alignment ALIGN, but alignment ALIGN is required --> tests/fail/const-ub-checks.rs:LL:CC | LL | ptr.read(); - | ^^^^^^^^^^ accessing memory based on pointer with alignment ALIGN, but alignment ALIGN is required + | ^^^^^^^^^^ evaluation of constant value failed here note: erroneous constant encountered --> tests/fail/const-ub-checks.rs:LL:CC diff --git a/src/tools/miri/tests/fail/erroneous_const.rs b/src/tools/miri/tests/fail/erroneous_const.rs index 65f7aafc3cc..6e126ed701f 100644 --- a/src/tools/miri/tests/fail/erroneous_const.rs +++ b/src/tools/miri/tests/fail/erroneous_const.rs @@ -4,7 +4,7 @@ struct PrintName<T>(T); impl<T> PrintName<T> { - const VOID: ! = panic!(); //~ERROR: evaluation of `PrintName::<i32>::VOID` failed + const VOID: ! = panic!(); //~ERROR: explicit panic } fn no_codegen<T>() { diff --git a/src/tools/miri/tests/fail/erroneous_const.stderr b/src/tools/miri/tests/fail/erroneous_const.stderr index 6f1e3529ccc..1aa87dd7188 100644 --- a/src/tools/miri/tests/fail/erroneous_const.stderr +++ b/src/tools/miri/tests/fail/erroneous_const.stderr @@ -1,8 +1,8 @@ -error[E0080]: evaluation of `PrintName::<i32>::VOID` failed +error[E0080]: evaluation panicked: explicit panic --> tests/fail/erroneous_const.rs:LL:CC | LL | const VOID: ! = panic!(); - | ^^^^^^^^ evaluation panicked: explicit panic + | ^^^^^^^^ evaluation of `PrintName::<i32>::VOID` failed here note: erroneous constant encountered --> tests/fail/erroneous_const.rs:LL:CC diff --git a/src/tools/miri/tests/fail/erroneous_const2.rs b/src/tools/miri/tests/fail/erroneous_const2.rs index 9a1a970778f..ea98b64ad81 100644 --- a/src/tools/miri/tests/fail/erroneous_const2.rs +++ b/src/tools/miri/tests/fail/erroneous_const2.rs @@ -1,7 +1,7 @@ const X: u32 = 5; const Y: u32 = 6; const FOO: u32 = [X - Y, Y - X][(X < Y) as usize]; -//~^ERROR: evaluation of constant value failed +//~^ERROR: overflow #[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/5391 fn main() { diff --git a/src/tools/miri/tests/fail/erroneous_const2.stderr b/src/tools/miri/tests/fail/erroneous_const2.stderr index 76f8cbcd289..9d3f1b13bee 100644 --- a/src/tools/miri/tests/fail/erroneous_const2.stderr +++ b/src/tools/miri/tests/fail/erroneous_const2.stderr @@ -1,8 +1,8 @@ -error[E0080]: evaluation of constant value failed +error[E0080]: attempt to compute `5_u32 - 6_u32`, which would overflow --> tests/fail/erroneous_const2.rs:LL:CC | LL | const FOO: u32 = [X - Y, Y - X][(X < Y) as usize]; - | ^^^^^ attempt to compute `5_u32 - 6_u32`, which would overflow + | ^^^^^ evaluation of constant value failed here note: erroneous constant encountered --> tests/fail/erroneous_const2.rs:LL:CC diff --git a/src/tools/miri/tests/pass/iter_macro.rs b/src/tools/miri/tests/pass/iter_macro.rs new file mode 100644 index 00000000000..367c13f9969 --- /dev/null +++ b/src/tools/miri/tests/pass/iter_macro.rs @@ -0,0 +1,22 @@ +#![feature(iter_macro, yield_expr)] + +use std::iter::iter; + +fn main() { + let i = iter! { || { + yield 0; + for x in 5..10 { + yield x * 2; + } + } }; + let mut i = i(); + assert_eq!(i.next(), Some(0)); + assert_eq!(i.next(), Some(10)); + assert_eq!(i.next(), Some(12)); + assert_eq!(i.next(), Some(14)); + assert_eq!(i.next(), Some(16)); + assert_eq!(i.next(), Some(18)); + assert_eq!(i.next(), None); + assert_eq!(i.next(), None); + assert_eq!(i.next(), None); +} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/files.rs b/src/tools/rust-analyzer/crates/hir-expand/src/files.rs index 321ee8feb9a..8024823cbc5 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/files.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/files.rs @@ -42,6 +42,49 @@ impl FilePosition { FilePositionWrapper { file_id: self.file_id.file_id(db), offset: self.offset } } } + +impl From<FileRange> for HirFileRange { + fn from(value: FileRange) -> Self { + HirFileRange { file_id: value.file_id.into(), range: value.range } + } +} + +impl From<FilePosition> for HirFilePosition { + fn from(value: FilePosition) -> Self { + HirFilePosition { file_id: value.file_id.into(), offset: value.offset } + } +} + +impl FilePositionWrapper<span::FileId> { + pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FilePosition { + FilePositionWrapper { + file_id: EditionedFileId::new(db, self.file_id, edition), + offset: self.offset, + } + } +} + +impl FileRangeWrapper<span::FileId> { + pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> FileRange { + FileRangeWrapper { + file_id: EditionedFileId::new(db, self.file_id, edition), + range: self.range, + } + } +} + +impl<T> InFileWrapper<span::FileId, T> { + pub fn with_edition(self, db: &dyn ExpandDatabase, edition: span::Edition) -> InRealFile<T> { + InRealFile { file_id: EditionedFileId::new(db, self.file_id, edition), value: self.value } + } +} + +impl HirFileRange { + pub fn file_range(self) -> Option<FileRange> { + Some(FileRange { file_id: self.file_id.file_id()?, range: self.range }) + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct FileRangeWrapper<FileKind> { pub file_id: FileKind, @@ -194,6 +237,9 @@ impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> { pub fn syntax(&self) -> InFileWrapper<FileId, &SyntaxNode> { self.with_value(self.value.syntax()) } + pub fn node_file_range(&self) -> FileRangeWrapper<FileId> { + FileRangeWrapper { file_id: self.file_id, range: self.value.syntax().text_range() } + } } impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> { @@ -204,9 +250,9 @@ impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, &N> { } // region:specific impls -impl<SN: Borrow<SyntaxNode>> InRealFile<SN> { - pub fn file_range(&self) -> FileRange { - FileRange { file_id: self.file_id, range: self.value.borrow().text_range() } +impl<FileId: Copy, SN: Borrow<SyntaxNode>> InFileWrapper<FileId, SN> { + pub fn file_range(&self) -> FileRangeWrapper<FileId> { + FileRangeWrapper { file_id: self.file_id, range: self.value.borrow().text_range() } } } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index d844d8f41ee..6ecac1463f5 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -392,6 +392,10 @@ impl HirFileId { } } + pub fn call_node(self, db: &dyn ExpandDatabase) -> Option<InFile<SyntaxNode>> { + Some(db.lookup_intern_macro_call(self.macro_file()?).to_node(db)) + } + pub fn as_builtin_derive_attr_node( &self, db: &dyn ExpandDatabase, @@ -848,7 +852,10 @@ impl ExpansionInfo { map_node_range_up(db, &self.exp_map, range) } - /// Maps up the text range out of the expansion into is macro call. + /// Maps up the text range out of the expansion into its macro call. + /// + /// Note that this may return multiple ranges as we lose the precise association between input to output + /// and as such we may consider inputs that are unrelated. pub fn map_range_up_once( &self, db: &dyn ExpandDatabase, @@ -864,11 +871,10 @@ impl ExpansionInfo { InFile { file_id, value: smallvec::smallvec![span.range + anchor_offset] } } SpanMap::ExpansionSpanMap(arg_map) => { - let arg_range = self - .arg - .value - .as_ref() - .map_or_else(|| TextRange::empty(TextSize::from(0)), |it| it.text_range()); + let Some(arg_node) = &self.arg.value else { + return InFile::new(self.arg.file_id, smallvec::smallvec![]); + }; + let arg_range = arg_node.text_range(); InFile::new( self.arg.file_id, arg_map diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs b/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs index 11cc434c2d8..6134c3a36b9 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/prettify_macro_expansion_.rs @@ -20,42 +20,46 @@ pub fn prettify_macro_expansion( let span_offset = syn.text_range().start(); let target_crate = target_crate_id.data(db); let mut syntax_ctx_id_to_dollar_crate_replacement = FxHashMap::default(); - syntax_bridge::prettify_macro_expansion::prettify_macro_expansion(syn, &mut |dollar_crate| { - let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx; - let replacement = - syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| { - let macro_call_id = - ctx.outer_expn(db).expect("`$crate` cannot come from `SyntaxContextId::ROOT`"); - let macro_call = db.lookup_intern_macro_call(macro_call_id.into()); - let macro_def_crate = macro_call.def.krate; - // First, if this is the same crate as the macro, nothing will work but `crate`. - // If not, if the target trait has the macro's crate as a dependency, using the dependency name - // will work in inserted code and match the user's expectation. - // If not, the crate's display name is what the dependency name is likely to be once such dependency - // is inserted, and also understandable to the user. - // Lastly, if nothing else found, resort to leaving `$crate`. - if target_crate_id == macro_def_crate { - make::tokens::crate_kw() - } else if let Some(dep) = - target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate) - { - make::tokens::ident(dep.name.as_str()) - } else if let Some(crate_name) = ¯o_def_crate.extra_data(db).display_name { - make::tokens::ident(crate_name.crate_name().as_str()) - } else { - return dollar_crate.clone(); - } - }); - if replacement.text() == "$crate" { - // The parent may have many children, and looking for the token may yield incorrect results. - return dollar_crate.clone(); - } - // We need to `clone_subtree()` but rowan doesn't provide such operation for tokens. - let parent = replacement.parent().unwrap().clone_subtree().clone_for_update(); - parent - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|it| it.kind() == replacement.kind()) - .unwrap() - }) + syntax_bridge::prettify_macro_expansion::prettify_macro_expansion( + syn, + &mut |dollar_crate| { + let ctx = span_map.span_at(dollar_crate.text_range().start() + span_offset).ctx; + let replacement = + syntax_ctx_id_to_dollar_crate_replacement.entry(ctx).or_insert_with(|| { + let macro_call_id = ctx + .outer_expn(db) + .expect("`$crate` cannot come from `SyntaxContextId::ROOT`"); + let macro_call = db.lookup_intern_macro_call(macro_call_id.into()); + let macro_def_crate = macro_call.def.krate; + // First, if this is the same crate as the macro, nothing will work but `crate`. + // If not, if the target trait has the macro's crate as a dependency, using the dependency name + // will work in inserted code and match the user's expectation. + // If not, the crate's display name is what the dependency name is likely to be once such dependency + // is inserted, and also understandable to the user. + // Lastly, if nothing else found, resort to leaving `$crate`. + if target_crate_id == macro_def_crate { + make::tokens::crate_kw() + } else if let Some(dep) = + target_crate.dependencies.iter().find(|dep| dep.crate_id == macro_def_crate) + { + make::tokens::ident(dep.name.as_str()) + } else if let Some(crate_name) = ¯o_def_crate.extra_data(db).display_name { + make::tokens::ident(crate_name.crate_name().as_str()) + } else { + return dollar_crate.clone(); + } + }); + if replacement.text() == "$crate" { + // The parent may have many children, and looking for the token may yield incorrect results. + return None; + } + // We need to `clone_subtree()` but rowan doesn't provide such operation for tokens. + let parent = replacement.parent().unwrap().clone_subtree().clone_for_update(); + parent + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|it| it.kind() == replacement.kind()) + }, + |_| (), + ) } diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index aea22545ed5..e01774650b1 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -24,7 +24,7 @@ use hir_expand::{ attrs::collect_attrs, builtin::{BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, - files::{FileRangeWrapper, InRealFile}, + files::{FileRangeWrapper, HirFileRange, InRealFile}, inert_attr_macro::find_builtin_attr_idx, mod_path::{ModPath, PathKind}, name::AsName, @@ -262,6 +262,17 @@ impl<DB: HirDatabase> Semantics<'_, DB> { self.imp.file_to_module_defs(file.into()) } + pub fn hir_file_to_module_def(&self, file: impl Into<HirFileId>) -> Option<Module> { + self.imp.hir_file_to_module_defs(file.into()).next() + } + + pub fn hir_file_to_module_defs( + &self, + file: impl Into<HirFileId>, + ) -> impl Iterator<Item = Module> { + self.imp.hir_file_to_module_defs(file.into()) + } + pub fn to_adt_def(&self, a: &ast::Adt) -> Option<Adt> { self.imp.to_def(a) } @@ -357,6 +368,15 @@ impl<'db> SemanticsImpl<'db> { tree } + pub fn adjust_edition(&self, file_id: HirFileId) -> HirFileId { + if let Some(editioned_file_id) = file_id.file_id() { + self.attach_first_edition(editioned_file_id.file_id(self.db)) + .map_or(file_id, Into::into) + } else { + file_id + } + } + pub fn find_parent_file(&self, file_id: HirFileId) -> Option<InFile<SyntaxNode>> { match file_id { HirFileId::FileId(file_id) => { @@ -653,7 +673,7 @@ impl<'db> SemanticsImpl<'db> { string: &ast::String, ) -> Option<Vec<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)>> { let string_start = string.syntax().text_range().start(); - let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?; + let token = self.wrap_token_infile(string.syntax().clone()); self.descend_into_macros_breakable(token, |token, _| { (|| { let token = token.value; @@ -693,50 +713,95 @@ impl<'db> SemanticsImpl<'db> { } /// Retrieves the formatting part of the format_args! template string at the given offset. + /// + // FIXME: Type the return type + /// Returns the range (pre-expansion) in the string literal corresponding to the resolution, + /// absolute file range (post-expansion) + /// of the part in the format string, the corresponding string token and the resolution if it + /// exists. + // FIXME: Remove this in favor of `check_for_format_args_template_with_file` pub fn check_for_format_args_template( &self, original_token: SyntaxToken, offset: TextSize, - ) -> Option<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)> { - let string_start = original_token.text_range().start(); - let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?; - self.descend_into_macros_breakable(original_token, |token, _| { - (|| { - let token = token.value; - self.resolve_offset_in_format_args( - ast::String::cast(token)?, - offset.checked_sub(string_start)?, - ) - .map(|(range, res)| (range + string_start, res)) - })() - .map_or(ControlFlow::Continue(()), ControlFlow::Break) - }) + ) -> Option<( + TextRange, + HirFileRange, + ast::String, + Option<Either<PathResolution, InlineAsmOperand>>, + )> { + let original_token = + self.wrap_token_infile(original_token).map(ast::String::cast).transpose()?; + self.check_for_format_args_template_with_file(original_token, offset) + } + + /// Retrieves the formatting part of the format_args! template string at the given offset. + /// + // FIXME: Type the return type + /// Returns the range (pre-expansion) in the string literal corresponding to the resolution, + /// absolute file range (post-expansion) + /// of the part in the format string, the corresponding string token and the resolution if it + /// exists. + pub fn check_for_format_args_template_with_file( + &self, + original_token: InFile<ast::String>, + offset: TextSize, + ) -> Option<( + TextRange, + HirFileRange, + ast::String, + Option<Either<PathResolution, InlineAsmOperand>>, + )> { + let relative_offset = + offset.checked_sub(original_token.value.syntax().text_range().start())?; + self.descend_into_macros_breakable( + original_token.as_ref().map(|it| it.syntax().clone()), + |token, _| { + (|| { + let token = token.map(ast::String::cast).transpose()?; + self.resolve_offset_in_format_args(token.as_ref(), relative_offset).map( + |(range, res)| { + ( + range + original_token.value.syntax().text_range().start(), + HirFileRange { + file_id: token.file_id, + range: range + token.value.syntax().text_range().start(), + }, + token.value, + res, + ) + }, + ) + })() + .map_or(ControlFlow::Continue(()), ControlFlow::Break) + }, + ) } fn resolve_offset_in_format_args( &self, - string: ast::String, + InFile { value: string, file_id }: InFile<&ast::String>, offset: TextSize, ) -> Option<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)> { debug_assert!(offset <= string.syntax().text_range().len()); let literal = string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?; let parent = literal.parent()?; if let Some(format_args) = ast::FormatArgsExpr::cast(parent.clone()) { - let source_analyzer = &self.analyze_no_infer(format_args.syntax())?; - let format_args = self.wrap_node_infile(format_args); + let source_analyzer = + &self.analyze_impl(InFile::new(file_id, format_args.syntax()), None, false)?; source_analyzer - .resolve_offset_in_format_args(self.db, format_args.as_ref(), offset) + .resolve_offset_in_format_args(self.db, InFile::new(file_id, &format_args), offset) .map(|(range, res)| (range, res.map(Either::Left))) } else { let asm = ast::AsmExpr::cast(parent)?; - let source_analyzer = &self.analyze_no_infer(asm.syntax())?; + let source_analyzer = + self.analyze_impl(InFile::new(file_id, asm.syntax()), None, false)?; let line = asm.template().position(|it| *it.syntax() == literal)?; - let asm = self.wrap_node_infile(asm); - source_analyzer.resolve_offset_in_asm_template(asm.as_ref(), line, offset).map( - |(owner, (expr, range, index))| { + source_analyzer + .resolve_offset_in_asm_template(InFile::new(file_id, &asm), line, offset) + .map(|(owner, (expr, range, index))| { (range, Some(Either::Right(InlineAsmOperand { owner, expr, index }))) - }, - ) + }) } } @@ -809,14 +874,11 @@ impl<'db> SemanticsImpl<'db> { None => return res, }; let file = self.find_file(node.syntax()); - let Some(file_id) = file.file_id.file_id() else { - return res; - }; if first == last { // node is just the token, so descend the token self.descend_into_macros_impl( - InRealFile::new(file_id, first), + InFile::new(file.file_id, first), &mut |InFile { value, .. }, _ctx| { if let Some(node) = value .parent_ancestors() @@ -831,14 +893,14 @@ impl<'db> SemanticsImpl<'db> { } else { // Descend first and last token, then zip them to look for the node they belong to let mut scratch: SmallVec<[_; 1]> = smallvec![]; - self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token, _ctx| { + self.descend_into_macros_impl(InFile::new(file.file_id, first), &mut |token, _ctx| { scratch.push(token); CONTINUE_NO_BREAKS }); let mut scratch = scratch.into_iter(); self.descend_into_macros_impl( - InRealFile::new(file_id, last), + InFile::new(file.file_id, last), &mut |InFile { value: last, file_id: last_fid }, _ctx| { if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() { if first_fid == last_fid { @@ -900,22 +962,18 @@ impl<'db> SemanticsImpl<'db> { token: SyntaxToken, mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext), ) { - if let Ok(token) = self.wrap_token_infile(token).into_real_file() { - self.descend_into_macros_impl(token, &mut |t, ctx| { - cb(t, ctx); - CONTINUE_NO_BREAKS - }); - } + self.descend_into_macros_impl(self.wrap_token_infile(token), &mut |t, ctx| { + cb(t, ctx); + CONTINUE_NO_BREAKS + }); } pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> { let mut res = smallvec![]; - if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() { - self.descend_into_macros_impl(token, &mut |t, _ctx| { - res.push(t.value); - CONTINUE_NO_BREAKS - }); - } + self.descend_into_macros_impl(self.wrap_token_infile(token.clone()), &mut |t, _ctx| { + res.push(t.value); + CONTINUE_NO_BREAKS + }); if res.is_empty() { res.push(token); } @@ -928,15 +986,13 @@ impl<'db> SemanticsImpl<'db> { ) -> SmallVec<[InFile<SyntaxToken>; 1]> { let mut res = smallvec![]; let token = self.wrap_token_infile(token); - if let Ok(token) = token.clone().into_real_file() { - self.descend_into_macros_impl(token, &mut |t, ctx| { - if !ctx.is_opaque(self.db) { - // Don't descend into opaque contexts - res.push(t); - } - CONTINUE_NO_BREAKS - }); - } + self.descend_into_macros_impl(token.clone(), &mut |t, ctx| { + if !ctx.is_opaque(self.db) { + // Don't descend into opaque contexts + res.push(t); + } + CONTINUE_NO_BREAKS + }); if res.is_empty() { res.push(token); } @@ -945,7 +1001,7 @@ impl<'db> SemanticsImpl<'db> { pub fn descend_into_macros_breakable<T>( &self, - token: InRealFile<SyntaxToken>, + token: InFile<SyntaxToken>, mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>, ) -> Option<T> { self.descend_into_macros_impl(token, &mut cb) @@ -974,33 +1030,58 @@ impl<'db> SemanticsImpl<'db> { r } + /// Descends the token into expansions, returning the tokens that matches the input + /// token's [`SyntaxKind`] and text. + pub fn descend_into_macros_exact_with_file( + &self, + token: SyntaxToken, + ) -> SmallVec<[InFile<SyntaxToken>; 1]> { + let mut r = smallvec![]; + let text = token.text(); + let kind = token.kind(); + + self.descend_into_macros_cb(token.clone(), |InFile { value, file_id }, ctx| { + let mapped_kind = value.kind(); + let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); + let matches = (kind == mapped_kind || any_ident_match()) + && text == value.text() + && !ctx.is_opaque(self.db); + if matches { + r.push(InFile { value, file_id }); + } + }); + if r.is_empty() { + r.push(self.wrap_token_infile(token)); + } + r + } + /// Descends the token into expansions, returning the first token that matches the input /// token's [`SyntaxKind`] and text. pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken { let text = token.text(); let kind = token.kind(); - if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() { - self.descend_into_macros_breakable(token, |InFile { value, file_id: _ }, _ctx| { + self.descend_into_macros_breakable( + self.wrap_token_infile(token.clone()), + |InFile { value, file_id: _ }, _ctx| { let mapped_kind = value.kind(); let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier(); let matches = (kind == mapped_kind || any_ident_match()) && text == value.text(); if matches { ControlFlow::Break(value) } else { ControlFlow::Continue(()) } - }) - } else { - None - } + }, + ) .unwrap_or(token) } fn descend_into_macros_impl<T>( &self, - InRealFile { value: token, file_id }: InRealFile<SyntaxToken>, + InFile { value: token, file_id }: InFile<SyntaxToken>, f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>, ) -> Option<T> { let _p = tracing::info_span!("descend_into_macros_impl").entered(); - let span = self.db.real_span_map(file_id).span_for_range(token.text_range()); + let span = self.db.span_map(file_id).span_for_range(token.text_range()); // Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| { @@ -1024,17 +1105,16 @@ impl<'db> SemanticsImpl<'db> { // the tokens themselves aren't that interesting as the span that is being used to map // things down never changes. let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![]; - let include = self.s2d_cache.borrow_mut().get_or_insert_include_for(self.db, file_id); + let include = file_id.file_id().and_then(|file_id| { + self.s2d_cache.borrow_mut().get_or_insert_include_for(self.db, file_id) + }); match include { Some(include) => { // include! inputs are always from real files, so they only need to be handled once upfront process_expansion_for_token(&mut stack, include)?; } None => { - stack.push(( - file_id.into(), - smallvec![(token, SyntaxContext::root(file_id.edition(self.db)))], - )); + stack.push((file_id, smallvec![(token, span.ctx)])); } } @@ -1678,6 +1758,11 @@ impl<'db> SemanticsImpl<'db> { self.with_ctx(|ctx| ctx.file_to_def(file).to_owned()).into_iter().map(Module::from) } + fn hir_file_to_module_defs(&self, file: HirFileId) -> impl Iterator<Item = Module> { + // FIXME: Do we need to care about inline modules for macro expansions? + self.file_to_module_defs(file.original_file_respecting_includes(self.db).file_id(self.db)) + } + pub fn scope(&self, node: &SyntaxNode) -> Option<SemanticsScope<'db>> { self.analyze_no_infer(node).map(|SourceAnalyzer { file_id, resolver, .. }| SemanticsScope { db: self.db, diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index d22812d3c69..ec2ccf8cba0 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -1303,6 +1303,7 @@ impl<'db> SourceAnalyzer<'db> { false } + /// Returns the range of the implicit template argument and its resolution at the given `offset` pub(crate) fn resolve_offset_in_format_args( &self, db: &'db dyn HirDatabase, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs index d310e11011b..f3243d369a0 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs @@ -128,11 +128,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< format!("Import `{import_name}`"), range, |builder| { - let scope = match scope.clone() { - ImportScope::File(it) => ImportScope::File(builder.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)), - }; + let scope = builder.make_import_scope_mut(scope.clone()); insert_use(&scope, mod_path_to_ast(&import_path, edition), &ctx.config.insert_use); }, ); @@ -153,11 +149,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< format!("Import `{import_name} as _`"), range, |builder| { - let scope = match scope.clone() { - ImportScope::File(it) => ImportScope::File(builder.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)), - }; + let scope = builder.make_import_scope_mut(scope.clone()); insert_use_as_alias( &scope, mod_path_to_ast(&import_path, edition), @@ -1877,4 +1869,30 @@ fn main() { ", ); } + + #[test] + fn carries_cfg_attr() { + check_assist( + auto_import, + r#" +mod m { + pub struct S; +} + +#[cfg(test)] +fn foo(_: S$0) {} +"#, + r#" +#[cfg(test)] +use m::S; + +mod m { + pub struct S; +} + +#[cfg(test)] +fn foo(_: S) {} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs index 00e9fdf124d..f73b8c4fd0f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_to_enum.rs @@ -312,12 +312,8 @@ fn replace_usages( } // add imports across modules where needed - if let Some((import_scope, path)) = import_data { - let scope = match import_scope { - ImportScope::File(it) => ImportScope::File(edit.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)), - }; + if let Some((scope, path)) = import_data { + let scope = edit.make_import_scope_mut(scope); delayed_mutations.push((scope, path)); } }, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index ed8aad7b2c6..5d75e445861 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -996,7 +996,8 @@ pub struct $0Foo { } "#, r#" -pub struct Foo(#[my_custom_attr] u32); +pub struct Foo(#[my_custom_attr] +u32); "#, ); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 777e366da95..0c0b93bcfbc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -923,7 +923,8 @@ where pub struct $0Foo(#[my_custom_attr] u32); "#, r#" -pub struct Foo { #[my_custom_attr] field1: u32 } +pub struct Foo { #[my_custom_attr] +field1: u32 } "#, ); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index e977798c4fd..cf45ea0a30d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -204,12 +204,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op .kind .is_some_and(|kind| matches!(kind, FlowKind::Break(_, _) | FlowKind::Continue(_))) { - let scope = match scope { - ImportScope::File(it) => ImportScope::File(builder.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)), - }; - + let scope = builder.make_import_scope_mut(scope); let control_flow_enum = FamousDefs(&ctx.sema, module.krate()).core_ops_ControlFlow(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs index c067747bc1b..fa005a411d3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs @@ -81,11 +81,7 @@ pub(crate) fn replace_qualified_name_with_use( |builder| { // Now that we've brought the name into scope, re-qualify all paths that could be // affected (that is, all paths inside the node we added the `use` to). - let scope = match scope { - ImportScope::File(it) => ImportScope::File(builder.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)), - }; + let scope = builder.make_import_scope_mut(scope); shorten_paths(scope.as_syntax_node(), &original_path); let path = drop_generic_args(&original_path); let edition = ctx diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs index ebb8ef99100..1f89a3d5f17 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unqualify_method_call.rs @@ -1,4 +1,3 @@ -use ide_db::imports::insert_use::ImportScope; use syntax::{ TextRange, ast::{self, AstNode, HasArgList, prec::ExprPrecedence}, @@ -114,11 +113,7 @@ fn add_import( ); if let Some(scope) = scope { - let scope = match scope { - ImportScope::File(it) => ImportScope::File(edit.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(edit.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(edit.make_mut(it)), - }; + let scope = edit.make_import_scope_mut(scope); ide_db::imports::insert_use::insert_use(&scope, import, &ctx.config.insert_use); } } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs index d26e5d62ced..813f38380f6 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs @@ -60,107 +60,87 @@ pub struct InsertUseConfig { } #[derive(Debug, Clone)] -pub enum ImportScope { +pub struct ImportScope { + pub kind: ImportScopeKind, + pub required_cfgs: Vec<ast::Attr>, +} + +#[derive(Debug, Clone)] +pub enum ImportScopeKind { File(ast::SourceFile), Module(ast::ItemList), Block(ast::StmtList), } impl ImportScope { - // FIXME: Remove this? - #[cfg(test)] - fn from(syntax: SyntaxNode) -> Option<Self> { - use syntax::match_ast; - fn contains_cfg_attr(attrs: &dyn HasAttrs) -> bool { - attrs.attrs().any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")) - } - match_ast! { - match syntax { - ast::Module(module) => module.item_list().map(ImportScope::Module), - ast::SourceFile(file) => Some(ImportScope::File(file)), - ast::Fn(func) => contains_cfg_attr(&func).then(|| func.body().and_then(|it| it.stmt_list().map(ImportScope::Block))).flatten(), - ast::Const(konst) => contains_cfg_attr(&konst).then(|| match konst.body()? { - ast::Expr::BlockExpr(block) => Some(block), - _ => None, - }).flatten().and_then(|it| it.stmt_list().map(ImportScope::Block)), - ast::Static(statik) => contains_cfg_attr(&statik).then(|| match statik.body()? { - ast::Expr::BlockExpr(block) => Some(block), - _ => None, - }).flatten().and_then(|it| it.stmt_list().map(ImportScope::Block)), - _ => None, - - } - } - } - /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. /// Returns the original source node inside attributes. pub fn find_insert_use_container( position: &SyntaxNode, sema: &Semantics<'_, RootDatabase>, ) -> Option<Self> { - fn contains_cfg_attr(attrs: &dyn HasAttrs) -> bool { - attrs.attrs().any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")) - } - + // The closest block expression ancestor + let mut block = None; + let mut required_cfgs = Vec::new(); // Walk up the ancestor tree searching for a suitable node to do insertions on // with special handling on cfg-gated items, in which case we want to insert imports locally // or FIXME: annotate inserted imports with the same cfg for syntax in sema.ancestors_with_macros(position.clone()) { if let Some(file) = ast::SourceFile::cast(syntax.clone()) { - return Some(ImportScope::File(file)); - } else if let Some(item) = ast::Item::cast(syntax) { - return match item { - ast::Item::Const(konst) if contains_cfg_attr(&konst) => { - // FIXME: Instead of bailing out with None, we should note down that - // this import needs an attribute added - match sema.original_ast_node(konst)?.body()? { - ast::Expr::BlockExpr(block) => block, - _ => return None, + return Some(ImportScope { kind: ImportScopeKind::File(file), required_cfgs }); + } else if let Some(module) = ast::Module::cast(syntax.clone()) { + // early return is important here, if we can't find the original module + // in the input there is no way for us to insert an import anywhere. + return sema + .original_ast_node(module)? + .item_list() + .map(ImportScopeKind::Module) + .map(|kind| ImportScope { kind, required_cfgs }); + } else if let Some(has_attrs) = ast::AnyHasAttrs::cast(syntax) { + if block.is_none() { + if let Some(b) = ast::BlockExpr::cast(has_attrs.syntax().clone()) { + if let Some(b) = sema.original_ast_node(b) { + block = b.stmt_list(); } - .stmt_list() - .map(ImportScope::Block) } - ast::Item::Fn(func) if contains_cfg_attr(&func) => { - // FIXME: Instead of bailing out with None, we should note down that - // this import needs an attribute added - sema.original_ast_node(func)?.body()?.stmt_list().map(ImportScope::Block) - } - ast::Item::Static(statik) if contains_cfg_attr(&statik) => { - // FIXME: Instead of bailing out with None, we should note down that - // this import needs an attribute added - match sema.original_ast_node(statik)?.body()? { - ast::Expr::BlockExpr(block) => block, - _ => return None, - } - .stmt_list() - .map(ImportScope::Block) - } - ast::Item::Module(module) => { - // early return is important here, if we can't find the original module - // in the input there is no way for us to insert an import anywhere. - sema.original_ast_node(module)?.item_list().map(ImportScope::Module) + } + if has_attrs + .attrs() + .any(|attr| attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg")) + { + if let Some(b) = block { + return Some(ImportScope { + kind: ImportScopeKind::Block(b), + required_cfgs, + }); } - _ => continue, - }; + required_cfgs.extend(has_attrs.attrs().filter(|attr| { + attr.as_simple_call().is_some_and(|(ident, _)| ident == "cfg") + })); + } } } None } pub fn as_syntax_node(&self) -> &SyntaxNode { - match self { - ImportScope::File(file) => file.syntax(), - ImportScope::Module(item_list) => item_list.syntax(), - ImportScope::Block(block) => block.syntax(), + match &self.kind { + ImportScopeKind::File(file) => file.syntax(), + ImportScopeKind::Module(item_list) => item_list.syntax(), + ImportScopeKind::Block(block) => block.syntax(), } } pub fn clone_for_update(&self) -> Self { - match self { - ImportScope::File(file) => ImportScope::File(file.clone_for_update()), - ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()), - ImportScope::Block(block) => ImportScope::Block(block.clone_for_update()), + Self { + kind: match &self.kind { + ImportScopeKind::File(file) => ImportScopeKind::File(file.clone_for_update()), + ImportScopeKind::Module(item_list) => { + ImportScopeKind::Module(item_list.clone_for_update()) + } + ImportScopeKind::Block(block) => ImportScopeKind::Block(block.clone_for_update()), + }, + required_cfgs: self.required_cfgs.iter().map(|attr| attr.clone_for_update()).collect(), } } } @@ -216,6 +196,11 @@ fn insert_use_with_alias_option( use_tree.wrap_in_tree_list(); } let use_item = make::use_(None, use_tree).clone_for_update(); + for attr in + scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update()) + { + ted::insert(ted::Position::first_child_of(use_item.syntax()), attr); + } // merge into existing imports if possible if let Some(mb) = mb { @@ -229,7 +214,6 @@ fn insert_use_with_alias_option( } } } - // either we weren't allowed to merge or there is no import that fits the merge conditions // so look for the place we have to insert to insert_use_(scope, use_item, cfg.group); @@ -316,10 +300,10 @@ fn guess_granularity_from_scope(scope: &ImportScope) -> ImportGranularityGuess { } _ => None, }; - let mut use_stmts = match scope { - ImportScope::File(f) => f.items(), - ImportScope::Module(m) => m.items(), - ImportScope::Block(b) => b.items(), + let mut use_stmts = match &scope.kind { + ImportScopeKind::File(f) => f.items(), + ImportScopeKind::Module(m) => m.items(), + ImportScopeKind::Block(b) => b.items(), } .filter_map(use_stmt); let mut res = ImportGranularityGuess::Unknown; @@ -463,12 +447,12 @@ fn insert_use_(scope: &ImportScope, use_item: ast::Use, group_imports: bool) { } } - let l_curly = match scope { - ImportScope::File(_) => None, + let l_curly = match &scope.kind { + ImportScopeKind::File(_) => None, // don't insert the imports before the item list/block expr's opening curly brace - ImportScope::Module(item_list) => item_list.l_curly_token(), + ImportScopeKind::Module(item_list) => item_list.l_curly_token(), // don't insert the imports before the item list's opening curly brace - ImportScope::Block(block) => block.l_curly_token(), + ImportScopeKind::Block(block) => block.l_curly_token(), }; // there are no imports in this file at all // so put the import after all inner module attributes and possible license header comments diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs index 428ba1d5118..4a00854f01a 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use/tests.rs @@ -23,7 +23,7 @@ struct Struct; } #[test] -fn respects_cfg_attr_fn() { +fn respects_cfg_attr_fn_body() { check( r"bar::Bar", r#" @@ -41,6 +41,25 @@ fn foo() { } #[test] +fn respects_cfg_attr_fn_sig() { + check( + r"bar::Bar", + r#" +#[cfg(test)] +fn foo($0) {} +"#, + r#" +#[cfg(test)] +use bar::Bar; + +#[cfg(test)] +fn foo() {} +"#, + ImportGranularity::Crate, + ); +} + +#[test] fn respects_cfg_attr_const() { check( r"bar::Bar", @@ -59,6 +78,51 @@ const FOO: Bar = { } #[test] +fn respects_cfg_attr_impl() { + check( + r"bar::Bar", + r#" +#[cfg(test)] +impl () {$0} +"#, + r#" +#[cfg(test)] +use bar::Bar; + +#[cfg(test)] +impl () {} +"#, + ImportGranularity::Crate, + ); +} + +#[test] +fn respects_cfg_attr_multiple_layers() { + check( + r"bar::Bar", + r#" +#[cfg(test)] +impl () { + #[cfg(test2)] + fn f($0) {} +} +"#, + r#" +#[cfg(test)] +#[cfg(test2)] +use bar::Bar; + +#[cfg(test)] +impl () { + #[cfg(test2)] + fn f() {} +} +"#, + ImportGranularity::Crate, + ); +} + +#[test] fn insert_skips_lone_glob_imports() { check( "use foo::baz::A", @@ -813,7 +877,7 @@ use {std::io};", } #[test] -fn merge_groups_skip_attributed() { +fn merge_groups_cfg_vs_no_cfg() { check_crate( "std::io", r#" @@ -837,6 +901,25 @@ use {std::io}; } #[test] +fn merge_groups_cfg_matching() { + check_crate( + "std::io", + r#" +#[cfg(feature = "gated")] use std::fmt::{Result, Display}; + +#[cfg(feature = "gated")] +fn f($0) {} +"#, + r#" +#[cfg(feature = "gated")] use std::{fmt::{Display, Result}, io}; + +#[cfg(feature = "gated")] +fn f() {} +"#, + ); +} + +#[test] fn split_out_merge() { // FIXME: This is suboptimal, we want to get `use std::fmt::{self, Result}` // instead. @@ -1259,12 +1342,14 @@ fn check_with_config( }; let sema = &Semantics::new(&db); let source_file = sema.parse(file_id); - let syntax = source_file.syntax().clone_for_update(); let file = pos - .and_then(|pos| syntax.token_at_offset(pos.expect_offset()).next()?.parent()) + .and_then(|pos| source_file.syntax().token_at_offset(pos.expect_offset()).next()?.parent()) .and_then(|it| ImportScope::find_insert_use_container(&it, sema)) - .or_else(|| ImportScope::from(syntax)) - .unwrap(); + .unwrap_or_else(|| ImportScope { + kind: ImportScopeKind::File(source_file), + required_cfgs: vec![], + }) + .clone_for_update(); let path = ast::SourceFile::parse(&format!("use {path};"), span::Edition::CURRENT) .tree() .syntax() @@ -1349,7 +1434,7 @@ fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior } fn check_guess(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: ImportGranularityGuess) { - let syntax = ast::SourceFile::parse(ra_fixture, span::Edition::CURRENT).tree().syntax().clone(); - let file = ImportScope::from(syntax).unwrap(); + let syntax = ast::SourceFile::parse(ra_fixture, span::Edition::CURRENT).tree(); + let file = ImportScope { kind: ImportScopeKind::File(syntax), required_cfgs: vec![] }; assert_eq!(super::guess_granularity_from_scope(&file), expected); } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index d4ab7592927..c5ad64ed594 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -961,12 +961,16 @@ impl<'a> FindUsages<'a> { // Search for occurrences of the items name for offset in Self::match_indices(&text, finder, search_range) { let ret = tree.token_at_offset(offset).any(|token| { - let Some(str_token) = ast::String::cast(token.clone()) else { return false }; - if let Some((range, Some(nameres))) = - sema.check_for_format_args_template(token, offset) + if let Some((range, _frange, string_token, Some(nameres))) = + sema.check_for_format_args_template(token.clone(), offset) { - return self - .found_format_args_ref(file_id, range, str_token, nameres, sink); + return self.found_format_args_ref( + file_id, + range, + string_token, + nameres, + sink, + ); } false }); diff --git a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs index b1b58d6568c..16c0d8d97a7 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs @@ -5,6 +5,7 @@ use std::{collections::hash_map::Entry, fmt, iter, mem}; +use crate::imports::insert_use::{ImportScope, ImportScopeKind}; use crate::text_edit::{TextEdit, TextEditBuilder}; use crate::{SnippetCap, assists::Command, syntax_helpers::tree_diff::diff}; use base_db::AnchoredPathBuf; @@ -367,6 +368,17 @@ impl SourceChangeBuilder { pub fn make_mut<N: AstNode>(&mut self, node: N) -> N { self.mutated_tree.get_or_insert_with(|| TreeMutator::new(node.syntax())).make_mut(&node) } + + pub fn make_import_scope_mut(&mut self, scope: ImportScope) -> ImportScope { + ImportScope { + kind: match scope.kind.clone() { + ImportScopeKind::File(it) => ImportScopeKind::File(self.make_mut(it)), + ImportScopeKind::Module(it) => ImportScopeKind::Module(self.make_mut(it)), + ImportScopeKind::Block(it) => ImportScopeKind::Block(self.make_mut(it)), + }, + required_cfgs: scope.required_cfgs.iter().map(|it| self.make_mut(it.clone())).collect(), + } + } /// Returns a copy of the `node`, suitable for mutation. /// /// Syntax trees in rust-analyzer are typically immutable, and mutating diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs index ac1b599c49e..87c9397fb77 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs @@ -137,11 +137,7 @@ pub(crate) fn json_in_items( ) .with_fixes(Some(vec![{ let mut scb = SourceChangeBuilder::new(vfs_file_id); - let scope = match import_scope { - ImportScope::File(it) => ImportScope::File(scb.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(scb.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(scb.make_mut(it)), - }; + let scope = scb.make_import_scope_mut(import_scope); let current_module = semantics_scope.module(); let cfg = ImportPathConfig { diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index c60ca3562f6..7917aab8bf7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -67,7 +67,7 @@ pub(crate) fn goto_definition( }); } - if let Some((range, resolution)) = + if let Some((range, _, _, resolution)) = sema.check_for_format_args_template(original_token.clone(), offset) { return Some(RangeInfo::new( diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs index a78f5cdc9d0..a6c7ea29b09 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs @@ -53,7 +53,9 @@ pub(crate) fn goto_type_definition( } }); }; - if let Some((range, resolution)) = sema.check_for_format_args_template(token.clone(), offset) { + if let Some((range, _, _, resolution)) = + sema.check_for_format_args_template(token.clone(), offset) + { if let Some(ty) = resolution.and_then(|res| match Definition::from(res) { Definition::Const(it) => Some(it.ty(db)), Definition::Static(it) => Some(it.ty(db)), diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs index 520ba39a238..aa947921a9b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs +++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs @@ -11,7 +11,6 @@ use ide_db::{ preorder_expr_with_ctx_checker, }, }; -use span::FileId; use syntax::{ AstNode, SyntaxKind::{self, IDENT, INT_NUMBER}, @@ -61,13 +60,12 @@ pub(crate) fn highlight_related( let file_id = sema .attach_first_edition(file_id) .unwrap_or_else(|| EditionedFileId::current_edition(sema.db, file_id)); - let span_file_id = file_id.editioned_file_id(sema.db); let syntax = sema.parse(file_id).syntax().clone(); let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind { T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?` T![->] => 4, - kind if kind.is_keyword(span_file_id.edition()) => 3, + kind if kind.is_keyword(file_id.edition(sema.db)) => 3, IDENT | INT_NUMBER => 2, T![|] => 1, _ => 0, @@ -92,18 +90,11 @@ pub(crate) fn highlight_related( T![unsafe] if token.parent().and_then(ast::BlockExpr::cast).is_some() => { highlight_unsafe_points(sema, token).remove(&file_id) } - T![|] if config.closure_captures => { - highlight_closure_captures(sema, token, file_id, span_file_id.file_id()) + T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id), + T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id), + _ if config.references => { + highlight_references(sema, token, FilePosition { file_id, offset }) } - T![move] if config.closure_captures => { - highlight_closure_captures(sema, token, file_id, span_file_id.file_id()) - } - _ if config.references => highlight_references( - sema, - token, - FilePosition { file_id, offset }, - span_file_id.file_id(), - ), _ => None, } } @@ -112,7 +103,6 @@ fn highlight_closure_captures( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, file_id: EditionedFileId, - vfs_file_id: FileId, ) -> Option<Vec<HighlightedRange>> { let closure = token.parent_ancestors().take(2).find_map(ast::ClosureExpr::cast)?; let search_range = closure.body()?.syntax().text_range(); @@ -145,7 +135,7 @@ fn highlight_closure_captures( .sources(sema.db) .into_iter() .flat_map(|x| x.to_nav(sema.db)) - .filter(|decl| decl.file_id == vfs_file_id) + .filter(|decl| decl.file_id == file_id.file_id(sema.db)) .filter_map(|decl| decl.focus_range) .map(move |range| HighlightedRange { range, category }) .chain(usages) @@ -158,9 +148,8 @@ fn highlight_references( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, FilePosition { file_id, offset }: FilePosition, - vfs_file_id: FileId, ) -> Option<Vec<HighlightedRange>> { - let defs = if let Some((range, resolution)) = + let defs = if let Some((range, _, _, resolution)) = sema.check_for_format_args_template(token.clone(), offset) { match resolution.map(Definition::from) { @@ -270,7 +259,7 @@ fn highlight_references( .sources(sema.db) .into_iter() .flat_map(|x| x.to_nav(sema.db)) - .filter(|decl| decl.file_id == vfs_file_id) + .filter(|decl| decl.file_id == file_id.file_id(sema.db)) .filter_map(|decl| decl.focus_range) .map(|range| HighlightedRange { range, category }) .for_each(|x| { @@ -288,7 +277,7 @@ fn highlight_references( }, }; for nav in navs { - if nav.file_id != vfs_file_id { + if nav.file_id != file_id.file_id(sema.db) { continue; } let hl_range = nav.focus_range.map(|range| { diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 8bb1c708e25..5404a9dc2ce 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -200,7 +200,7 @@ fn hover_offset( }); } - if let Some((range, resolution)) = + if let Some((range, _, _, resolution)) = sema.check_for_format_args_template(original_token.clone(), offset) { let res = hover_for_definition( diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index d649dffbd90..82dbcde4c06 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -514,7 +514,6 @@ impl Analysis { self.with_db(|db| goto_type_definition::goto_type_definition(db, position)) } - /// Finds all usages of the reference at point. pub fn find_all_refs( &self, position: FilePosition, diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 4fa116444b7..c6a323d4081 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -8,6 +8,14 @@ //! for text occurrences of the identifier. If there's an `ast::NameRef` //! at the index that the match starts at and its tree parent is //! resolved to the search element definition, we get a reference. +//! +//! Special handling for constructors/initializations: +//! When searching for references to a struct/enum/variant, if the cursor is positioned on: +//! - `{` after a struct/enum/variant definition +//! - `(` for tuple structs/variants +//! - `;` for unit structs +//! - The type name in a struct/enum/variant definition +//! Then only constructor/initialization usages will be shown, filtering out other references. use hir::{PathResolution, Semantics}; use ide_db::{ @@ -28,27 +36,76 @@ use syntax::{ use crate::{FilePosition, HighlightedRange, NavigationTarget, TryToNav, highlight_related}; +/// Result of a reference search operation. #[derive(Debug, Clone)] pub struct ReferenceSearchResult { + /// Information about the declaration site of the searched item. + /// For ADTs (structs/enums), this points to the type definition. + /// May be None for primitives or items without clear declaration sites. pub declaration: Option<Declaration>, + /// All references found, grouped by file. + /// For ADTs when searching from a constructor position (e.g. on '{', '(', ';'), + /// this only includes constructor/initialization usages. + /// The map key is the file ID, and the value is a vector of (range, category) pairs. + /// - range: The text range of the reference in the file + /// - category: Metadata about how the reference is used (read/write/etc) pub references: IntMap<FileId, Vec<(TextRange, ReferenceCategory)>>, } +/// Information about the declaration site of a searched item. #[derive(Debug, Clone)] pub struct Declaration { + /// Navigation information to jump to the declaration pub nav: NavigationTarget, + /// Whether the declared item is mutable (relevant for variables) pub is_mut: bool, } // Feature: Find All References // -// Shows all references of the item at the cursor location +// Shows all references of the item at the cursor location. This includes: +// - Direct references to variables, functions, types, etc. +// - Constructor/initialization references when cursor is on struct/enum definition tokens +// - References in patterns and type contexts +// - References through dereferencing and borrowing +// - References in macro expansions +// +// Special handling for constructors: +// - When the cursor is on `{`, `(`, or `;` in a struct/enum definition +// - When the cursor is on the type name in a struct/enum definition +// These cases will show only constructor/initialization usages of the type // // | Editor | Shortcut | // |---------|----------| // | VS Code | <kbd>Shift+Alt+F12</kbd> | // //  + +/// Find all references to the item at the given position. +/// +/// # Arguments +/// * `sema` - Semantic analysis context +/// * `position` - Position in the file where to look for the item +/// * `search_scope` - Optional scope to limit the search (e.g. current crate only) +/// +/// # Returns +/// Returns `None` if no valid item is found at the position. +/// Otherwise returns a vector of `ReferenceSearchResult`, usually with one element. +/// Multiple results can occur in case of ambiguity or when searching for trait items. +/// +/// # Special cases +/// - Control flow keywords (break, continue, etc): Shows all related jump points +/// - Constructor search: When on struct/enum definition tokens (`{`, `(`, `;`), shows only initialization sites +/// - Format string arguments: Shows template parameter usages +/// - Lifetime parameters: Shows lifetime constraint usages +/// +/// # Constructor search +/// When the cursor is on specific tokens in a struct/enum definition: +/// - `{` after struct/enum/variant: Shows record literal initializations +/// - `(` after tuple struct/variant: Shows tuple literal initializations +/// - `;` after unit struct: Shows unit literal initializations +/// - Type name in definition: Shows all initialization usages +/// In these cases, other kinds of references (like type references) are filtered out. pub(crate) fn find_all_refs( sema: &Semantics<'_, RootDatabase>, position: FilePosition, @@ -143,7 +200,7 @@ pub(crate) fn find_defs( ) })?; - if let Some((_, resolution)) = sema.check_for_format_args_template(token.clone(), offset) { + if let Some((.., resolution)) = sema.check_for_format_args_template(token.clone(), offset) { return resolution.map(Definition::from).map(|it| vec![it]); } @@ -219,7 +276,19 @@ fn retain_adt_literal_usages( } } -/// Returns `Some` if the cursor is at a position for an item to search for all its constructor/literal usages +/// Returns `Some` if the cursor is at a position where we should search for constructor/initialization usages. +/// This is used to implement the special constructor search behavior when the cursor is on specific tokens +/// in a struct/enum/variant definition. +/// +/// # Returns +/// - `Some(name)` if the cursor is on: +/// - `{` after a struct/enum/variant definition +/// - `(` for tuple structs/variants +/// - `;` for unit structs +/// - The type name in a struct/enum/variant definition +/// - `None` otherwise +/// +/// The returned name is the name of the type whose constructor usages should be searched for. fn name_for_constructor_search(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> { let token = syntax.token_at_offset(position.offset).right_biased()?; let token_parent = token.parent()?; @@ -257,6 +326,16 @@ fn name_for_constructor_search(syntax: &SyntaxNode, position: FilePosition) -> O } } +/// Checks if a name reference is part of an enum variant literal expression. +/// Used to filter references when searching for enum variant constructors. +/// +/// # Arguments +/// * `sema` - Semantic analysis context +/// * `enum_` - The enum type to check against +/// * `name_ref` - The name reference to check +/// +/// # Returns +/// `true` if the name reference is used as part of constructing a variant of the given enum. fn is_enum_lit_name_ref( sema: &Semantics<'_, RootDatabase>, enum_: hir::Enum, @@ -284,12 +363,19 @@ fn is_enum_lit_name_ref( .unwrap_or(false) } +/// Checks if a path ends with the given name reference. +/// Helper function for checking constructor usage patterns. fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool { path.and_then(|path| path.segment()) .and_then(|segment| segment.name_ref()) .map_or(false, |segment| segment == *name_ref) } +/// Checks if a name reference is used in a literal (constructor) context. +/// Used to filter references when searching for struct/variant constructors. +/// +/// # Returns +/// `true` if the name reference is used as part of a struct/variant literal expression. fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool { name_ref.syntax().ancestors().find_map(|ancestor| { match_ast! { diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs index e6cda60cd95..0423e3da2c8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs @@ -203,7 +203,7 @@ fn find_definitions( ) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition)>> { let token = syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING)); - if let Some((range, Some(resolution))) = + if let Some((range, _, _, Some(resolution))) = token.and_then(|token| sema.check_for_format_args_template(token, offset)) { return Ok(vec![( diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index e1bc76318f8..3ca172977cb 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -542,7 +542,7 @@ fn descend_token( let mut t = None; let mut r = 0; - sema.descend_into_macros_breakable(token.clone(), |tok, _ctx| { + sema.descend_into_macros_breakable(token.clone().into(), |tok, _ctx| { // FIXME: Consider checking ctx transparency for being opaque? let my_rank = ranker.rank_token(&tok.value); diff --git a/src/tools/rust-analyzer/crates/mbe/src/tests.rs b/src/tools/rust-analyzer/crates/mbe/src/tests.rs index 3369dfff281..769455faac0 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/tests.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/tests.rs @@ -74,7 +74,8 @@ fn check_( "{}", syntax_bridge::prettify_macro_expansion::prettify_macro_expansion( node.syntax_node(), - &mut |it| it.clone() + &mut |_| None, + |_| () ) ); expect.assert_eq(&expect_res); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs index f04ada38893..40d05567fcc 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/dispatch.rs @@ -6,7 +6,7 @@ use std::{ use ide_db::base_db::{ DbPanicContext, - salsa::{self, Cancelled}, + salsa::{self, Cancelled, UnexpectedCycle}, }; use lsp_server::{ExtractError, Response, ResponseError}; use serde::{Serialize, de::DeserializeOwned}; @@ -349,11 +349,14 @@ where let mut message = "request handler panicked".to_owned(); if let Some(panic_message) = panic_message { message.push_str(": "); - message.push_str(panic_message) + message.push_str(panic_message); + } else if let Some(cycle) = panic.downcast_ref::<UnexpectedCycle>() { + tracing::error!("{cycle}"); + message.push_str(": unexpected cycle"); } else if let Ok(cancelled) = panic.downcast::<Cancelled>() { tracing::error!("Cancellation propagated out of salsa! This is a bug"); return Err(HandlerCancelledError::Inner(*cancelled)); - } + }; Ok(lsp_server::Response::new_err( id, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/from_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/from_proto.rs index fb8a9838290..02757616d4f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/from_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/from_proto.rs @@ -103,6 +103,7 @@ pub(crate) fn file_range_uri( pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> { let assist_kind = match &kind { + k if k == &lsp_types::CodeActionKind::EMPTY => AssistKind::Generate, k if k == &lsp_types::CodeActionKind::QUICKFIX => AssistKind::QuickFix, k if k == &lsp_types::CodeActionKind::REFACTOR => AssistKind::Refactor, k if k == &lsp_types::CodeActionKind::REFACTOR_EXTRACT => AssistKind::RefactorExtract, diff --git a/src/tools/rust-analyzer/crates/span/src/lib.rs b/src/tools/rust-analyzer/crates/span/src/lib.rs index 54f90908f36..f81648ac42c 100644 --- a/src/tools/rust-analyzer/crates/span/src/lib.rs +++ b/src/tools/rust-analyzer/crates/span/src/lib.rs @@ -112,7 +112,10 @@ pub struct EditionedFileId(u32); impl fmt::Debug for EditionedFileId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("EditionedFileId").field(&self.file_id()).field(&self.edition()).finish() + f.debug_tuple("EditionedFileId") + .field(&self.file_id().index()) + .field(&self.edition()) + .finish() } } diff --git a/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs b/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs index e815e07d80a..0a5c8df0d0a 100644 --- a/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs +++ b/src/tools/rust-analyzer/crates/syntax-bridge/src/prettify_macro_expansion.rs @@ -7,6 +7,13 @@ use syntax::{ ted::{self, Position}, }; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PrettifyWsKind { + Space, + Indent(usize), + Newline, +} + /// Renders a [`SyntaxNode`] with whitespace inserted between tokens that require them. /// /// This is an internal API that is only exported because `mbe` needs it for tests and cannot depend @@ -15,7 +22,8 @@ use syntax::{ #[deprecated = "use `hir_expand::prettify_macro_expansion()` instead"] pub fn prettify_macro_expansion( syn: SyntaxNode, - dollar_crate_replacement: &mut dyn FnMut(&SyntaxToken) -> SyntaxToken, + dollar_crate_replacement: &mut dyn FnMut(&SyntaxToken) -> Option<SyntaxToken>, + inspect_mods: impl FnOnce(&[(Position, PrettifyWsKind)]), ) -> SyntaxNode { let mut indent = 0; let mut last: Option<SyntaxKind> = None; @@ -27,14 +35,12 @@ pub fn prettify_macro_expansion( let after = Position::after; let do_indent = |pos: fn(_) -> Position, token: &SyntaxToken, indent| { - (pos(token.clone()), make::tokens::whitespace(&" ".repeat(4 * indent))) - }; - let do_ws = |pos: fn(_) -> Position, token: &SyntaxToken| { - (pos(token.clone()), make::tokens::single_space()) - }; - let do_nl = |pos: fn(_) -> Position, token: &SyntaxToken| { - (pos(token.clone()), make::tokens::single_newline()) + (pos(token.clone()), PrettifyWsKind::Indent(indent)) }; + let do_ws = + |pos: fn(_) -> Position, token: &SyntaxToken| (pos(token.clone()), PrettifyWsKind::Space); + let do_nl = + |pos: fn(_) -> Position, token: &SyntaxToken| (pos(token.clone()), PrettifyWsKind::Newline); for event in syn.preorder_with_tokens() { let token = match event { @@ -46,20 +52,19 @@ pub fn prettify_macro_expansion( ) => { if indent > 0 { - mods.push(( - Position::after(node.clone()), - make::tokens::whitespace(&" ".repeat(4 * indent)), - )); + mods.push((Position::after(node.clone()), PrettifyWsKind::Indent(indent))); } if node.parent().is_some() { - mods.push((Position::after(node), make::tokens::single_newline())); + mods.push((Position::after(node), PrettifyWsKind::Newline)); } continue; } _ => continue, }; if token.kind() == SyntaxKind::IDENT && token.text() == "$crate" { - dollar_crate_replacements.push((token.clone(), dollar_crate_replacement(&token))); + if let Some(replacement) = dollar_crate_replacement(&token) { + dollar_crate_replacements.push((token.clone(), replacement)); + } } let tok = &token; @@ -129,8 +134,16 @@ pub fn prettify_macro_expansion( last = Some(tok.kind()); } + inspect_mods(&mods); for (pos, insert) in mods { - ted::insert(pos, insert); + ted::insert_raw( + pos, + match insert { + PrettifyWsKind::Space => make::tokens::single_space(), + PrettifyWsKind::Indent(indent) => make::tokens::whitespace(&" ".repeat(4 * indent)), + PrettifyWsKind::Newline => make::tokens::single_newline(), + }, + ); } for (old, new) in dollar_crate_replacements { ted::replace(old, new); diff --git a/src/tools/rust-analyzer/crates/syntax/src/ted.rs b/src/tools/rust-analyzer/crates/syntax/src/ted.rs index 64d5ea084c1..6fcbdd006c2 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ted.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ted.rs @@ -5,6 +5,7 @@ use std::{mem, ops::RangeInclusive}; use parser::T; +use rowan::TextSize; use crate::{ SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, @@ -74,6 +75,12 @@ impl Position { }; Position { repr } } + pub fn offset(&self) -> TextSize { + match &self.repr { + PositionRepr::FirstChild(node) => node.text_range().start(), + PositionRepr::After(elem) => elem.text_range().end(), + } + } } pub fn insert(position: Position, elem: impl Element) { @@ -207,5 +214,12 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken } return Some(make::tokens::whitespace(&format!("\n{indent}"))); } + if left.kind() == SyntaxKind::ATTR { + let mut indent = IndentLevel::from_element(right); + if right.kind() == SyntaxKind::ATTR { + indent.0 = IndentLevel::from_element(left).0.max(indent.0); + } + return Some(make::tokens::whitespace(&format!("\n{indent}"))); + } Some(make::tokens::single_space()) } diff --git a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs index c35b7f229f6..7e2c787afc7 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs @@ -39,6 +39,13 @@ impl VfsPath { } } + pub fn into_abs_path(self) -> Option<AbsPathBuf> { + match self.0 { + VfsPathRepr::PathBuf(it) => Some(it), + VfsPathRepr::VirtualPath(_) => None, + } + } + /// Creates a new `VfsPath` with `path` adjoined to `self`. pub fn join(&self, path: &str) -> Option<VfsPath> { match &self.0 { diff --git a/src/tools/rust-analyzer/xtask/src/codegen.rs b/src/tools/rust-analyzer/xtask/src/codegen.rs index bba7ad73f38..19ca62e8a32 100644 --- a/src/tools/rust-analyzer/xtask/src/codegen.rs +++ b/src/tools/rust-analyzer/xtask/src/codegen.rs @@ -24,8 +24,8 @@ impl flags::Codegen { grammar::generate(self.check); assists_doc_tests::generate(self.check); parser_inline_tests::generate(self.check); - feature_docs::generate(self.check) - // diagnostics_docs::generate(self.check) doesn't generate any tests + feature_docs::generate(self.check); + diagnostics_docs::generate(self.check); // lints::generate(self.check) Updating clones the rust repo, so don't run it unless // explicitly asked for } diff --git a/src/tools/test-float-parse/Cargo.lock b/src/tools/test-float-parse/Cargo.lock new file mode 100644 index 00000000000..3f60423fed3 --- /dev/null +++ b/src/tools/test-float-parse/Cargo.lock @@ -0,0 +1,75 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "test-float-parse" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/src/tools/test-float-parse/Cargo.toml b/src/tools/test-float-parse/Cargo.toml new file mode 100644 index 00000000000..e407e322f9e --- /dev/null +++ b/src/tools/test-float-parse/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "test-float-parse" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +indicatif = { version = "0.17.8", default-features = false } +num = "0.4.3" +rand = "0.9.0" +rand_chacha = "0.9.0" +rayon = "1" + +[lib] +name = "test_float_parse" + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + # Internal features aren't marked known config by default + 'cfg(target_has_reliable_f16)', +] diff --git a/src/tools/test-float-parse/README.md b/src/tools/test-float-parse/README.md new file mode 100644 index 00000000000..5e2c43d1cad --- /dev/null +++ b/src/tools/test-float-parse/README.md @@ -0,0 +1,55 @@ +# Float Parsing Tests + +These are tests designed to test decimal to float conversions (`dec2flt`) used +by the standard library. + +It consists of a collection of test generators that each generate a set of +patterns intended to test a specific property. In addition, there are exhaustive +tests (for <= `f32`) and fuzzers (for anything that can't be run exhaustively). + +The generators work as follows: + +- Each generator is a struct that lives somewhere in the `gen` module. Usually + it is generic over a float type. +- These generators must implement `Iterator`, which should return a context type + that can be used to construct a test string (but usually not the string + itself). +- They must also implement the `Generator` trait, which provides a method to + write test context to a string as a test case, as well as some extra metadata. + + The split between context generation and string construction is so that we can + reuse string allocations. +- Each generator gets registered once for each float type. Each of these + generators then get their iterator called, and each test case checked against + the float type's parse implementation. + +Some generators produce decimal strings, others create bit patterns that need to +be bitcasted to the float type, which then uses its `Display` implementation to +write to a string. For these, float to decimal (`flt2dec`) conversions also get +tested, if unintentionally. + +For each test case, the following is done: + +- The test string is parsed to the float type using the standard library's + implementation. +- The test string is parsed separately to a `BigRational`, which acts as a + representation with infinite precision. +- The rational value then gets checked that it is within the float's + representable values (absolute value greater than the smallest number to round + to zero, but less less than the first value to round to infinity). If these + limits are exceeded, check that the parsed float reflects that. +- For real nonzero numbers, the parsed float is converted into a rational using + `significand * 2^exponent`. It is then checked against the actual rational + value, and verified to be within half a bit's precision of the parsed value. + Also it is checked that ties round to even. + +This is all highly parallelized with `rayon`; test generators can run in +parallel, and their tests get chunked and run in parallel. + +There is a simple command line that allows filtering which tests are run, +setting the number of iterations for fuzzing tests, limiting failures, setting +timeouts, etc. See `main.rs` or run with `--help` for options. + +Note that when running via `./x`, only tests that take less than a few minutes +are run by default. Navigate to the crate (or pass `-C` to Cargo) and run it +directly to run all tests or pass specific arguments. diff --git a/src/tools/test-float-parse/src/gen_/exhaustive.rs b/src/tools/test-float-parse/src/gen_/exhaustive.rs new file mode 100644 index 00000000000..01458fb0b60 --- /dev/null +++ b/src/tools/test-float-parse/src/gen_/exhaustive.rs @@ -0,0 +1,42 @@ +use std::fmt::Write; +use std::ops::RangeInclusive; + +use crate::{Float, Generator, Int}; + +/// Test every possible bit pattern. This is infeasible to run on any float types larger than +/// `f32` (which takes about an hour). +pub struct Exhaustive<F: Float> { + iter: RangeInclusive<F::Int>, +} + +impl<F: Float> Generator<F> for Exhaustive<F> +where + RangeInclusive<F::Int>: Iterator<Item = F::Int>, +{ + const SHORT_NAME: &'static str = "exhaustive"; + + type WriteCtx = F; + + fn total_tests() -> u64 { + 1u64.checked_shl(F::Int::BITS).expect("More than u64::MAX tests") + } + + fn new() -> Self { + Self { iter: F::Int::ZERO..=F::Int::MAX } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + write!(s, "{ctx:e}").unwrap(); + } +} + +impl<F: Float> Iterator for Exhaustive<F> +where + RangeInclusive<F::Int>: Iterator<Item = F::Int>, +{ + type Item = F; + + fn next(&mut self) -> Option<Self::Item> { + Some(F::from_bits(self.iter.next()?)) + } +} diff --git a/src/tools/test-float-parse/src/gen_/exponents.rs b/src/tools/test-float-parse/src/gen_/exponents.rs new file mode 100644 index 00000000000..3748e9d380c --- /dev/null +++ b/src/tools/test-float-parse/src/gen_/exponents.rs @@ -0,0 +1,95 @@ +use std::fmt::Write; +use std::ops::RangeInclusive; + +use crate::traits::BoxGenIter; +use crate::{Float, Generator}; + +const SMALL_COEFF_MAX: i32 = 10_000; +const SMALL_EXP_MAX: i32 = 300; + +const SMALL_COEFF_RANGE: RangeInclusive<i32> = (-SMALL_COEFF_MAX)..=SMALL_COEFF_MAX; +const SMALL_EXP_RANGE: RangeInclusive<i32> = (-SMALL_EXP_MAX)..=SMALL_EXP_MAX; + +const LARGE_COEFF_RANGE: RangeInclusive<u32> = 0..=100_000; +const LARGE_EXP_RANGE: RangeInclusive<u32> = 300..=350; + +/// Check exponential values around zero. +pub struct SmallExponents<F: Float> { + iter: BoxGenIter<Self, F>, +} + +impl<F: Float> Generator<F> for SmallExponents<F> { + const NAME: &'static str = "small exponents"; + const SHORT_NAME: &'static str = "small exp"; + + /// `(coefficient, exponent)` + type WriteCtx = (i32, i32); + + fn total_tests() -> u64 { + ((1 + SMALL_COEFF_RANGE.end() - SMALL_COEFF_RANGE.start()) + * (1 + SMALL_EXP_RANGE.end() - SMALL_EXP_RANGE.start())) + .try_into() + .unwrap() + } + + fn new() -> Self { + let iter = SMALL_EXP_RANGE.flat_map(|exp| SMALL_COEFF_RANGE.map(move |coeff| (coeff, exp))); + + Self { iter: Box::new(iter) } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + let (coeff, exp) = ctx; + write!(s, "{coeff}e{exp}").unwrap(); + } +} + +impl<F: Float> Iterator for SmallExponents<F> { + type Item = (i32, i32); + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next() + } +} + +/// Check exponential values further from zero. +pub struct LargeExponents<F: Float> { + iter: BoxGenIter<Self, F>, +} + +impl<F: Float> Generator<F> for LargeExponents<F> { + const NAME: &'static str = "large positive exponents"; + const SHORT_NAME: &'static str = "large exp"; + + /// `(coefficient, exponent, is_positive)` + type WriteCtx = (u32, u32, bool); + + fn total_tests() -> u64 { + ((1 + LARGE_EXP_RANGE.end() - LARGE_EXP_RANGE.start()) + * (1 + LARGE_COEFF_RANGE.end() - LARGE_COEFF_RANGE.start()) + * 2) + .into() + } + + fn new() -> Self { + let iter = LARGE_EXP_RANGE + .flat_map(|exp| LARGE_COEFF_RANGE.map(move |coeff| (coeff, exp))) + .flat_map(|(coeff, exp)| [(coeff, exp, false), (coeff, exp, true)]); + + Self { iter: Box::new(iter) } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + let (coeff, exp, is_positive) = ctx; + let sign = if is_positive { "" } else { "-" }; + write!(s, "{sign}{coeff}e{exp}").unwrap(); + } +} + +impl<F: Float> Iterator for LargeExponents<F> { + type Item = (u32, u32, bool); + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next() + } +} diff --git a/src/tools/test-float-parse/src/gen_/fuzz.rs b/src/tools/test-float-parse/src/gen_/fuzz.rs new file mode 100644 index 00000000000..1d6c5562a14 --- /dev/null +++ b/src/tools/test-float-parse/src/gen_/fuzz.rs @@ -0,0 +1,87 @@ +use std::any::{TypeId, type_name}; +use std::collections::BTreeMap; +use std::fmt::Write; +use std::marker::PhantomData; +use std::ops::Range; +use std::sync::Mutex; + +use rand::Rng; +use rand::distr::{Distribution, StandardUniform}; +use rand_chacha::ChaCha8Rng; +use rand_chacha::rand_core::SeedableRng; + +use crate::{Float, Generator, Int, SEED}; + +/// Mapping of float types to the number of iterations that should be run. +/// +/// We could probably make `Generator::new` take an argument instead of the global state, +/// but we only load this once so it works. +static FUZZ_COUNTS: Mutex<BTreeMap<TypeId, u64>> = Mutex::new(BTreeMap::new()); + +/// Generic fuzzer; just tests deterministic random bit patterns N times. +pub struct Fuzz<F> { + iter: Range<u64>, + rng: ChaCha8Rng, + /// Allow us to use generics in `Iterator`. + marker: PhantomData<F>, +} + +impl<F: Float> Fuzz<F> { + /// Register how many iterations the fuzzer should run for a type. Uses some logic by + /// default, but if `from_cfg` is `Some`, that will be used instead. + pub fn set_iterations(from_cfg: Option<u64>) { + let count = if let Some(cfg_count) = from_cfg { + cfg_count + } else if F::BITS <= crate::MAX_BITS_FOR_EXHAUUSTIVE { + // If we run exhaustively, still fuzz but only do half as many bits. The only goal here is + // to catch failures from e.g. high bit patterns before exhaustive tests would get to them. + (F::Int::MAX >> (F::BITS / 2)).try_into().unwrap() + } else { + // Eveything bigger gets a fuzz test with as many iterations as `f32` exhaustive. + u32::MAX.into() + }; + + let _ = FUZZ_COUNTS.lock().unwrap().insert(TypeId::of::<F>(), count); + } +} + +impl<F: Float> Generator<F> for Fuzz<F> +where + StandardUniform: Distribution<<F as Float>::Int>, +{ + const SHORT_NAME: &'static str = "fuzz"; + + type WriteCtx = F; + + fn total_tests() -> u64 { + *FUZZ_COUNTS + .lock() + .unwrap() + .get(&TypeId::of::<F>()) + .unwrap_or_else(|| panic!("missing fuzz count for {}", type_name::<F>())) + } + + fn new() -> Self { + let rng = ChaCha8Rng::from_seed(SEED); + + Self { iter: 0..Self::total_tests(), rng, marker: PhantomData } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + write!(s, "{ctx:e}").unwrap(); + } +} + +impl<F: Float> Iterator for Fuzz<F> +where + StandardUniform: Distribution<<F as Float>::Int>, +{ + type Item = <Self as Generator<F>>::WriteCtx; + + fn next(&mut self) -> Option<Self::Item> { + let _ = self.iter.next()?; + let i: F::Int = self.rng.random(); + + Some(F::from_bits(i)) + } +} diff --git a/src/tools/test-float-parse/src/gen_/integers.rs b/src/tools/test-float-parse/src/gen_/integers.rs new file mode 100644 index 00000000000..070d188e88c --- /dev/null +++ b/src/tools/test-float-parse/src/gen_/integers.rs @@ -0,0 +1,104 @@ +use std::fmt::Write; +use std::ops::{Range, RangeInclusive}; + +use crate::traits::BoxGenIter; +use crate::{Float, Generator}; + +const SMALL_MAX_POW2: u32 = 19; + +/// All values up to the max power of two +const SMALL_VALUES: RangeInclusive<i32> = { + let max = 1i32 << SMALL_MAX_POW2; + (-max)..=max +}; + +/// Large values only get tested around powers of two +const LARGE_POWERS: Range<u32> = SMALL_MAX_POW2..128; + +/// We perturbe each large value around these ranges +const LARGE_PERTURBATIONS: RangeInclusive<i128> = -256..=256; + +/// Test all integers up to `2 ^ MAX_POW2` +pub struct SmallInt { + iter: RangeInclusive<i32>, +} + +impl<F: Float> Generator<F> for SmallInt { + const NAME: &'static str = "small integer values"; + const SHORT_NAME: &'static str = "int small"; + + type WriteCtx = i32; + + fn total_tests() -> u64 { + (SMALL_VALUES.end() + 1 - SMALL_VALUES.start()).try_into().unwrap() + } + + fn new() -> Self { + Self { iter: SMALL_VALUES } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + write!(s, "{ctx}").unwrap(); + } +} + +impl Iterator for SmallInt { + type Item = i32; + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next() + } +} + +/// Test much bigger integers than [`SmallInt`]. +pub struct LargeInt<F: Float> { + iter: BoxGenIter<Self, F>, +} + +impl<F: Float> LargeInt<F> { + const EDGE_CASES: [i128; 7] = [ + i32::MIN as i128, + i32::MAX as i128, + i64::MIN as i128, + i64::MAX as i128, + u64::MAX as i128, + i128::MIN, + i128::MAX, + ]; +} + +impl<F: Float> Generator<F> for LargeInt<F> { + const NAME: &'static str = "large integer values"; + const SHORT_NAME: &'static str = "int large"; + + type WriteCtx = i128; + + fn total_tests() -> u64 { + u64::try_from( + (i128::from(LARGE_POWERS.end - LARGE_POWERS.start) + + i128::try_from(Self::EDGE_CASES.len()).unwrap()) + * (LARGE_PERTURBATIONS.end() + 1 - LARGE_PERTURBATIONS.start()), + ) + .unwrap() + } + + fn new() -> Self { + let iter = LARGE_POWERS + .map(|pow| 1i128 << pow) + .chain(Self::EDGE_CASES) + .flat_map(|base| LARGE_PERTURBATIONS.map(move |perturb| base.saturating_add(perturb))); + + Self { iter: Box::new(iter) } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + write!(s, "{ctx}").unwrap(); + } +} +impl<F: Float> Iterator for LargeInt<F> { + type Item = i128; + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next() + } +} diff --git a/src/tools/test-float-parse/src/gen_/long_fractions.rs b/src/tools/test-float-parse/src/gen_/long_fractions.rs new file mode 100644 index 00000000000..b75148b779c --- /dev/null +++ b/src/tools/test-float-parse/src/gen_/long_fractions.rs @@ -0,0 +1,58 @@ +use std::char; +use std::fmt::Write; + +use crate::{Float, Generator}; + +/// Number of decimal digits to check (all of them). +const MAX_DIGIT: u32 = 9; +/// Test with this many decimals in the string. +const MAX_DECIMALS: usize = 410; +const PREFIX: &str = "0."; + +/// Test e.g. `0.1`, `0.11`, `0.111`, `0.1111`, ..., `0.2`, `0.22`, ... +pub struct RepeatingDecimal { + digit: u32, + buf: String, +} + +impl<F: Float> Generator<F> for RepeatingDecimal { + const NAME: &'static str = "repeating decimal"; + const SHORT_NAME: &'static str = "dec rep"; + + type WriteCtx = String; + + fn total_tests() -> u64 { + u64::from(MAX_DIGIT + 1) * u64::try_from(MAX_DECIMALS + 1).unwrap() + 1 + } + + fn new() -> Self { + Self { digit: 0, buf: PREFIX.to_owned() } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + *s = ctx; + } +} + +impl Iterator for RepeatingDecimal { + type Item = String; + + fn next(&mut self) -> Option<Self::Item> { + if self.digit > MAX_DIGIT { + return None; + } + + let digit = self.digit; + let inc_digit = self.buf.len() - PREFIX.len() > MAX_DECIMALS; + + if inc_digit { + // Reset the string + self.buf.clear(); + self.digit += 1; + self.buf.write_str(PREFIX).unwrap(); + } + + self.buf.push(char::from_digit(digit, 10).unwrap()); + Some(self.buf.clone()) + } +} diff --git a/src/tools/test-float-parse/src/gen_/many_digits.rs b/src/tools/test-float-parse/src/gen_/many_digits.rs new file mode 100644 index 00000000000..741e11437fe --- /dev/null +++ b/src/tools/test-float-parse/src/gen_/many_digits.rs @@ -0,0 +1,84 @@ +use std::char; +use std::fmt::Write; +use std::marker::PhantomData; +use std::ops::{Range, RangeInclusive}; + +use rand::distr::{Distribution, Uniform}; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha8Rng; + +use crate::{Float, Generator, SEED}; + +/// Total iterations +const ITERATIONS: u64 = 5_000_000; + +/// Possible lengths of the string, excluding decimals and exponents +const POSSIBLE_NUM_DIGITS: RangeInclusive<usize> = 100..=400; + +/// Range of possible exponents +const EXP_RANGE: Range<i32> = -4500..4500; + +/// Try strings of random digits. +pub struct RandDigits<F> { + rng: ChaCha8Rng, + iter: Range<u64>, + uniform: Uniform<u32>, + /// Allow us to use generics in `Iterator`. + marker: PhantomData<F>, +} + +impl<F: Float> Generator<F> for RandDigits<F> { + const NAME: &'static str = "random digits"; + + const SHORT_NAME: &'static str = "rand digits"; + + type WriteCtx = String; + + fn total_tests() -> u64 { + ITERATIONS + } + + fn new() -> Self { + let rng = ChaCha8Rng::from_seed(SEED); + let range = Uniform::try_from(0..10).unwrap(); + + Self { rng, iter: 0..ITERATIONS, uniform: range, marker: PhantomData } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + *s = ctx; + } +} + +impl<F: Float> Iterator for RandDigits<F> { + type Item = String; + + fn next(&mut self) -> Option<Self::Item> { + let _ = self.iter.next()?; + let num_digits = self.rng.random_range(POSSIBLE_NUM_DIGITS); + let has_decimal = self.rng.random_bool(0.2); + let has_exp = self.rng.random_bool(0.2); + + let dec_pos = if has_decimal { Some(self.rng.random_range(0..num_digits)) } else { None }; + + let mut s = String::with_capacity(num_digits); + + for pos in 0..num_digits { + let digit = char::from_digit(self.uniform.sample(&mut self.rng), 10).unwrap(); + s.push(digit); + + if let Some(dec_pos) = dec_pos { + if pos == dec_pos { + s.push('.'); + } + } + } + + if has_exp { + let exp = self.rng.random_range(EXP_RANGE); + write!(s, "e{exp}").unwrap(); + } + + Some(s) + } +} diff --git a/src/tools/test-float-parse/src/gen_/sparse.rs b/src/tools/test-float-parse/src/gen_/sparse.rs new file mode 100644 index 00000000000..72b65d4ce7f --- /dev/null +++ b/src/tools/test-float-parse/src/gen_/sparse.rs @@ -0,0 +1,99 @@ +use std::fmt::Write; + +use crate::traits::BoxGenIter; +use crate::{Float, Generator}; + +const POWERS_OF_TWO: [u128; 128] = make_powers_of_two(); + +const fn make_powers_of_two() -> [u128; 128] { + let mut ret = [0; 128]; + let mut i = 0; + while i < 128 { + ret[i] = 1 << i; + i += 1; + } + + ret +} + +/// Can't clone this result because of lifetime errors, just use a macro. +macro_rules! pow_iter { + () => { + (0..F::BITS).map(|i| F::Int::try_from(POWERS_OF_TWO[i as usize]).unwrap()) + }; +} + +/// Test all numbers that include three 1s in the binary representation as integers. +pub struct FewOnesInt<F: Float> +where + FewOnesInt<F>: Generator<F>, +{ + iter: BoxGenIter<Self, F>, +} + +impl<F: Float> Generator<F> for FewOnesInt<F> +where + <F::Int as TryFrom<u128>>::Error: std::fmt::Debug, +{ + const SHORT_NAME: &'static str = "few ones int"; + + type WriteCtx = F::Int; + + fn total_tests() -> u64 { + u64::from(F::BITS).pow(3) + } + + fn new() -> Self { + let iter = pow_iter!() + .flat_map(move |a| pow_iter!().map(move |b| (a, b))) + .flat_map(move |(a, b)| pow_iter!().map(move |c| (a, b, c))) + .map(|(a, b, c)| a | b | c); + + Self { iter: Box::new(iter) } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + write!(s, "{ctx}").unwrap(); + } +} + +impl<F: Float> Iterator for FewOnesInt<F> { + type Item = F::Int; + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next() + } +} + +/// Similar to `FewOnesInt` except test those bit patterns as a float. +pub struct FewOnesFloat<F: Float>(FewOnesInt<F>); + +impl<F: Float> Generator<F> for FewOnesFloat<F> +where + <F::Int as TryFrom<u128>>::Error: std::fmt::Debug, +{ + const NAME: &'static str = "few ones float"; + const SHORT_NAME: &'static str = "few ones float"; + + type WriteCtx = F; + + fn total_tests() -> u64 { + FewOnesInt::<F>::total_tests() + } + + fn new() -> Self { + Self(FewOnesInt::new()) + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + write!(s, "{ctx:e}").unwrap(); + } +} + +impl<F: Float> Iterator for FewOnesFloat<F> { + type Item = F; + + fn next(&mut self) -> Option<Self::Item> { + self.0.next().map(|i| F::from_bits(i)) + } +} diff --git a/src/tools/test-float-parse/src/gen_/spot_checks.rs b/src/tools/test-float-parse/src/gen_/spot_checks.rs new file mode 100644 index 00000000000..18691f9d6cf --- /dev/null +++ b/src/tools/test-float-parse/src/gen_/spot_checks.rs @@ -0,0 +1,101 @@ +use std::fmt::Write; + +use crate::traits::{Float, Generator}; + +const SPECIAL: &[&str] = &[ + "inf", "Inf", "iNf", "INF", "-inf", "-Inf", "-iNf", "-INF", "+inf", "+Inf", "+iNf", "+INF", + "nan", "NaN", "NAN", "nAn", "-nan", "-NaN", "-NAN", "-nAn", "+nan", "+NaN", "+NAN", "+nAn", + "1", "-1", "+1", "1e1", "-1e1", "+1e1", "1e-1", "-1e-1", "+1e-1", "1e+1", "-1e+1", "+1e+1", + "1E1", "-1E1", "+1E1", "1E-1", "-1E-1", "+1E-1", "1E+1", "-1E+1", "+1E+1", "0", "-0", "+0", +]; + +/// Check various non-numeric special strings. +pub struct Special { + iter: std::slice::Iter<'static, &'static str>, +} + +impl<F: Float> Generator<F> for Special { + const NAME: &'static str = "special values"; + + const SHORT_NAME: &'static str = "special"; + + type WriteCtx = &'static str; + + fn total_tests() -> u64 { + SPECIAL.len().try_into().unwrap() + } + + fn new() -> Self { + Self { iter: SPECIAL.iter() } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + s.write_str(ctx).unwrap(); + } +} + +impl Iterator for Special { + type Item = &'static str; + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next().copied() + } +} + +/// Strings that we know have failed in the past +const REGRESSIONS: &[&str] = &[ + // From <https://github.com/rust-lang/rust/issues/31407> + "1234567890123456789012345678901234567890e-340", + "2.225073858507201136057409796709131975934819546351645648023426109724822222021076945516529523908135087914149158913039621106870086438694594645527657207407820621743379988141063267329253552286881372149012981122451451889849057222307285255133155755015914397476397983411801999323962548289017107081850690630666655994938275772572015763062690663332647565300009245888316433037779791869612049497390377829704905051080609940730262937128958950003583799967207254304360284078895771796150945516748243471030702609144621572289880258182545180325707018860872113128079512233426288368622321503775666622503982534335974568884423900265498198385487948292206894721689831099698365846814022854243330660339850886445804001034933970427567186443383770486037861622771738545623065874679014086723327636718749999999999999999999999999999999999999e-308", + "2.22507385850720113605740979670913197593481954635164564802342610972482222202107694551652952390813508791414915891303962110687008643869459464552765720740782062174337998814106326732925355228688137214901298112245145188984905722230728525513315575501591439747639798341180199932396254828901710708185069063066665599493827577257201576306269066333264756530000924588831643303777979186961204949739037782970490505108060994073026293712895895000358379996720725430436028407889577179615094551674824347103070260914462157228988025818254518032570701886087211312807951223342628836862232150377566662250398253433597456888442390026549819838548794829220689472168983109969836584681402285424333066033985088644580400103493397042756718644338377048603786162277173854562306587467901408672332763671875e-308", + "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000222507385850720138309023271733240406421921598046233183055332741688720443481391819585428315901251102056406733973103581100515243416155346010885601238537771882113077799353200233047961014744258363607192156504694250373420837525080665061665815894872049117996859163964850063590877011830487479978088775374994945158045160505091539985658247081864511353793580499211598108576605199243335211435239014879569960959128889160299264151106346631339366347758651302937176204732563178148566435087212282863764204484681140761391147706280168985324411002416144742161856716615054015428508471675290190316132277889672970737312333408698898317506783884692609277397797285865965494109136909540613646756870239867831529068098461721092462539672851562500000000000000001", + "179769313486231580793728971405303415079934132710037826936173778980444968292764750946649017977587207096330286416692887910946555547851940402630657488671505820681908902000708383676273854845817711531764475730270069855571366959622842914819860834936475292719074168444365510704342711559699508093042880177904174497791.9999999999999999999999999999999999999999999999999999999999999999999999", + "2.47032822920623272e-324", + "6.631236871469758276785396630275967243399099947355303144249971758736286630139265439618068200788048744105960420552601852889715006376325666595539603330361800519107591783233358492337208057849499360899425128640718856616503093444922854759159988160304439909868291973931426625698663157749836252274523485312442358651207051292453083278116143932569727918709786004497872322193856150225415211997283078496319412124640111777216148110752815101775295719811974338451936095907419622417538473679495148632480391435931767981122396703443803335529756003353209830071832230689201383015598792184172909927924176339315507402234836120730914783168400715462440053817592702766213559042115986763819482654128770595766806872783349146967171293949598850675682115696218943412532098591327667236328125E-316", + "3.237883913302901289588352412501532174863037669423108059901297049552301970670676565786835742587799557860615776559838283435514391084153169252689190564396459577394618038928365305143463955100356696665629202017331344031730044369360205258345803431471660032699580731300954848363975548690010751530018881758184174569652173110473696022749934638425380623369774736560008997404060967498028389191878963968575439222206416981462690113342524002724385941651051293552601421155333430225237291523843322331326138431477823591142408800030775170625915670728657003151953664260769822494937951845801530895238439819708403389937873241463484205608000027270531106827387907791444918534771598750162812548862768493201518991668028251730299953143924168545708663913273994694463908672332763671875E-319", + "6.953355807847677105972805215521891690222119817145950754416205607980030131549636688806115726399441880065386399864028691275539539414652831584795668560082999889551357784961446896042113198284213107935110217162654939802416034676213829409720583759540476786936413816541621287843248433202369209916612249676005573022703244799714622116542188837770376022371172079559125853382801396219552418839469770514904192657627060319372847562301074140442660237844114174497210955449896389180395827191602886654488182452409583981389442783377001505462015745017848754574668342161759496661766020028752888783387074850773192997102997936619876226688096314989645766000479009083731736585750335262099860150896718774401964796827166283225641992040747894382698751809812609536720628966577351093292236328125E-310", + "3.339068557571188581835713701280943911923401916998521771655656997328440314559615318168849149074662609099998113009465566426808170378434065722991659642619467706034884424989741080790766778456332168200464651593995817371782125010668346652995912233993254584461125868481633343674905074271064409763090708017856584019776878812425312008812326260363035474811532236853359905334625575404216060622858633280744301892470300555678734689978476870369853549413277156622170245846166991655321535529623870646888786637528995592800436177901746286272273374471701452991433047257863864601424252024791567368195056077320885329384322332391564645264143400798619665040608077549162173963649264049738362290606875883456826586710961041737908872035803481241600376705491726170293986797332763671875E-319", + "2.4703282292062327208828439643411068618252990130716238221279284125033775363510437593264991818081799618989828234772285886546332835517796989819938739800539093906315035659515570226392290858392449105184435931802849936536152500319370457678249219365623669863658480757001585769269903706311928279558551332927834338409351978015531246597263579574622766465272827220056374006485499977096599470454020828166226237857393450736339007967761930577506740176324673600968951340535537458516661134223766678604162159680461914467291840300530057530849048765391711386591646239524912623653881879636239373280423891018672348497668235089863388587925628302755995657524455507255189313690836254779186948667994968324049705821028513185451396213837722826145437693412532098591327667236328124999e-324", + "2.4703282292062327208828439643411068618252990130716238221279284125033775363510437593264991818081799618989828234772285886546332835517796989819938739800539093906315035659515570226392290858392449105184435931802849936536152500319370457678249219365623669863658480757001585769269903706311928279558551332927834338409351978015531246597263579574622766465272827220056374006485499977096599470454020828166226237857393450736339007967761930577506740176324673600968951340535537458516661134223766678604162159680461914467291840300530057530849048765391711386591646239524912623653881879636239373280423891018672348497668235089863388587925628302755995657524455507255189313690836254779186948667994968324049705821028513185451396213837722826145437693412532098591327667236328125e-324", + "2.4703282292062327208828439643411068618252990130716238221279284125033775363510437593264991818081799618989828234772285886546332835517796989819938739800539093906315035659515570226392290858392449105184435931802849936536152500319370457678249219365623669863658480757001585769269903706311928279558551332927834338409351978015531246597263579574622766465272827220056374006485499977096599470454020828166226237857393450736339007967761930577506740176324673600968951340535537458516661134223766678604162159680461914467291840300530057530849048765391711386591646239524912623653881879636239373280423891018672348497668235089863388587925628302755995657524455507255189313690836254779186948667994968324049705821028513185451396213837722826145437693412532098591327667236328125001e-324", + "7.4109846876186981626485318930233205854758970392148714663837852375101326090531312779794975454245398856969484704316857659638998506553390969459816219401617281718945106978546710679176872575177347315553307795408549809608457500958111373034747658096871009590975442271004757307809711118935784838675653998783503015228055934046593739791790738723868299395818481660169122019456499931289798411362062484498678713572180352209017023903285791732520220528974020802906854021606612375549983402671300035812486479041385743401875520901590172592547146296175134159774938718574737870961645638908718119841271673056017045493004705269590165763776884908267986972573366521765567941072508764337560846003984904972149117463085539556354188641513168478436313080237596295773983001708984374999e-324", + "7.4109846876186981626485318930233205854758970392148714663837852375101326090531312779794975454245398856969484704316857659638998506553390969459816219401617281718945106978546710679176872575177347315553307795408549809608457500958111373034747658096871009590975442271004757307809711118935784838675653998783503015228055934046593739791790738723868299395818481660169122019456499931289798411362062484498678713572180352209017023903285791732520220528974020802906854021606612375549983402671300035812486479041385743401875520901590172592547146296175134159774938718574737870961645638908718119841271673056017045493004705269590165763776884908267986972573366521765567941072508764337560846003984904972149117463085539556354188641513168478436313080237596295773983001708984375e-324", + "7.4109846876186981626485318930233205854758970392148714663837852375101326090531312779794975454245398856969484704316857659638998506553390969459816219401617281718945106978546710679176872575177347315553307795408549809608457500958111373034747658096871009590975442271004757307809711118935784838675653998783503015228055934046593739791790738723868299395818481660169122019456499931289798411362062484498678713572180352209017023903285791732520220528974020802906854021606612375549983402671300035812486479041385743401875520901590172592547146296175134159774938718574737870961645638908718119841271673056017045493004705269590165763776884908267986972573366521765567941072508764337560846003984904972149117463085539556354188641513168478436313080237596295773983001708984375001e-324", + "94393431193180696942841837085033647913224148539854e-358", + "104308485241983990666713401708072175773165034278685682646111762292409330928739751702404658197872319129036519947435319418387839758990478549477777586673075945844895981012024387992135617064532141489278815239849108105951619997829153633535314849999674266169258928940692239684771590065027025835804863585454872499320500023126142553932654370362024104462255244034053203998964360882487378334860197725139151265590832887433736189468858614521708567646743455601905935595381852723723645799866672558576993978025033590728687206296379801363024094048327273913079612469982585674824156000783167963081616214710691759864332339239688734656548790656486646106983450809073750535624894296242072010195710276073042036425579852459556183541199012652571123898996574563824424330960027873516082763671875e-1075", + "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247032822920623272088284396434110686182529901307162382212792841250337753635104375932649918180817996189898282347722858865463328355177969898199387398005390939063150356595155702263922908583924491051844359318028499365361525003193704576782492193656236698636584807570015857692699037063119282795585513329278343384093519780155312465972635795746227664652728272200563740064854999770965994704540208281662262378573934507363390079677619305775067401763246736009689513405355374585166611342237666786041621596804619144672918403005300575308490487653917113865916462395249126236538818796362393732804238910186723484976682350898633885879256283027559956575244555072551893136908362547791869486679949683240497058210285131854513962138377228261454376934125320985913276672363281249", + "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247032822920623272088284396434110686182529901307162382212792841250337753635104375932649918180817996189898282347722858865463328355177969898199387398005390939063150356595155702263922908583924491051844359318028499365361525003193704576782492193656236698636584807570015857692699037063119282795585513329278343384093519780155312465972635795746227664652728272200563740064854999770965994704540208281662262378573934507363390079677619305775067401763246736009689513405355374585166611342237666786041621596804619144672918403005300575308490487653917113865916462395249126236538818796362393732804238910186723484976682350898633885879256283027559956575244555072551893136908362547791869486679949683240497058210285131854513962138377228261454376934125320985913276672363281251", +]; + +/// Check items that failed in the past. +pub struct RegressionCheck { + iter: std::slice::Iter<'static, &'static str>, +} + +impl<F: Float> Generator<F> for RegressionCheck { + const NAME: &'static str = "regression check"; + + const SHORT_NAME: &'static str = "regression"; + + type WriteCtx = &'static str; + + fn total_tests() -> u64 { + REGRESSIONS.len().try_into().unwrap() + } + + fn new() -> Self { + Self { iter: REGRESSIONS.iter() } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + s.write_str(ctx).unwrap(); + } +} + +impl Iterator for RegressionCheck { + type Item = &'static str; + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next().copied() + } +} diff --git a/src/tools/test-float-parse/src/gen_/subnorm.rs b/src/tools/test-float-parse/src/gen_/subnorm.rs new file mode 100644 index 00000000000..654f324b9b0 --- /dev/null +++ b/src/tools/test-float-parse/src/gen_/subnorm.rs @@ -0,0 +1,108 @@ +use std::fmt::Write; +use std::ops::RangeInclusive; + +use crate::{Float, Generator, Int}; + +/// Spot check some edge cases for subnormals. +pub struct SubnormEdgeCases<F: Float> { + cases: [F::Int; 6], + index: usize, +} + +impl<F: Float> SubnormEdgeCases<F> { + /// Shorthand + const I1: F::Int = F::Int::ONE; + + fn edge_cases() -> [F::Int; 6] { + // Comments use an 8-bit mantissa as a demo + [ + // 0b00000001 + Self::I1, + // 0b10000000 + Self::I1 << (F::MAN_BITS - 1), + // 0b00001000 + Self::I1 << ((F::MAN_BITS / 2) - 1), + // 0b00001111 + Self::I1 << ((F::MAN_BITS / 2) - 1), + // 0b00001111 + Self::I1 << ((F::MAN_BITS / 2) - 1), + // 0b11111111 + F::MAN_MASK, + ] + } +} + +impl<F: Float> Generator<F> for SubnormEdgeCases<F> { + const NAME: &'static str = "subnormal edge cases"; + const SHORT_NAME: &'static str = "subnorm edge"; + + type WriteCtx = F; + + fn new() -> Self { + Self { cases: Self::edge_cases(), index: 0 } + } + + fn total_tests() -> u64 { + Self::edge_cases().len().try_into().unwrap() + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + write!(s, "{ctx:e}").unwrap(); + } +} + +impl<F: Float> Iterator for SubnormEdgeCases<F> { + type Item = F; + + fn next(&mut self) -> Option<Self::Item> { + let i = self.cases.get(self.index)?; + self.index += 1; + + Some(F::from_bits(*i)) + } +} + +/// Test all subnormals up to `1 << 22`. +pub struct SubnormComplete<F: Float> { + iter: RangeInclusive<F::Int>, +} + +impl<F: Float> Generator<F> for SubnormComplete<F> +where + RangeInclusive<F::Int>: Iterator<Item = F::Int>, +{ + const NAME: &'static str = "subnormal"; + const SHORT_NAME: &'static str = "subnorm "; + + type WriteCtx = F; + + fn total_tests() -> u64 { + let iter = Self::new().iter; + (F::Int::ONE + *iter.end() - *iter.start()).try_into().unwrap() + } + + fn new() -> Self { + let upper_lim = if F::MAN_BITS >= 22 { + F::Int::ONE << 22 + } else { + (F::Int::ONE << F::MAN_BITS) - F::Int::ONE + }; + + Self { iter: F::Int::ZERO..=upper_lim } + } + + fn write_string(s: &mut String, ctx: Self::WriteCtx) { + write!(s, "{ctx:e}").unwrap(); + } +} + +impl<F: Float> Iterator for SubnormComplete<F> +where + RangeInclusive<F::Int>: Iterator<Item = F::Int>, +{ + type Item = F; + + fn next(&mut self) -> Option<Self::Item> { + Some(F::from_bits(self.iter.next()?)) + } +} diff --git a/src/tools/test-float-parse/src/lib.rs b/src/tools/test-float-parse/src/lib.rs new file mode 100644 index 00000000000..f590149523b --- /dev/null +++ b/src/tools/test-float-parse/src/lib.rs @@ -0,0 +1,419 @@ +#![feature(f16)] +#![feature(cfg_target_has_reliable_f16_f128)] +#![expect(internal_features)] // reliable_f16_f128 + +mod traits; +mod ui; +mod validate; + +use std::any::type_name; +use std::cmp::min; +use std::ops::RangeInclusive; +use std::process::ExitCode; +use std::sync::OnceLock; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::{fmt, time}; + +use rand::distr::{Distribution, StandardUniform}; +use rayon::prelude::*; +use time::{Duration, Instant}; +use traits::{Float, Generator, Int}; +use validate::CheckError; + +/// Test generators. +mod gen_ { + pub mod exhaustive; + pub mod exponents; + pub mod fuzz; + pub mod integers; + pub mod long_fractions; + pub mod many_digits; + pub mod sparse; + pub mod spot_checks; + pub mod subnorm; +} + +/// How many failures to exit after if unspecified. +const DEFAULT_MAX_FAILURES: u64 = 20; + +/// Register exhaustive tests only for <= 32 bits. No more because it would take years. +const MAX_BITS_FOR_EXHAUUSTIVE: u32 = 32; + +/// If there are more tests than this threshold, the test will be deferred until after all +/// others run (so as to avoid thread pool starvation). They also can be excluded with +/// `--skip-huge`. +const HUGE_TEST_CUTOFF: u64 = 5_000_000; + +/// Seed for tests that use a deterministic RNG. +const SEED: [u8; 32] = *b"3.141592653589793238462643383279"; + +/// Global configuration. +#[derive(Debug)] +pub struct Config { + pub timeout: Duration, + /// Failures per test + pub max_failures: u64, + pub disable_max_failures: bool, + /// If `None`, the default will be used + pub fuzz_count: Option<u64>, + pub skip_huge: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + timeout: Duration::from_secs(60 * 60 * 3), + max_failures: DEFAULT_MAX_FAILURES, + disable_max_failures: false, + fuzz_count: None, + skip_huge: false, + } + } +} + +/// Collect, filter, and launch all tests. +pub fn run(cfg: Config, include: &[String], exclude: &[String]) -> ExitCode { + // With default parallelism, the CPU doesn't saturate. We don't need to be nice to + // other processes, so do 1.5x to make sure we use all available resources. + let threads = std::thread::available_parallelism().map(Into::into).unwrap_or(0) * 3 / 2; + rayon::ThreadPoolBuilder::new().num_threads(threads).build_global().unwrap(); + + let mut tests = register_tests(&cfg); + println!("registered"); + let initial_tests: Vec<_> = tests.iter().map(|t| t.name.clone()).collect(); + + let unmatched: Vec<_> = include + .iter() + .chain(exclude.iter()) + .filter(|filt| !tests.iter().any(|t| t.matches(filt))) + .collect(); + + assert!( + unmatched.is_empty(), + "filters were provided that have no matching tests: {unmatched:#?}" + ); + + tests.retain(|test| !exclude.iter().any(|exc| test.matches(exc))); + + if cfg.skip_huge { + tests.retain(|test| !test.is_huge_test()); + } + + if !include.is_empty() { + tests.retain(|test| include.iter().any(|inc| test.matches(inc))); + } + + for exc in initial_tests.iter().filter(|orig_name| !tests.iter().any(|t| t.name == **orig_name)) + { + println!("Skipping test '{exc}'"); + } + + println!("Launching all"); + let elapsed = launch_tests(&mut tests, &cfg); + ui::finish_all(&tests, elapsed, &cfg) +} + +/// Enumerate tests to run but don't actually run them. +pub fn register_tests(cfg: &Config) -> Vec<TestInfo> { + let mut tests = Vec::new(); + + // Register normal generators for all floats. + + #[cfg(not(bootstrap))] + #[cfg(target_has_reliable_f16)] + register_float::<f16>(&mut tests, cfg); + register_float::<f32>(&mut tests, cfg); + register_float::<f64>(&mut tests, cfg); + + tests.sort_unstable_by_key(|t| (t.float_name, t.gen_name)); + for i in 0..(tests.len() - 1) { + if tests[i].gen_name == tests[i + 1].gen_name { + panic!("duplicate test name {}", tests[i].gen_name); + } + } + + tests +} + +/// Register all generators for a single float. +fn register_float<F: Float>(tests: &mut Vec<TestInfo>, cfg: &Config) +where + RangeInclusive<F::Int>: Iterator<Item = F::Int>, + <F::Int as TryFrom<u128>>::Error: std::fmt::Debug, + StandardUniform: Distribution<<F as traits::Float>::Int>, +{ + if F::BITS <= MAX_BITS_FOR_EXHAUUSTIVE { + // Only run exhaustive tests if there is a chance of completion. + TestInfo::register::<F, gen_::exhaustive::Exhaustive<F>>(tests); + } + + gen_::fuzz::Fuzz::<F>::set_iterations(cfg.fuzz_count); + + TestInfo::register::<F, gen_::exponents::LargeExponents<F>>(tests); + TestInfo::register::<F, gen_::exponents::SmallExponents<F>>(tests); + TestInfo::register::<F, gen_::fuzz::Fuzz<F>>(tests); + TestInfo::register::<F, gen_::integers::LargeInt<F>>(tests); + TestInfo::register::<F, gen_::integers::SmallInt>(tests); + TestInfo::register::<F, gen_::long_fractions::RepeatingDecimal>(tests); + TestInfo::register::<F, gen_::many_digits::RandDigits<F>>(tests); + TestInfo::register::<F, gen_::sparse::FewOnesFloat<F>>(tests); + TestInfo::register::<F, gen_::sparse::FewOnesInt<F>>(tests); + TestInfo::register::<F, gen_::spot_checks::RegressionCheck>(tests); + TestInfo::register::<F, gen_::spot_checks::Special>(tests); + TestInfo::register::<F, gen_::subnorm::SubnormComplete<F>>(tests); + TestInfo::register::<F, gen_::subnorm::SubnormEdgeCases<F>>(tests); +} + +/// Configuration for a single test. +#[derive(Debug)] +pub struct TestInfo { + pub name: String, + float_name: &'static str, + float_bits: u32, + gen_name: &'static str, + /// Name for display in the progress bar. + short_name: String, + /// Pad the short name to a common width for progress bar use. + short_name_padded: String, + total_tests: u64, + /// Function to launch this test. + launch: fn(&TestInfo, &Config), + /// Progress bar to be updated. + progress: Option<ui::Progress>, + /// Once completed, this will be set. + completed: OnceLock<Completed>, +} + +impl TestInfo { + /// Check if either the name or short name is a match, for filtering. + fn matches(&self, pat: &str) -> bool { + self.short_name.contains(pat) || self.name.contains(pat) + } + + /// Create a `TestInfo` for a given float and generator, then add it to a list. + fn register<F: Float, G: Generator<F>>(v: &mut Vec<Self>) { + let f_name = type_name::<F>(); + let gen_name = G::NAME; + let gen_short_name = G::SHORT_NAME; + let name = format!("{f_name} {gen_name}"); + let short_name = format!("{f_name} {gen_short_name}"); + let short_name_padded = format!("{short_name:18}"); + + let info = TestInfo { + float_name: f_name, + float_bits: F::BITS, + gen_name, + progress: None, + name, + short_name_padded, + short_name, + launch: test_runner::<F, G>, + total_tests: G::total_tests(), + completed: OnceLock::new(), + }; + v.push(info); + } + + /// True if this should be run after all others. + fn is_huge_test(&self) -> bool { + self.total_tests >= HUGE_TEST_CUTOFF + } + + /// When the test is finished, update progress bar messages and finalize. + fn complete(&self, c: Completed) { + self.progress.as_ref().unwrap().complete(&c, 0); + self.completed.set(c).unwrap(); + } +} + +/// Result of an input did not parsing successfully. +#[derive(Clone, Debug)] +enum CheckFailure { + /// Above the zero cutoff but got rounded to zero. + UnexpectedZero, + /// Below the infinity cutoff but got rounded to infinity. + UnexpectedInf, + /// Above the negative infinity cutoff but got rounded to negative infinity. + UnexpectedNegInf, + /// Got a `NaN` when none was expected. + UnexpectedNan, + /// Expected `NaN`, got none. + ExpectedNan, + /// Expected infinity, got finite. + ExpectedInf, + /// Expected negative infinity, got finite. + ExpectedNegInf, + /// The value exceeded its error tolerance. + InvalidReal { + /// Error from the expected value, as a float. + error_float: Option<f64>, + /// Error as a rational string (since it can't always be represented as a float). + error_str: Box<str>, + /// True if the error was caused by not rounding to even at the midpoint between + /// two representable values. + incorrect_midpoint_rounding: bool, + }, + /// String did not parse successfully. + ParsingFailed(Box<str>), + /// A panic was caught. + Panic(Box<str>), +} + +impl fmt::Display for CheckFailure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CheckFailure::UnexpectedZero => { + write!(f, "incorrectly rounded to 0 (expected nonzero)") + } + CheckFailure::UnexpectedInf => { + write!(f, "incorrectly rounded to +inf (expected finite)") + } + CheckFailure::UnexpectedNegInf => { + write!(f, "incorrectly rounded to -inf (expected finite)") + } + CheckFailure::UnexpectedNan => write!(f, "got a NaN where none was expected"), + CheckFailure::ExpectedNan => write!(f, "expected a NaN but did not get it"), + CheckFailure::ExpectedInf => write!(f, "expected +inf but did not get it"), + CheckFailure::ExpectedNegInf => write!(f, "expected -inf but did not get it"), + CheckFailure::InvalidReal { error_float, error_str, incorrect_midpoint_rounding } => { + if *incorrect_midpoint_rounding { + write!( + f, + "midpoint between two representable values did not correctly \ + round to even; error: {error_str}" + )?; + } else { + write!(f, "real number did not parse correctly; error: {error_str}")?; + } + + if let Some(float) = error_float { + write!(f, " ({float})")?; + } + Ok(()) + } + CheckFailure::ParsingFailed(e) => write!(f, "parsing failed: {e}"), + CheckFailure::Panic(e) => write!(f, "function panicked: {e}"), + } + } +} + +/// Information about a completed test generator. +#[derive(Clone, Debug)] +struct Completed { + /// Finished tests (both successful and failed). + executed: u64, + /// Failed tests. + failures: u64, + /// Extra exit information if unsuccessful. + result: Result<FinishedAll, EarlyExit>, + /// If there is something to warn about (e.g bad estimate), leave it here. + warning: Option<Box<str>>, + /// Total time to run the test. + elapsed: Duration, +} + +/// Marker for completing all tests (used in `Result` types). +#[derive(Clone, Debug)] +struct FinishedAll; + +/// Reasons for exiting early. +#[derive(Clone, Debug)] +enum EarlyExit { + Timeout, + MaxFailures, +} + +/// Run all tests in `tests`. +/// +/// This launches a main thread that receives messages and handlees UI updates, and uses the +/// rest of the thread pool to execute the tests. +fn launch_tests(tests: &mut [TestInfo], cfg: &Config) -> Duration { + // Run shorter tests and smaller float types first. + tests.sort_unstable_by_key(|test| (test.total_tests, test.float_bits)); + + for test in tests.iter() { + println!("Launching test '{}'", test.name); + } + + let mut all_progress_bars = Vec::new(); + let start = Instant::now(); + + for test in tests.iter_mut() { + test.progress = Some(ui::Progress::new(test, &mut all_progress_bars)); + ui::set_panic_hook(&all_progress_bars); + ((test.launch)(test, cfg)); + } + + start.elapsed() +} + +/// Test runer for a single generator. +/// +/// This calls the generator's iterator multiple times (in parallel) and validates each output. +fn test_runner<F: Float, G: Generator<F>>(test: &TestInfo, cfg: &Config) { + let gen_ = G::new(); + let executed = AtomicU64::new(0); + let failures = AtomicU64::new(0); + + let checks_per_update = min(test.total_tests, 1000); + let started = Instant::now(); + + // Function to execute for a single test iteration. + let check_one = |buf: &mut String, ctx: G::WriteCtx| { + let executed = executed.fetch_add(1, Ordering::Relaxed); + buf.clear(); + G::write_string(buf, ctx); + + match validate::validate::<F>(buf) { + Ok(()) => (), + Err(e) => { + let CheckError { fail, input, float_res } = e; + test.progress.as_ref().unwrap().println(&format!( + "Failure in '{}': {fail}. parsing '{input}'. Parsed as: {float_res}", + test.name + )); + + let f = failures.fetch_add(1, Ordering::Relaxed); + // End early if the limit is exceeded. + if f >= cfg.max_failures { + return Err(EarlyExit::MaxFailures); + } + } + }; + + // Send periodic updates + if executed % checks_per_update == 0 { + let failures = failures.load(Ordering::Relaxed); + test.progress.as_ref().unwrap().update(executed, failures); + if started.elapsed() > cfg.timeout { + return Err(EarlyExit::Timeout); + } + } + + Ok(()) + }; + + // Run the test iterations in parallel. Each thread gets a string buffer to write + // its check values to. + let res = gen_.par_bridge().try_for_each_init(String::new, check_one); + + let elapsed = started.elapsed(); + let executed = executed.into_inner(); + let failures = failures.into_inner(); + + // Warn about bad estimates if relevant. + let warning = if executed != test.total_tests && res.is_ok() { + let msg = format!( + "executed tests != estimated ({executed} != {}) for {}", + test.total_tests, + G::NAME + ); + + Some(msg.into()) + } else { + None + }; + + let result = res.map(|()| FinishedAll); + test.complete(Completed { executed, failures, result, warning, elapsed }); +} diff --git a/src/tools/test-float-parse/src/main.rs b/src/tools/test-float-parse/src/main.rs new file mode 100644 index 00000000000..9c6cad7324f --- /dev/null +++ b/src/tools/test-float-parse/src/main.rs @@ -0,0 +1,129 @@ +use std::process::ExitCode; +use std::time::Duration; + +use test_float_parse as tfp; + +static HELP: &str = r#"Usage: + + ./test-float-parse [--timeout x] [--exclude x] [--max-failures x] [INCLUDE ...] + ./test-float-parse [--fuzz-count x] [INCLUDE ...] + ./test-float-parse [--skip-huge] [INCLUDE ...] + ./test-float-parse --list + +Args: + + INCLUDE Include only tests with names containing these + strings. If this argument is not specified, all tests + are run. + --timeout N Exit after this amount of time (in seconds). + --exclude FILTER Skip tests containing this string. May be specified + more than once. + --list List available tests. + --max-failures N Limit to N failures per test. Defaults to 20. Pass + "--max-failures none" to remove this limit. + --fuzz-count N Run the fuzzer with N iterations. Only has an effect + if fuzz tests are enabled. Pass `--fuzz-count none` + to remove this limit. + --skip-huge Skip tests that run for a long time. + --all Reset previous `--exclude`, `--skip-huge`, and + `INCLUDE` arguments (useful for running all tests + via `./x`). +"#; + +enum ArgMode { + Any, + Timeout, + Exclude, + FuzzCount, + MaxFailures, +} + +fn main() -> ExitCode { + if cfg!(debug_assertions) { + println!( + "WARNING: running in debug mode. Release mode is recommended to reduce test duration." + ); + std::thread::sleep(Duration::from_secs(2)); + } + + let args: Vec<_> = std::env::args().skip(1).collect(); + if args.iter().any(|arg| arg == "--help" || arg == "-h") { + println!("{HELP}"); + return ExitCode::SUCCESS; + } + + if args.iter().any(|arg| arg == "--list") { + let tests = tfp::register_tests(&tfp::Config::default()); + println!("Available tests:"); + for t in tests { + println!("{}", t.name); + } + + return ExitCode::SUCCESS; + } + + let (cfg, include, exclude) = parse_args(args); + + tfp::run(cfg, &include, &exclude) +} + +/// Simple command argument parser +fn parse_args(args: Vec<String>) -> (tfp::Config, Vec<String>, Vec<String>) { + let mut cfg = tfp::Config::default(); + + let mut mode = ArgMode::Any; + let mut include = Vec::new(); + let mut exclude = Vec::new(); + + for arg in args { + mode = match mode { + ArgMode::Any if arg == "--timeout" => ArgMode::Timeout, + ArgMode::Any if arg == "--exclude" => ArgMode::Exclude, + ArgMode::Any if arg == "--max-failures" => ArgMode::MaxFailures, + ArgMode::Any if arg == "--fuzz-count" => ArgMode::FuzzCount, + ArgMode::Any if arg == "--skip-huge" => { + cfg.skip_huge = true; + ArgMode::Any + } + ArgMode::Any if arg == "--all" => { + cfg.skip_huge = false; + include.clear(); + exclude.clear(); + ArgMode::Any + } + ArgMode::Any if arg.starts_with('-') => { + panic!("Unknown argument {arg}. Usage:\n{HELP}") + } + ArgMode::Any => { + include.push(arg); + ArgMode::Any + } + ArgMode::Timeout => { + cfg.timeout = Duration::from_secs(arg.parse().unwrap()); + ArgMode::Any + } + ArgMode::MaxFailures => { + if arg == "none" { + cfg.disable_max_failures = true; + } else { + cfg.max_failures = arg.parse().unwrap(); + } + ArgMode::Any + } + ArgMode::FuzzCount => { + if arg == "none" { + cfg.fuzz_count = Some(u64::MAX); + } else { + cfg.fuzz_count = Some(arg.parse().unwrap()); + } + ArgMode::Any + } + ArgMode::Exclude => { + exclude.push(arg); + ArgMode::Any + } + } + } + + (cfg, include, exclude) +} diff --git a/src/tools/test-float-parse/src/traits.rs b/src/tools/test-float-parse/src/traits.rs new file mode 100644 index 00000000000..16484f8fe2c --- /dev/null +++ b/src/tools/test-float-parse/src/traits.rs @@ -0,0 +1,206 @@ +//! Interfaces used throughout this crate. + +use std::str::FromStr; +use std::{fmt, ops}; + +use num::Integer; +use num::bigint::ToBigInt; + +use crate::validate::Constants; + +/// Integer types. +#[allow(dead_code)] // Some functions only used for testing +pub trait Int: + Clone + + Copy + + fmt::Debug + + fmt::Display + + fmt::LowerHex + + ops::Add<Output = Self> + + ops::Sub<Output = Self> + + ops::Shl<u32, Output = Self> + + ops::Shr<u32, Output = Self> + + ops::BitAnd<Output = Self> + + ops::BitOr<Output = Self> + + ops::Not<Output = Self> + + ops::AddAssign + + ops::BitAndAssign + + ops::BitOrAssign + + From<u8> + + TryFrom<i8> + + TryFrom<u32, Error: fmt::Debug> + + TryFrom<u64, Error: fmt::Debug> + + TryFrom<u128, Error: fmt::Debug> + + TryInto<u64, Error: fmt::Debug> + + TryInto<u32, Error: fmt::Debug> + + ToBigInt + + PartialOrd + + Integer + + Send + + 'static +{ + type Signed: Int; + type Bytes: Default + AsMut<[u8]>; + + const BITS: u32; + const ZERO: Self; + const ONE: Self; + const MAX: Self; + + fn to_signed(self) -> Self::Signed; + fn wrapping_neg(self) -> Self; + fn trailing_zeros(self) -> u32; + + fn hex(self) -> String { + format!("{self:x}") + } +} + +macro_rules! impl_int { + ($($uty:ty, $sty:ty);+) => { + $( + impl Int for $uty { + type Signed = $sty; + type Bytes = [u8; Self::BITS as usize / 8]; + const BITS: u32 = Self::BITS; + const ZERO: Self = 0; + const ONE: Self = 1; + const MAX: Self = Self::MAX; + fn to_signed(self) -> Self::Signed { + self.try_into().unwrap() + } + fn wrapping_neg(self) -> Self { + self.wrapping_neg() + } + fn trailing_zeros(self) -> u32 { + self.trailing_zeros() + } + } + + impl Int for $sty { + type Signed = Self; + type Bytes = [u8; Self::BITS as usize / 8]; + const BITS: u32 = Self::BITS; + const ZERO: Self = 0; + const ONE: Self = 1; + const MAX: Self = Self::MAX; + fn to_signed(self) -> Self::Signed { + self + } + fn wrapping_neg(self) -> Self { + self.wrapping_neg() + } + fn trailing_zeros(self) -> u32 { + self.trailing_zeros() + } + } + )+ + } +} + +impl_int!(u16, i16; u32, i32; u64, i64); + +/// Floating point types. +pub trait Float: + Copy + fmt::Debug + fmt::LowerExp + FromStr<Err: fmt::Display> + Sized + Send + 'static +{ + /// Unsigned integer of same width + type Int: Int<Signed = Self::SInt>; + type SInt: Int; + + /// Total bits + const BITS: u32; + + /// (Stored) bits in the mantissa) + const MAN_BITS: u32; + + /// Bits in the exponent + const EXP_BITS: u32 = Self::BITS - Self::MAN_BITS - 1; + + /// A saturated exponent (all ones) + const EXP_SAT: u32 = (1 << Self::EXP_BITS) - 1; + + /// The exponent bias, also its maximum value + const EXP_BIAS: u32 = Self::EXP_SAT >> 1; + + const MAN_MASK: Self::Int; + const SIGN_MASK: Self::Int; + + fn from_bits(i: Self::Int) -> Self; + fn to_bits(self) -> Self::Int; + + /// Rational constants associated with this float type. + fn constants() -> &'static Constants; + + fn is_sign_negative(self) -> bool { + (self.to_bits() & Self::SIGN_MASK) > Self::Int::ZERO + } + + /// Exponent without adjustment for bias. + fn exponent(self) -> u32 { + ((self.to_bits() >> Self::MAN_BITS) & Self::EXP_SAT.try_into().unwrap()).try_into().unwrap() + } + + fn mantissa(self) -> Self::Int { + self.to_bits() & Self::MAN_MASK + } +} + +macro_rules! impl_float { + ($($fty:ty, $ity:ty);+) => { + $( + impl Float for $fty { + type Int = $ity; + type SInt = <Self::Int as Int>::Signed; + const BITS: u32 = <$ity>::BITS; + const MAN_BITS: u32 = Self::MANTISSA_DIGITS - 1; + const MAN_MASK: Self::Int = (Self::Int::ONE << Self::MAN_BITS) - Self::Int::ONE; + const SIGN_MASK: Self::Int = Self::Int::ONE << (Self::BITS-1); + fn from_bits(i: Self::Int) -> Self { Self::from_bits(i) } + fn to_bits(self) -> Self::Int { self.to_bits() } + fn constants() -> &'static Constants { + use std::sync::LazyLock; + static CONSTANTS: LazyLock<Constants> = LazyLock::new(Constants::new::<$fty>); + &CONSTANTS + } + } + )+ + } +} + +impl_float!(f32, u32; f64, u64); + +#[cfg(not(bootstrap))] +#[cfg(target_has_reliable_f16)] +impl_float!(f16, u16); + +/// A test generator. Should provide an iterator that produces unique patterns to parse. +/// +/// The iterator needs to provide a `WriteCtx` (could be anything), which is then used to +/// write the string at a later step. This is done separately so that we can reuse string +/// allocations (which otherwise turn out to be a pretty expensive part of these tests). +pub trait Generator<F: Float>: Iterator<Item = Self::WriteCtx> + Send + 'static { + /// Full display and filtering name + const NAME: &'static str = Self::SHORT_NAME; + + /// Name for display with the progress bar + const SHORT_NAME: &'static str; + + /// The context needed to create a test string. + type WriteCtx: Send; + + /// Number of tests that will be run. + fn total_tests() -> u64; + + /// Constructor for this test generator. + fn new() -> Self; + + /// Create a test string given write context, which was produced as a step from the iterator. + /// + /// `s` will be provided empty. + fn write_string(s: &mut String, ctx: Self::WriteCtx); +} + +/// For tests that use iterator combinators, it is easier to just to box the iterator than trying +/// to specify its type. This is a shorthand for the usual type. +pub type BoxGenIter<This, F> = Box<dyn Iterator<Item = <This as Generator<F>>::WriteCtx> + Send>; diff --git a/src/tools/test-float-parse/src/ui.rs b/src/tools/test-float-parse/src/ui.rs new file mode 100644 index 00000000000..73473eef0bf --- /dev/null +++ b/src/tools/test-float-parse/src/ui.rs @@ -0,0 +1,168 @@ +//! Progress bars and such. + +use std::any::type_name; +use std::fmt; +use std::io::{self, Write}; +use std::process::ExitCode; +use std::time::Duration; + +use indicatif::{ProgressBar, ProgressStyle}; + +use crate::{Completed, Config, EarlyExit, FinishedAll, TestInfo}; + +/// Templates for progress bars. +const PB_TEMPLATE: &str = "[{elapsed:3} {percent:3}%] {bar:20.cyan/blue} NAME \ + {human_pos:>8}/{human_len:8} {msg} f {per_sec:14} eta {eta:8}"; +const PB_TEMPLATE_FINAL: &str = "[{elapsed:3} {percent:3}%] {bar:20.cyan/blue} NAME \ + {human_pos:>8}/{human_len:8} {msg:.COLOR} {per_sec:18} {elapsed_precise}"; + +/// Thin abstraction over our usage of a `ProgressBar`. +#[derive(Debug)] +pub struct Progress { + pb: ProgressBar, + make_final_style: NoDebug<Box<dyn Fn(&'static str) -> ProgressStyle + Sync>>, +} + +impl Progress { + /// Create a new progress bar within a multiprogress bar. + pub fn new(test: &TestInfo, all_bars: &mut Vec<ProgressBar>) -> Self { + let initial_template = PB_TEMPLATE.replace("NAME", &test.short_name_padded); + let final_template = PB_TEMPLATE_FINAL.replace("NAME", &test.short_name_padded); + let initial_style = + ProgressStyle::with_template(&initial_template).unwrap().progress_chars("##-"); + let make_final_style = move |color| { + ProgressStyle::with_template(&final_template.replace("COLOR", color)) + .unwrap() + .progress_chars("##-") + }; + + let pb = ProgressBar::new(test.total_tests); + pb.set_style(initial_style); + pb.set_length(test.total_tests); + pb.set_message("0"); + all_bars.push(pb.clone()); + + Progress { pb, make_final_style: NoDebug(Box::new(make_final_style)) } + } + + /// Completed a out of b tests. + pub fn update(&self, completed: u64, failures: u64) { + // Infrequently update the progress bar. + if completed % 5_000 == 0 || failures > 0 { + self.pb.set_position(completed); + } + + if failures > 0 { + self.pb.set_message(format! {"{failures}"}); + } + } + + /// Finalize the progress bar. + pub fn complete(&self, c: &Completed, real_total: u64) { + let f = c.failures; + let (color, msg, finish_fn): (&str, String, fn(&ProgressBar)) = match &c.result { + Ok(FinishedAll) if f > 0 => { + ("red", format!("{f} f (completed with errors)",), ProgressBar::finish) + } + Ok(FinishedAll) => { + ("green", format!("{f} f (completed successfully)",), ProgressBar::finish) + } + Err(EarlyExit::Timeout) => ("red", format!("{f} f (timed out)"), ProgressBar::abandon), + Err(EarlyExit::MaxFailures) => { + ("red", format!("{f} f (failure limit)"), ProgressBar::abandon) + } + }; + + self.pb.set_position(real_total); + self.pb.set_style(self.make_final_style.0(color)); + self.pb.set_message(msg); + finish_fn(&self.pb); + } + + /// Print a message to stdout above the current progress bar. + pub fn println(&self, msg: &str) { + self.pb.suspend(|| println!("{msg}")); + } +} + +/// Print final messages after all tests are complete. +pub fn finish_all(tests: &[TestInfo], total_elapsed: Duration, cfg: &Config) -> ExitCode { + println!("\n\nResults:"); + + let mut failed_generators = 0; + let mut stopped_generators = 0; + + for t in tests { + let Completed { executed, failures, elapsed, warning, result } = t.completed.get().unwrap(); + + let stat = if result.is_err() { + stopped_generators += 1; + "STOPPED" + } else if *failures > 0 { + failed_generators += 1; + "FAILURE" + } else { + "SUCCESS" + }; + + println!( + " {stat} for generator '{name}'. {passed}/{executed} passed in {elapsed:?}", + name = t.name, + passed = executed - failures, + ); + + if let Some(warning) = warning { + println!(" warning: {warning}"); + } + + match result { + Ok(FinishedAll) => (), + Err(EarlyExit::Timeout) => { + println!(" exited early; exceded {:?} timeout", cfg.timeout) + } + Err(EarlyExit::MaxFailures) => { + println!(" exited early; exceeded {:?} max failures", cfg.max_failures) + } + } + } + + println!( + "{passed}/{} tests succeeded in {total_elapsed:?} ({passed} passed, {} failed, {} stopped)", + tests.len(), + failed_generators, + stopped_generators, + passed = tests.len() - failed_generators - stopped_generators, + ); + + if failed_generators > 0 || stopped_generators > 0 { + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } +} + +/// indicatif likes to eat panic messages. This workaround isn't ideal, but it improves things. +/// <https://github.com/console-rs/indicatif/issues/121>. +pub fn set_panic_hook(drop_bars: &[ProgressBar]) { + let hook = std::panic::take_hook(); + let drop_bars = drop_bars.to_owned(); + std::panic::set_hook(Box::new(move |info| { + for bar in &drop_bars { + bar.abandon(); + println!(); + io::stdout().flush().unwrap(); + io::stderr().flush().unwrap(); + } + hook(info); + })); +} + +/// Allow non-Debug items in a `derive(Debug)` struct. +#[derive(Clone)] +struct NoDebug<T>(T); + +impl<T> fmt::Debug for NoDebug<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(type_name::<Self>()) + } +} diff --git a/src/tools/test-float-parse/src/validate.rs b/src/tools/test-float-parse/src/validate.rs new file mode 100644 index 00000000000..40dda274e3b --- /dev/null +++ b/src/tools/test-float-parse/src/validate.rs @@ -0,0 +1,394 @@ +//! Everything related to verifying that parsed outputs are correct. + +use std::any::{Any, type_name}; +use std::collections::BTreeMap; +use std::ops::RangeInclusive; +use std::str::FromStr; +use std::sync::LazyLock; + +use num::bigint::ToBigInt; +use num::{BigInt, BigRational, FromPrimitive, Signed, ToPrimitive}; + +use crate::{CheckFailure, Float, Int}; + +/// Powers of two that we store for constants. Account for binary128 which has a 15-bit exponent. +const POWERS_OF_TWO_RANGE: RangeInclusive<i32> = (-(2 << 15))..=(2 << 15); + +/// Powers of ten that we cache. Account for binary128, which can fit +4932/-4931 +const POWERS_OF_TEN_RANGE: RangeInclusive<i32> = -5_000..=5_000; + +/// Cached powers of 10 so we can look them up rather than recreating. +static POWERS_OF_TEN: LazyLock<BTreeMap<i32, BigRational>> = LazyLock::new(|| { + POWERS_OF_TEN_RANGE.map(|exp| (exp, BigRational::from_u32(10).unwrap().pow(exp))).collect() +}); + +/// Rational property-related constants for a specific float type. +#[allow(dead_code)] +#[derive(Debug)] +pub struct Constants { + /// The minimum positive value (a subnormal). + min_subnormal: BigRational, + /// The maximum possible finite value. + max: BigRational, + /// Cutoff between rounding to zero and rounding to the minimum value (min subnormal). + zero_cutoff: BigRational, + /// Cutoff between rounding to the max value and rounding to infinity. + inf_cutoff: BigRational, + /// Opposite of `inf_cutoff` + neg_inf_cutoff: BigRational, + /// The powers of two for all relevant integers. + powers_of_two: BTreeMap<i32, BigRational>, + /// Half of each power of two. ULP = "unit in last position". + /// + /// This is a mapping from integers to half the precision available at that exponent. In other + /// words, `0.5 * 2^n` = `2^(n-1)`, which is half the distance between `m * 2^n` and + /// `(m + 1) * 2^n`, m ∈ ℤ. + /// + /// So, this is the maximum error from a real number to its floating point representation, + /// assuming the float type can represent the exponent. + half_ulp: BTreeMap<i32, BigRational>, + /// Handy to have around so we don't need to reallocate for it + two: BigInt, +} + +impl Constants { + pub fn new<F: Float>() -> Self { + let two_int = &BigInt::from_u32(2).unwrap(); + let two = &BigRational::from_integer(2.into()); + + // The minimum subnormal (aka minimum positive) value. Most negative power of two is the + // minimum exponent (bias - 1) plus the extra from shifting within the mantissa bits. + let min_subnormal = two.pow(-(F::EXP_BIAS + F::MAN_BITS - 1).to_signed()); + + // The maximum value is the maximum exponent with a fully saturated mantissa. This + // is easiest to calculate by evaluating what the next value up would be if representable + // (zeroed mantissa, exponent increments by one, i.e. `2^(bias + 1)`), and subtracting + // a single LSB (`2 ^ (-mantissa_bits)`). + let max = (two - two.pow(-F::MAN_BITS.to_signed())) * (two.pow(F::EXP_BIAS.to_signed())); + let zero_cutoff = &min_subnormal / two_int; + + let inf_cutoff = &max + two_int.pow(F::EXP_BIAS - F::MAN_BITS - 1); + let neg_inf_cutoff = -&inf_cutoff; + + let powers_of_two: BTreeMap<i32, _> = + (POWERS_OF_TWO_RANGE).map(|n| (n, two.pow(n))).collect(); + let mut half_ulp = powers_of_two.clone(); + half_ulp.iter_mut().for_each(|(_k, v)| *v = &*v / two_int); + + Self { + min_subnormal, + max, + zero_cutoff, + inf_cutoff, + neg_inf_cutoff, + powers_of_two, + half_ulp, + two: two_int.clone(), + } + } +} + +/// Validate that a string parses correctly +pub fn validate<F: Float>(input: &str) -> Result<(), CheckError> { + // Catch panics in case debug assertions within `std` fail. + let parsed = std::panic::catch_unwind(|| { + input.parse::<F>().map_err(|e| CheckError { + fail: CheckFailure::ParsingFailed(e.to_string().into()), + input: input.into(), + float_res: "none".into(), + }) + }) + .map_err(|e| convert_panic_error(&e, input))??; + + // Parsed float, decoded into significand and exponent + let decoded = decode(parsed); + + // Float parsed separately into a rational + let rational = Rational::parse(input); + + // Verify that the values match + decoded.check(rational, input) +} + +/// Turn panics into concrete error types. +fn convert_panic_error(e: &dyn Any, input: &str) -> CheckError { + let msg = e + .downcast_ref::<String>() + .map(|s| s.as_str()) + .or_else(|| e.downcast_ref::<&str>().copied()) + .unwrap_or("(no contents)"); + + CheckError { + fail: CheckFailure::Panic(msg.into()), + input: input.into(), + float_res: "none".into(), + } +} + +/// The result of parsing a string to a float type. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FloatRes<F: Float> { + Inf, + NegInf, + Zero, + Nan, + /// A real number with significand and exponent. Value is `sig * 2 ^ exp`. + Real { + sig: F::SInt, + exp: i32, + }, +} + +#[derive(Clone, Debug)] +pub struct CheckError { + pub fail: CheckFailure, + /// String for which parsing was attempted. + pub input: Box<str>, + /// The parsed & decomposed `FloatRes`, already stringified so we don't need generics here. + pub float_res: Box<str>, +} + +impl<F: Float> FloatRes<F> { + /// Given a known exact rational, check that this representation is accurate within the + /// limits of the float representation. If not, construct a failure `Update` to send. + fn check(self, expected: Rational, input: &str) -> Result<(), CheckError> { + let consts = F::constants(); + // let bool_helper = |cond: bool, err| cond.then_some(()).ok_or(err); + + let res = match (expected, self) { + // Easy correct cases + (Rational::Inf, FloatRes::Inf) + | (Rational::NegInf, FloatRes::NegInf) + | (Rational::Nan, FloatRes::Nan) => Ok(()), + + // Easy incorrect cases + ( + Rational::Inf, + FloatRes::NegInf | FloatRes::Zero | FloatRes::Nan | FloatRes::Real { .. }, + ) => Err(CheckFailure::ExpectedInf), + ( + Rational::NegInf, + FloatRes::Inf | FloatRes::Zero | FloatRes::Nan | FloatRes::Real { .. }, + ) => Err(CheckFailure::ExpectedNegInf), + ( + Rational::Nan, + FloatRes::Inf | FloatRes::NegInf | FloatRes::Zero | FloatRes::Real { .. }, + ) => Err(CheckFailure::ExpectedNan), + (Rational::Finite(_), FloatRes::Nan) => Err(CheckFailure::UnexpectedNan), + + // Cases near limits + (Rational::Finite(r), FloatRes::Zero) => { + if r <= consts.zero_cutoff { + Ok(()) + } else { + Err(CheckFailure::UnexpectedZero) + } + } + (Rational::Finite(r), FloatRes::Inf) => { + if r >= consts.inf_cutoff { + Ok(()) + } else { + Err(CheckFailure::UnexpectedInf) + } + } + (Rational::Finite(r), FloatRes::NegInf) => { + if r <= consts.neg_inf_cutoff { + Ok(()) + } else { + Err(CheckFailure::UnexpectedNegInf) + } + } + + // Actual numbers + (Rational::Finite(r), FloatRes::Real { sig, exp }) => Self::validate_real(r, sig, exp), + }; + + res.map_err(|fail| CheckError { + fail, + input: input.into(), + float_res: format!("{self:?}").into(), + }) + } + + /// Check that `sig * 2^exp` is the same as `rational`, within the float's error margin. + fn validate_real(rational: BigRational, sig: F::SInt, exp: i32) -> Result<(), CheckFailure> { + let consts = F::constants(); + + // `2^exp`. Use cached powers of two to be faster. + let two_exp = consts + .powers_of_two + .get(&exp) + .unwrap_or_else(|| panic!("missing exponent {exp} for {}", type_name::<F>())); + + // Rational from the parsed value, `sig * 2^exp` + let parsed_rational = two_exp * sig.to_bigint().unwrap(); + let error = (parsed_rational - &rational).abs(); + + // Determine acceptable error at this exponent, which is halfway between this value + // (`sig * 2^exp`) and the next value up (`(sig+1) * 2^exp`). + let half_ulp = consts.half_ulp.get(&exp).unwrap(); + + // If we are within one error value (but not equal) then we rounded correctly. + if &error < half_ulp { + return Ok(()); + } + + // For values where we are exactly between two representable values, meaning that the error + // is exactly one half of the precision at that exponent, we need to round to an even + // binary value (i.e. mantissa ends in 0). + let incorrect_midpoint_rounding = if &error == half_ulp { + if sig & F::SInt::ONE == F::SInt::ZERO { + return Ok(()); + } + + // We rounded to odd rather than even; failing based on midpoint rounding. + true + } else { + // We are out of spec for some other reason. + false + }; + + let one_ulp = consts.half_ulp.get(&(exp + 1)).unwrap(); + assert_eq!(one_ulp, &(half_ulp * &consts.two), "ULP values are incorrect"); + + let relative_error = error / one_ulp; + + Err(CheckFailure::InvalidReal { + error_float: relative_error.to_f64(), + error_str: relative_error.to_string().into(), + incorrect_midpoint_rounding, + }) + } + + /// Remove trailing zeros in the significand and adjust the exponent + #[cfg(test)] + fn normalize(self) -> Self { + use std::cmp::min; + + match self { + Self::Real { sig, exp } => { + // If there are trailing zeroes, remove them and increment the exponent instead + let shift = min(sig.trailing_zeros(), exp.wrapping_neg().try_into().unwrap()); + Self::Real { sig: sig >> shift, exp: exp + i32::try_from(shift).unwrap() } + } + _ => self, + } + } +} + +/// Decompose a float into its integral components. This includes the implicit bit. +/// +/// If `allow_nan` is `false`, panic if `NaN` values are reached. +fn decode<F: Float>(f: F) -> FloatRes<F> { + let ione = F::SInt::ONE; + let izero = F::SInt::ZERO; + + let mut exponent_biased = f.exponent(); + let mut mantissa = f.mantissa().to_signed(); + + if exponent_biased == 0 { + if mantissa == izero { + return FloatRes::Zero; + } + + exponent_biased += 1; + } else if exponent_biased == F::EXP_SAT { + if mantissa != izero { + return FloatRes::Nan; + } + + if f.is_sign_negative() { + return FloatRes::NegInf; + } + + return FloatRes::Inf; + } else { + // Set implicit bit + mantissa |= ione << F::MAN_BITS; + } + + let mut exponent = i32::try_from(exponent_biased).unwrap(); + + // Adjust for bias and the rnage of the mantissa + exponent -= i32::try_from(F::EXP_BIAS + F::MAN_BITS).unwrap(); + + if f.is_sign_negative() { + mantissa = mantissa.wrapping_neg(); + } + + FloatRes::Real { sig: mantissa, exp: exponent } +} + +/// A rational or its unrepresentable values. +#[derive(Clone, Debug, PartialEq)] +enum Rational { + Inf, + NegInf, + Nan, + Finite(BigRational), +} + +impl Rational { + /// Turn a string into a rational. `None` if `NaN`. + fn parse(s: &str) -> Rational { + let mut s = s; // lifetime rules + + if s.strip_prefix('+').unwrap_or(s).eq_ignore_ascii_case("nan") + || s.eq_ignore_ascii_case("-nan") + { + return Rational::Nan; + } + + if s.strip_prefix('+').unwrap_or(s).eq_ignore_ascii_case("inf") { + return Rational::Inf; + } + + if s.eq_ignore_ascii_case("-inf") { + return Rational::NegInf; + } + + // Fast path; no decimals or exponents ot parse + if s.bytes().all(|b| b.is_ascii_digit() || b == b'-') { + return Rational::Finite(BigRational::from_str(s).unwrap()); + } + + let mut ten_exp: i32 = 0; + + // Remove and handle e.g. `e-4`, `e+10`, `e5` suffixes + if let Some(pos) = s.bytes().position(|b| b == b'e' || b == b'E') { + let (dec, exp) = s.split_at(pos); + s = dec; + ten_exp = exp[1..].parse().unwrap(); + } + + // Remove the decimal and instead change our exponent + // E.g. "12.3456" becomes "123456 * 10^-4" + let mut s_owned; + if let Some(pos) = s.bytes().position(|b| b == b'.') { + ten_exp = ten_exp.checked_sub((s.len() - pos - 1).try_into().unwrap()).unwrap(); + s_owned = s.to_owned(); + s_owned.remove(pos); + s = &s_owned; + } + + // let pow = BigRational::from_u32(10).unwrap().pow(ten_exp); + let pow = + POWERS_OF_TEN.get(&ten_exp).unwrap_or_else(|| panic!("missing power of ten {ten_exp}")); + let r = pow + * BigInt::from_str(s) + .unwrap_or_else(|e| panic!("`BigInt::from_str(\"{s}\")` failed with {e}")); + Rational::Finite(r) + } + + #[cfg(test)] + fn expect_finite(self) -> BigRational { + let Self::Finite(r) = self else { + panic!("got non rational: {self:?}"); + }; + + r + } +} + +#[cfg(test)] +mod tests; diff --git a/src/tools/test-float-parse/src/validate/tests.rs b/src/tools/test-float-parse/src/validate/tests.rs new file mode 100644 index 00000000000..ab0e7d8a7ba --- /dev/null +++ b/src/tools/test-float-parse/src/validate/tests.rs @@ -0,0 +1,149 @@ +use num::ToPrimitive; + +use super::*; + +#[test] +fn test_parse_rational() { + assert_eq!(Rational::parse("1234").expect_finite(), BigRational::new(1234.into(), 1.into())); + assert_eq!( + Rational::parse("-1234").expect_finite(), + BigRational::new((-1234).into(), 1.into()) + ); + assert_eq!(Rational::parse("1e+6").expect_finite(), BigRational::new(1000000.into(), 1.into())); + assert_eq!(Rational::parse("1e-6").expect_finite(), BigRational::new(1.into(), 1000000.into())); + assert_eq!( + Rational::parse("10.4e6").expect_finite(), + BigRational::new(10400000.into(), 1.into()) + ); + assert_eq!( + Rational::parse("10.4e+6").expect_finite(), + BigRational::new(10400000.into(), 1.into()) + ); + assert_eq!( + Rational::parse("10.4e-6").expect_finite(), + BigRational::new(13.into(), 1250000.into()) + ); + assert_eq!( + Rational::parse("10.4243566462342456234124").expect_finite(), + BigRational::new(104243566462342456234124_i128.into(), 10000000000000000000000_i128.into()) + ); + assert_eq!(Rational::parse("inf"), Rational::Inf); + assert_eq!(Rational::parse("+inf"), Rational::Inf); + assert_eq!(Rational::parse("-inf"), Rational::NegInf); + assert_eq!(Rational::parse("NaN"), Rational::Nan); +} + +#[test] +fn test_decode() { + assert_eq!(decode(0f32), FloatRes::Zero); + assert_eq!(decode(f32::INFINITY), FloatRes::Inf); + assert_eq!(decode(f32::NEG_INFINITY), FloatRes::NegInf); + assert_eq!(decode(1.0f32).normalize(), FloatRes::Real { sig: 1, exp: 0 }); + assert_eq!(decode(-1.0f32).normalize(), FloatRes::Real { sig: -1, exp: 0 }); + assert_eq!(decode(100.0f32).normalize(), FloatRes::Real { sig: 100, exp: 0 }); + assert_eq!(decode(100.5f32).normalize(), FloatRes::Real { sig: 201, exp: -1 }); + assert_eq!(decode(-4.004f32).normalize(), FloatRes::Real { sig: -8396997, exp: -21 }); + assert_eq!(decode(0.0004f32).normalize(), FloatRes::Real { sig: 13743895, exp: -35 }); + assert_eq!(decode(f32::from_bits(0x1)).normalize(), FloatRes::Real { sig: 1, exp: -149 }); +} + +#[test] +fn test_validate() { + validate::<f32>("0").unwrap(); + validate::<f32>("-0").unwrap(); + validate::<f32>("1").unwrap(); + validate::<f32>("-1").unwrap(); + validate::<f32>("1.1").unwrap(); + validate::<f32>("-1.1").unwrap(); + validate::<f32>("1e10").unwrap(); + validate::<f32>("1e1000").unwrap(); + validate::<f32>("-1e1000").unwrap(); + validate::<f32>("1e-1000").unwrap(); + validate::<f32>("-1e-1000").unwrap(); +} + +#[test] +fn test_validate_real() { + // Most of the arbitrary values come from checking against <http://weitz.de/ieee/>. + let r = &BigRational::from_float(10.0).unwrap(); + FloatRes::<f32>::validate_real(r.clone(), 10, 0).unwrap(); + FloatRes::<f32>::validate_real(r.clone(), 10, -1).unwrap_err(); + FloatRes::<f32>::validate_real(r.clone(), 10, 1).unwrap_err(); + + let r = &BigRational::from_float(0.25).unwrap(); + FloatRes::<f32>::validate_real(r.clone(), 1, -2).unwrap(); + FloatRes::<f32>::validate_real(r.clone(), 2, -2).unwrap_err(); + + let r = &BigRational::from_float(1234.5678).unwrap(); + FloatRes::<f32>::validate_real(r.clone(), 0b100110100101001000101011, -13).unwrap(); + FloatRes::<f32>::validate_real(r.clone(), 0b100110100101001000101010, -13).unwrap_err(); + FloatRes::<f32>::validate_real(r.clone(), 0b100110100101001000101100, -13).unwrap_err(); + + let r = &BigRational::from_float(-1234.5678).unwrap(); + FloatRes::<f32>::validate_real(r.clone(), -0b100110100101001000101011, -13).unwrap(); + FloatRes::<f32>::validate_real(r.clone(), -0b100110100101001000101010, -13).unwrap_err(); + FloatRes::<f32>::validate_real(r.clone(), -0b100110100101001000101100, -13).unwrap_err(); +} + +#[test] +#[allow(unused)] +fn test_validate_real_rounding() { + // Check that we catch when values don't round to even. + + // For f32, the cutoff between 1.0 and the next value up (1.0000001) is + // 1.000000059604644775390625. Anything below it should round down, anything above it should + // round up, and the value itself should round _down_ because `1.0` has an even significand but + // 1.0000001 is odd. + let v1_low_down = Rational::parse("1.00000005960464477539062499999").expect_finite(); + let v1_mid_down = Rational::parse("1.000000059604644775390625").expect_finite(); + let v1_high_up = Rational::parse("1.00000005960464477539062500001").expect_finite(); + + let exp = -(f32::MAN_BITS as i32); + let v1_down_sig = 1 << f32::MAN_BITS; + let v1_up_sig = (1 << f32::MAN_BITS) | 0b1; + + FloatRes::<f32>::validate_real(v1_low_down.clone(), v1_down_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(v1_mid_down.clone(), v1_down_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(v1_high_up.clone(), v1_up_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(-v1_low_down.clone(), -v1_down_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(-v1_mid_down.clone(), -v1_down_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(-v1_high_up.clone(), -v1_up_sig, exp).unwrap(); + + // 1.000000178813934326171875 is between 1.0000001 and the next value up, 1.0000002. The middle + // value here should round _up_ since 1.0000002 has an even mantissa. + let v2_low_down = Rational::parse("1.00000017881393432617187499999").expect_finite(); + let v2_mid_up = Rational::parse("1.000000178813934326171875").expect_finite(); + let v2_high_up = Rational::parse("1.00000017881393432617187500001").expect_finite(); + + let v2_down_sig = v1_up_sig; + let v2_up_sig = (1 << f32::MAN_BITS) | 0b10; + + FloatRes::<f32>::validate_real(v2_low_down.clone(), v2_down_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(v2_mid_up.clone(), v2_up_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(v2_high_up.clone(), v2_up_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(-v2_low_down.clone(), -v2_down_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(-v2_mid_up.clone(), -v2_up_sig, exp).unwrap(); + FloatRes::<f32>::validate_real(-v2_high_up.clone(), -v2_up_sig, exp).unwrap(); + + // Rounding the wrong direction should error + for res in [ + FloatRes::<f32>::validate_real(v1_mid_down.clone(), v1_up_sig, exp), + FloatRes::<f32>::validate_real(v2_mid_up.clone(), v2_down_sig, exp), + FloatRes::<f32>::validate_real(-v1_mid_down.clone(), -v1_up_sig, exp), + FloatRes::<f32>::validate_real(-v2_mid_up.clone(), -v2_down_sig, exp), + ] { + let e = res.unwrap_err(); + let CheckFailure::InvalidReal { incorrect_midpoint_rounding: true, .. } = e else { + panic!("{e:?}"); + }; + } +} + +/// Just a quick check that the constants are what we expect. +#[test] +fn check_constants() { + assert_eq!(f32::constants().max.to_f32().unwrap(), f32::MAX); + assert_eq!(f32::constants().min_subnormal.to_f32().unwrap(), f32::from_bits(0x1)); + assert_eq!(f64::constants().max.to_f64().unwrap(), f64::MAX); + assert_eq!(f64::constants().min_subnormal.to_f64().unwrap(), f64::from_bits(0x1)); +} diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 9f333cc43cf..fdca7a7a40e 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -74,13 +74,13 @@ pub(crate) const WORKSPACES: &[(&str, ExceptionList, Option<(&[&str], &[&str])>, ("compiler/rustc_codegen_gcc", EXCEPTIONS_GCC, None, &[]), ("src/bootstrap", EXCEPTIONS_BOOTSTRAP, None, &[]), ("src/ci/docker/host-x86_64/test-various/uefi_qemu_test", EXCEPTIONS_UEFI_QEMU_TEST, None, &[]), - ("src/etc/test-float-parse", EXCEPTIONS, None, &[]), ("src/tools/cargo", EXCEPTIONS_CARGO, None, &["src/tools/cargo"]), //("src/tools/miri/test-cargo-miri", &[], None), // FIXME uncomment once all deps are vendored //("src/tools/miri/test_dependencies", &[], None), // FIXME uncomment once all deps are vendored ("src/tools/rust-analyzer", EXCEPTIONS_RUST_ANALYZER, None, &[]), ("src/tools/rustbook", EXCEPTIONS_RUSTBOOK, None, &["src/doc/book", "src/doc/reference"]), ("src/tools/rustc-perf", EXCEPTIONS_RUSTC_PERF, None, &["src/tools/rustc-perf"]), + ("src/tools/test-float-parse", EXCEPTIONS, None, &[]), // tidy-alphabetical-end ]; diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs index c708ff7150c..9b915e0f737 100644 --- a/src/tools/tidy/src/pal.rs +++ b/src/tools/tidy/src/pal.rs @@ -36,6 +36,7 @@ use crate::walk::{filter_dirs, walk}; // Paths that may contain platform-specific code. const EXCEPTION_PATHS: &[&str] = &[ + "library/compiler-builtins", "library/windows_targets", "library/panic_abort", "library/panic_unwind", diff --git a/src/tools/tidy/src/walk.rs b/src/tools/tidy/src/walk.rs index 08ee5c16c12..c2c9eb8d250 100644 --- a/src/tools/tidy/src/walk.rs +++ b/src/tools/tidy/src/walk.rs @@ -14,6 +14,7 @@ pub fn filter_dirs(path: &Path) -> bool { "compiler/rustc_codegen_gcc", "src/llvm-project", "library/backtrace", + "library/compiler-builtins", "library/portable-simd", "library/stdarch", "src/tools/cargo", |
