1
Fork 0

Auto merge of #138058 - jieyouxu:rollup-skdt0oz, r=jieyouxu

Rollup of 20 pull requests

Successful merges:

 - #134063 (dec2flt: Clean up float parsing modules)
 - #136581 (Retire the legacy `Makefile`-based `run-make` test infra)
 - #136662 (Count char width at most once in `Formatter::pad`)
 - #136764 (Make `ptr_cast_add_auto_to_object` lint into hard error)
 - #136798 (Added documentation for flushing per #74348)
 - #136865 (Perform deeper compiletest path normalization for `$TEST_BUILD_DIR` to account for compare-mode/debugger cases, and normalize long type file filename hashes)
 - #136975 (Look for `python3` first on MacOS, not `py`)
 - #136977 (Upload Datadog metrics with citool)
 - #137240 (Slightly reformat `std::fs::remove_dir_all` error docs)
 - #137298 (Check signature WF when lowering MIR body)
 - #137463 ([illumos] attempt to use posix_spawn to spawn processes)
 - #137477 (uefi: Add Service Binding Protocol abstraction)
 - #137569 (Stabilize `string_extend_from_within`)
 - #137633 (Only use implied bounds hack if bevy, and use deeply normalize in implied bounds hack)
 - #137679 (Various coretests improvements)
 - #137723 (Make `rust.description` more general-purpose and pass `CFG_VER_DESCRIPTION`)
 - #137728 (Remove unsizing coercions for tuples)
 - #137731 (Resume one waiter at once in deadlock handler)
 - #137875 (mir_build: Integrate "simplification" steps into match-pair-tree creation)
 - #138028 (compiler: add `ExternAbi::is_rustic_abi`)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2025-03-05 15:09:49 +00:00
commit 07b5eeebc9
254 changed files with 2856 additions and 8345 deletions

View file

@ -58,8 +58,8 @@ jobs:
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
cd src/ci/citool
cargo test
cargo run calculate-job-matrix >> $GITHUB_OUTPUT
CARGO_INCREMENTAL=0 cargo test
CARGO_INCREMENTAL=0 cargo run calculate-job-matrix >> $GITHUB_OUTPUT
id: jobs
job:
name: ${{ matrix.full_name }}
@ -183,11 +183,11 @@ jobs:
run: src/ci/scripts/dump-environment.sh
# Pre-build citool before the following step uninstalls rustup
# Build is into the build directory, to avoid modifying sources
# Build it into the build directory, to avoid modifying sources
- name: build citool
run: |
cd src/ci/citool
CARGO_TARGET_DIR=../../../build/citool cargo build
CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=../../../build/citool cargo build
- name: run the build
# Redirect stderr to stdout to avoid reordering the two streams in the GHA logs.
@ -238,13 +238,9 @@ jobs:
- name: upload job metrics to DataDog
if: needs.calculate_matrix.outputs.run_type != 'pr'
env:
DATADOG_SITE: datadoghq.com
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
DD_GITHUB_JOB_NAME: ${{ matrix.full_name }}
run: |
cd src/ci
npm ci
python3 scripts/upload-build-metrics.py ../../build/cpu-usage.csv
run: ./build/citool/debug/citool upload-build-metrics build/cpu-usage.csv
# This job isused to tell bors the final status of the build, as there is no practical way to detect
# when a workflow is successful listening to webhooks only in our current bors implementation (homu).

View file

@ -191,6 +191,17 @@ impl StableOrd for ExternAbi {
}
impl ExternAbi {
/// An ABI "like Rust"
///
/// These ABIs are fully controlled by the Rust compiler, which means they
/// - support unwinding with `-Cpanic=unwind`, unlike `extern "C"`
/// - often diverge from the C ABI
/// - are subject to change between compiler versions
pub fn is_rustic_abi(self) -> bool {
use ExternAbi::*;
matches!(self, Rust | RustCall | RustIntrinsic | RustCold)
}
pub fn supports_varargs(self) -> bool {
// * C and Cdecl obviously support varargs.
// * C can be based on Aapcs, SysV64 or Win64, so they must support varargs.

View file

@ -0,0 +1,41 @@
An auto trait cannot be added to the bounds of a `dyn Trait` type via
a pointer cast.
Erroneous code example:
```rust,edition2021,compile_fail,E0804
let ptr: *const dyn core::any::Any = &();
_ = ptr as *const (dyn core::any::Any + Send);
```
Adding an auto trait can make the vtable invalid, potentially causing
UB in safe code afterwards. For example:
```rust,edition2021,no_run
use core::{mem::transmute, ptr::NonNull};
trait Trait {
fn f(&self)
where
Self: Send;
}
impl Trait for NonNull<()> {
fn f(&self) {
unreachable!()
}
}
fn main() {
let unsend: &dyn Trait = &NonNull::dangling();
let bad: &(dyn Trait + Send) = unsafe { transmute(unsend) };
// This crashes, since the vtable for `NonNull as dyn Trait` does
// not have an entry for `Trait::f`.
bad.f();
}
```
To fix this error, you can use `transmute` rather than pointer casts,
but you must ensure that the vtable is valid for the pointer's type
before calling a method on the trait object or allowing other code to
do so.

View file

@ -547,6 +547,7 @@ E0800: 0800,
E0801: 0801,
E0802: 0802,
E0803: 0803,
E0804: 0804,
);
)
}

View file

@ -244,6 +244,8 @@ declare_features! (
/// Allows unnamed fields of struct and union type
(removed, unnamed_fields, "1.83.0", Some(49804), Some("feature needs redesign")),
(removed, unsafe_no_drop_flag, "1.0.0", None, None),
(removed, unsized_tuple_coercion, "CURRENT_RUSTC_VERSION", Some(42877),
Some("The feature restricts possible layouts for tuples, and this restriction is not worth it.")),
/// Allows `union` fields that don't implement `Copy` as long as they don't have any drop glue.
(removed, untagged_unions, "1.13.0", Some(55149),
Some("unions with `Copy` and `ManuallyDrop` fields are stable; there is no intent to stabilize more")),

View file

@ -659,8 +659,6 @@ declare_features! (
(internal, unsized_fn_params, "1.49.0", Some(48055)),
/// Allows unsized rvalues at arguments and parameters.
(incomplete, unsized_locals, "1.30.0", Some(48055)),
/// Allows unsized tuple coercion.
(unstable, unsized_tuple_coercion, "1.20.0", Some(42877)),
/// Allows using the `#[used(linker)]` (or `#[used(compiler)]`) attribute.
(unstable, used_with_arg, "1.60.0", Some(93798)),
/// Allows use of attributes in `where` clauses.

View file

@ -745,14 +745,10 @@ fn check_static_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) {
pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) {
match tcx.def_kind(def_id) {
DefKind::Static { .. } => {
tcx.ensure_ok().typeck(def_id);
maybe_check_static_with_link_section(tcx, def_id);
check_static_inhabited(tcx, def_id);
check_static_linkage(tcx, def_id);
}
DefKind::Const => {
tcx.ensure_ok().typeck(def_id);
}
DefKind::Const => {}
DefKind::Enum => {
check_enum(tcx, def_id);
}
@ -766,7 +762,6 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) {
ExternAbi::Rust,
)
}
// Everything else is checked entirely within check_item_body
}
DefKind::Impl { of_trait } => {
if of_trait && let Some(impl_trait_header) = tcx.impl_trait_header(def_id) {

View file

@ -145,7 +145,7 @@ pub fn forbid_intrinsic_abi(tcx: TyCtxt<'_>, sp: Span, abi: ExternAbi) {
}
}
fn maybe_check_static_with_link_section(tcx: TyCtxt<'_>, id: LocalDefId) {
pub(super) fn maybe_check_static_with_link_section(tcx: TyCtxt<'_>, id: LocalDefId) {
// Only restricted on wasm target for now
if !tcx.sess.target.is_like_wasm {
return;

View file

@ -126,13 +126,14 @@ where
let infcx_compat = infcx.fork();
// We specifically want to call the non-compat version of `implied_bounds_tys`; we do this always.
// We specifically want to *disable* the implied bounds hack, first,
// so we can detect when failures are due to bevy's implied bounds.
let outlives_env = OutlivesEnvironment::new_with_implied_bounds_compat(
&infcx,
body_def_id,
param_env,
assumed_wf_types.iter().copied(),
false,
true,
);
lint_redundant_lifetimes(tcx, body_def_id, &outlives_env);
@ -142,53 +143,22 @@ where
return Ok(());
}
let is_bevy = assumed_wf_types.visit_with(&mut ContainsBevyParamSet { tcx }).is_break();
// If we have set `no_implied_bounds_compat`, then do not attempt compatibility.
// We could also just always enter if `is_bevy`, and call `implied_bounds_tys`,
// but that does result in slightly more work when this option is set and
// just obscures what we mean here anyways. Let's just be explicit.
if is_bevy && !infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat {
let outlives_env = OutlivesEnvironment::new_with_implied_bounds_compat(
&infcx,
body_def_id,
param_env,
assumed_wf_types,
true,
);
let errors_compat = infcx_compat.resolve_regions_with_outlives_env(&outlives_env);
if errors_compat.is_empty() {
Ok(())
} else {
Err(infcx_compat.err_ctxt().report_region_errors(body_def_id, &errors_compat))
}
let outlives_env = OutlivesEnvironment::new_with_implied_bounds_compat(
&infcx_compat,
body_def_id,
param_env,
assumed_wf_types,
// Don't *disable* the implied bounds hack; though this will only apply
// the implied bounds hack if this contains `bevy_ecs`'s `ParamSet` type.
false,
);
let errors_compat = infcx_compat.resolve_regions_with_outlives_env(&outlives_env);
if errors_compat.is_empty() {
// FIXME: Once we fix bevy, this would be the place to insert a warning
// to upgrade bevy.
Ok(())
} else {
Err(infcx.err_ctxt().report_region_errors(body_def_id, &errors))
}
}
struct ContainsBevyParamSet<'tcx> {
tcx: TyCtxt<'tcx>,
}
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsBevyParamSet<'tcx> {
type Result = ControlFlow<()>;
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
// We only care to match `ParamSet<T>` or `&ParamSet<T>`.
match t.kind() {
ty::Adt(def, _) => {
if self.tcx.item_name(def.did()) == sym::ParamSet
&& self.tcx.crate_name(def.did().krate) == sym::bevy_ecs
{
return ControlFlow::Break(());
}
}
ty::Ref(_, ty, _) => ty.visit_with(self)?,
_ => {}
}
ControlFlow::Continue(())
Err(infcx_compat.err_ctxt().report_region_errors(body_def_id, &errors_compat))
}
}
@ -201,7 +171,7 @@ fn check_well_formed(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGua
hir::Node::ImplItem(item) => check_impl_item(tcx, item),
hir::Node::ForeignItem(item) => check_foreign_item(tcx, item),
hir::Node::OpaqueTy(_) => Ok(crate::check::check::check_item_type(tcx, def_id)),
_ => unreachable!(),
_ => unreachable!("{node:?}"),
};
if let Some(generics) = node.generics() {
@ -1108,7 +1078,13 @@ fn check_associated_item(
let ty = tcx.type_of(item.def_id).instantiate_identity();
let ty = wfcx.normalize(span, Some(WellFormedLoc::Ty(item_id)), ty);
wfcx.register_wf_obligation(span, loc, ty.into());
check_sized_if_body(wfcx, item.def_id.expect_local(), ty, Some(span));
check_sized_if_body(
wfcx,
item.def_id.expect_local(),
ty,
Some(span),
ObligationCauseCode::SizedConstOrStatic,
);
Ok(())
}
ty::AssocKind::Fn => {
@ -1354,7 +1330,7 @@ fn check_item_type(
traits::ObligationCause::new(
ty_span,
wfcx.body_def_id,
ObligationCauseCode::WellFormed(None),
ObligationCauseCode::SizedConstOrStatic,
),
wfcx.param_env,
item_ty,
@ -1698,6 +1674,7 @@ fn check_fn_or_method<'tcx>(
hir::FnRetTy::Return(ty) => Some(ty.span),
hir::FnRetTy::DefaultReturn(_) => None,
},
ObligationCauseCode::SizedReturnType,
);
}
@ -1706,13 +1683,14 @@ fn check_sized_if_body<'tcx>(
def_id: LocalDefId,
ty: Ty<'tcx>,
maybe_span: Option<Span>,
code: ObligationCauseCode<'tcx>,
) {
let tcx = wfcx.tcx();
if let Some(body) = tcx.hir_maybe_body_owned_by(def_id) {
let span = maybe_span.unwrap_or(body.value.span);
wfcx.register_bound(
ObligationCause::new(span, def_id, traits::ObligationCauseCode::SizedReturnType),
ObligationCause::new(span, def_id, code),
wfcx.param_env,
ty,
tcx.require_lang_item(LangItem::Sized, Some(span)),

View file

@ -212,7 +212,10 @@ pub fn check_crate(tcx: TyCtxt<'_>) {
tcx.par_hir_body_owners(|item_def_id| {
let def_kind = tcx.def_kind(item_def_id);
match def_kind {
DefKind::Static { .. } => tcx.ensure_ok().eval_static_initializer(item_def_id),
DefKind::Static { .. } => {
tcx.ensure_ok().eval_static_initializer(item_def_id);
check::maybe_check_static_with_link_section(tcx, item_def_id);
}
DefKind::Const if tcx.generics_of(item_def_id).is_empty() => {
let instance = ty::Instance::new(item_def_id.into(), ty::GenericArgs::empty());
let cid = GlobalId { instance, promoted: None };
@ -223,12 +226,9 @@ pub fn check_crate(tcx: TyCtxt<'_>) {
}
});
// FIXME: Remove this when we implement creating `DefId`s
// for anon constants during their parents' typeck.
// Typeck all body owners in parallel will produce queries
// cycle errors because it may typeck on anon constants directly.
tcx.par_hir_body_owners(|item_def_id| {
let def_kind = tcx.def_kind(item_def_id);
// Skip `AnonConst`s because we feed their `type_of`.
if !matches!(def_kind, DefKind::AnonConst) {
tcx.ensure_ok().typeck(item_def_id);
}

View file

@ -171,10 +171,13 @@ hir_typeck_pass_to_variadic_function = can't pass `{$ty}` to variadic function
.suggestion = cast the value to `{$cast_ty}`
.teach_help = certain types, like `{$ty}`, must be casted before passing them to a variadic function, because of arcane ABI rules dictated by the C standard
hir_typeck_ptr_cast_add_auto_to_object = adding {$traits_len ->
[1] an auto trait {$traits}
hir_typeck_ptr_cast_add_auto_to_object = cannot add {$traits_len ->
[1] auto trait {$traits}
*[other] auto traits {$traits}
} to a trait object in a pointer cast may cause UB later on
} to dyn bound via pointer cast
.note = this could allow UB elsewhere
.help = use `transmute` if you're sure this is sound
.label = unsupported cast
hir_typeck_remove_semi_for_coerce = you might have meant to return the `match` expression
hir_typeck_remove_semi_for_coerce_expr = this could be implicitly returned but it is a statement, not a tail expression

View file

@ -940,23 +940,19 @@ impl<'a, 'tcx> CastCheck<'tcx> {
.collect::<Vec<_>>();
if !added.is_empty() {
tcx.emit_node_span_lint(
lint::builtin::PTR_CAST_ADD_AUTO_TO_OBJECT,
self.expr.hir_id,
self.span,
errors::PtrCastAddAutoToObject {
traits_len: added.len(),
traits: {
let mut traits: Vec<_> = added
.into_iter()
.map(|trait_did| tcx.def_path_str(trait_did))
.collect();
tcx.dcx().emit_err(errors::PtrCastAddAutoToObject {
span: self.span,
traits_len: added.len(),
traits: {
let mut traits: Vec<_> = added
.into_iter()
.map(|trait_did| tcx.def_path_str(trait_did))
.collect();
traits.sort();
traits.into()
},
traits.sort();
traits.into()
},
)
});
}
Ok(CastKind::PtrPtrCast)

View file

@ -51,15 +51,13 @@ use rustc_infer::traits::{
PredicateObligations,
};
use rustc_middle::span_bug;
use rustc_middle::traits::BuiltinImplSource;
use rustc_middle::ty::adjustment::{
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCoercion,
};
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::visit::TypeVisitableExt;
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt};
use rustc_session::parse::feature_err;
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span, sym};
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span};
use rustc_trait_selection::infer::InferCtxtExt as _;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use rustc_trait_selection::traits::{
@ -610,8 +608,6 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
ty::TraitRef::new(self.tcx, coerce_unsized_did, [coerce_source, coerce_target])
)];
let mut has_unsized_tuple_coercion = false;
// Keep resolving `CoerceUnsized` and `Unsize` predicates to avoid
// emitting a coercion in cases like `Foo<$1>` -> `Foo<$2>`, where
// inference might unify those two inner type variables later.
@ -690,31 +686,10 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
// be silent, as it causes a type mismatch later.
}
Ok(Some(impl_source)) => {
// Some builtin coercions are still unstable so we detect
// these here and emit a feature error if coercion doesn't fail
// due to another reason.
match impl_source {
traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
has_unsized_tuple_coercion = true;
}
_ => {}
}
queue.extend(impl_source.nested_obligations())
}
Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()),
}
}
if has_unsized_tuple_coercion && !self.tcx.features().unsized_tuple_coercion() {
feature_err(
&self.tcx.sess,
sym::unsized_tuple_coercion,
self.cause.span,
"unsized tuple coercion is not stable enough for use and is subject to change",
)
.emit();
}
Ok(coercion)
}

View file

@ -373,9 +373,14 @@ pub(crate) struct LossyProvenanceInt2Ptr<'tcx> {
pub sugg: LossyProvenanceInt2PtrSuggestion,
}
#[derive(LintDiagnostic)]
#[diag(hir_typeck_ptr_cast_add_auto_to_object)]
#[derive(Diagnostic)]
#[diag(hir_typeck_ptr_cast_add_auto_to_object, code = E0804)]
#[note]
#[help]
pub(crate) struct PtrCastAddAutoToObject {
#[primary_span]
#[label]
pub span: Span,
pub traits_len: usize,
pub traits: DiagSymbolList<String>,
}

View file

@ -1806,7 +1806,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
crate::GatherLocalsVisitor::new(&fcx).visit_body(body);
let ty = fcx.check_expr_with_expectation(body.value, expected);
fcx.require_type_is_sized(ty, body.value.span, ObligationCauseCode::ConstSized);
fcx.require_type_is_sized(ty, body.value.span, ObligationCauseCode::SizedConstOrStatic);
fcx.write_ty(block.hir_id, ty);
ty
}

View file

@ -599,6 +599,11 @@ fn register_builtins(store: &mut LintStore) {
"converted into hard error, \
see <https://github.com/rust-lang/rust/issues/73333> for more information",
);
store.register_removed(
"ptr_cast_add_auto_to_object",
"converted into hard error, see issue #127323 \
<https://github.com/rust-lang/rust/issues/127323> for more information",
);
}
fn register_internals(store: &mut LintStore) {

View file

@ -1,7 +1,7 @@
use std::iter;
use std::ops::ControlFlow;
use rustc_abi::{BackendRepr, ExternAbi, TagEncoding, VariantIdx, Variants, WrappingRange};
use rustc_abi::{BackendRepr, TagEncoding, VariantIdx, Variants, WrappingRange};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::DiagMessage;
use rustc_hir::intravisit::VisitorExt;
@ -1349,7 +1349,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
ty::FnPtr(sig_tys, hdr) => {
let sig = sig_tys.with(hdr);
if self.is_internal_abi(sig.abi()) {
if sig.abi().is_rustic_abi() {
return FfiUnsafe {
ty,
reason: fluent::lint_improper_ctypes_fnptr_reason,
@ -1552,13 +1552,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
self.check_type_for_ffi_and_report_errors(span, ty, true, false);
}
fn is_internal_abi(&self, abi: ExternAbi) -> bool {
matches!(
abi,
ExternAbi::Rust | ExternAbi::RustCall | ExternAbi::RustCold | ExternAbi::RustIntrinsic
)
}
/// Find any fn-ptr types with external ABIs in `ty`.
///
/// For example, `Option<extern "C" fn()>` returns `extern "C" fn()`
@ -1567,17 +1560,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
hir_ty: &hir::Ty<'tcx>,
ty: Ty<'tcx>,
) -> Vec<(Ty<'tcx>, Span)> {
struct FnPtrFinder<'a, 'b, 'tcx> {
visitor: &'a ImproperCTypesVisitor<'b, 'tcx>,
struct FnPtrFinder<'tcx> {
spans: Vec<Span>,
tys: Vec<Ty<'tcx>>,
}
impl<'a, 'b, 'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'a, 'b, 'tcx> {
impl<'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'tcx> {
fn visit_ty(&mut self, ty: &'_ hir::Ty<'_, AmbigArg>) {
debug!(?ty);
if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind
&& !self.visitor.is_internal_abi(*abi)
&& !abi.is_rustic_abi()
{
self.spans.push(ty.span);
}
@ -1586,12 +1578,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
}
}
impl<'a, 'b, 'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'a, 'b, 'tcx> {
impl<'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'tcx> {
type Result = ();
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
if let ty::FnPtr(_, hdr) = ty.kind()
&& !self.visitor.is_internal_abi(hdr.abi)
&& !hdr.abi.is_rustic_abi()
{
self.tys.push(ty);
}
@ -1600,7 +1592,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
}
}
let mut visitor = FnPtrFinder { visitor: self, spans: Vec::new(), tys: Vec::new() };
let mut visitor = FnPtrFinder { spans: Vec::new(), tys: Vec::new() };
ty.visit_with(&mut visitor);
visitor.visit_ty_unambig(hir_ty);
@ -1615,13 +1607,13 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations {
match it.kind {
hir::ForeignItemKind::Fn(sig, _, _) => {
if vis.is_internal_abi(abi) {
if abi.is_rustic_abi() {
vis.check_fn(it.owner_id.def_id, sig.decl)
} else {
vis.check_foreign_fn(it.owner_id.def_id, sig.decl);
}
}
hir::ForeignItemKind::Static(ty, _, _) if !vis.is_internal_abi(abi) => {
hir::ForeignItemKind::Static(ty, _, _) if !abi.is_rustic_abi() => {
vis.check_foreign_static(it.owner_id, ty.span);
}
hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (),
@ -1775,7 +1767,7 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions {
};
let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
if vis.is_internal_abi(abi) {
if abi.is_rustic_abi() {
vis.check_fn(id, decl);
} else {
vis.check_foreign_fn(id, decl);

View file

@ -80,7 +80,6 @@ declare_lint_pass! {
PRIVATE_BOUNDS,
PRIVATE_INTERFACES,
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
PTR_CAST_ADD_AUTO_TO_OBJECT,
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
PUB_USE_OF_PRIVATE_EXTERN_CRATE,
REDUNDANT_IMPORTS,
@ -4827,58 +4826,6 @@ declare_lint! {
};
}
declare_lint! {
/// The `ptr_cast_add_auto_to_object` lint detects casts of raw pointers to trait
/// objects, which add auto traits.
///
/// ### Example
///
/// ```rust,edition2021,compile_fail
/// let ptr: *const dyn core::any::Any = &();
/// _ = ptr as *const dyn core::any::Any + Send;
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Adding an auto trait can make the vtable invalid, potentially causing
/// UB in safe code afterwards. For example:
///
/// ```ignore (causes a warning)
/// #![feature(arbitrary_self_types)]
///
/// trait Trait {
/// fn f(self: *const Self)
/// where
/// Self: Send;
/// }
///
/// impl Trait for *const () {
/// fn f(self: *const Self) {
/// unreachable!()
/// }
/// }
///
/// fn main() {
/// let unsend: *const () = &();
/// let unsend: *const dyn Trait = &unsend;
/// let send_bad: *const (dyn Trait + Send) = unsend as _;
/// send_bad.f(); // this crashes, since vtable for `*const ()` does not have an entry for `f`
/// }
/// ```
///
/// Generally you must ensure that vtable is right for the pointer's type,
/// before passing the pointer to safe code.
pub PTR_CAST_ADD_AUTO_TO_OBJECT,
Warn,
"detects `as` casts from pointers to `dyn Trait` to pointers to `dyn Trait + Auto`",
@future_incompatible = FutureIncompatibleInfo {
reason: FutureIncompatibilityReason::FutureReleaseErrorReportInDeps,
reference: "issue #127323 <https://github.com/rust-lang/rust/issues/127323>",
};
}
declare_lint! {
/// The `out_of_scope_macro_calls` lint detects `macro_rules` called when they are not in scope,
/// above their definition, which may happen in key-value attributes.

View file

@ -508,6 +508,14 @@ impl<'tcx, T: Clone> Key for CanonicalQueryInput<'tcx, T> {
}
}
impl<'tcx, T: Clone> Key for (CanonicalQueryInput<'tcx, T>, bool) {
type Cache<V> = DefaultCache<Self, V>;
fn default_span(&self, _tcx: TyCtxt<'_>) -> Span {
DUMMY_SP
}
}
impl Key for (Symbol, u32, u32) {
type Cache<V> = DefaultCache<Self, V>;

View file

@ -2262,22 +2262,13 @@ rustc_queries! {
desc { "normalizing `{}`", goal.value }
}
query implied_outlives_bounds_compat(
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>
) -> Result<
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
NoSolution,
> {
desc { "computing implied outlives bounds for `{}`", goal.canonical.value.value.ty }
}
query implied_outlives_bounds(
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>
key: (CanonicalImpliedOutlivesBoundsGoal<'tcx>, bool)
) -> Result<
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
NoSolution,
> {
desc { "computing implied outlives bounds v2 for `{}`", goal.canonical.value.value.ty }
desc { "computing implied outlives bounds for `{}` (hack disabled = {:?})", key.0.canonical.value.value.ty, key.1 }
}
/// Do not call this query directly:

View file

@ -266,7 +266,7 @@ pub enum ObligationCauseCode<'tcx> {
},
/// Constant expressions must be sized.
ConstSized,
SizedConstOrStatic,
/// `static` items must have `Sync` type.
SharedStatic,

View file

@ -6,27 +6,25 @@ use rustc_middle::ty::{self, Ty, TypeVisitableExt};
use crate::builder::Builder;
use crate::builder::expr::as_place::{PlaceBase, PlaceBuilder};
use crate::builder::matches::{FlatPat, MatchPairTree, TestCase};
use crate::builder::matches::{FlatPat, MatchPairTree, PatternExtraData, TestCase};
impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Builds and returns [`MatchPairTree`] subtrees, one for each pattern in
/// Builds and pushes [`MatchPairTree`] subtrees, one for each pattern in
/// `subpatterns`, representing the fields of a [`PatKind::Variant`] or
/// [`PatKind::Leaf`].
///
/// Used internally by [`MatchPairTree::for_pattern`].
fn field_match_pairs(
&mut self,
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
extra_data: &mut PatternExtraData<'tcx>,
place: PlaceBuilder<'tcx>,
subpatterns: &[FieldPat<'tcx>],
) -> Vec<MatchPairTree<'tcx>> {
subpatterns
.iter()
.map(|fieldpat| {
let place =
place.clone_project(PlaceElem::Field(fieldpat.field, fieldpat.pattern.ty));
MatchPairTree::for_pattern(place, &fieldpat.pattern, self)
})
.collect()
) {
for fieldpat in subpatterns {
let place = place.clone_project(PlaceElem::Field(fieldpat.field, fieldpat.pattern.ty));
MatchPairTree::for_pattern(place, &fieldpat.pattern, self, match_pairs, extra_data);
}
}
/// Builds [`MatchPairTree`] subtrees for the prefix/middle/suffix parts of an
@ -36,6 +34,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
fn prefix_slice_suffix(
&mut self,
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
extra_data: &mut PatternExtraData<'tcx>,
place: &PlaceBuilder<'tcx>,
prefix: &[Pat<'tcx>],
opt_slice: &Option<Box<Pat<'tcx>>>,
@ -56,11 +55,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
((prefix.len() + suffix.len()).try_into().unwrap(), false)
};
match_pairs.extend(prefix.iter().enumerate().map(|(idx, subpattern)| {
for (idx, subpattern) in prefix.iter().enumerate() {
let elem =
ProjectionElem::ConstantIndex { offset: idx as u64, min_length, from_end: false };
MatchPairTree::for_pattern(place.clone_project(elem), subpattern, self)
}));
let place = place.clone_project(elem);
MatchPairTree::for_pattern(place, subpattern, self, match_pairs, extra_data)
}
if let Some(subslice_pat) = opt_slice {
let suffix_len = suffix.len() as u64;
@ -69,10 +69,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
to: if exact_size { min_length - suffix_len } else { suffix_len },
from_end: !exact_size,
});
match_pairs.push(MatchPairTree::for_pattern(subslice, subslice_pat, self));
MatchPairTree::for_pattern(subslice, subslice_pat, self, match_pairs, extra_data);
}
match_pairs.extend(suffix.iter().rev().enumerate().map(|(idx, subpattern)| {
for (idx, subpattern) in suffix.iter().rev().enumerate() {
let end_offset = (idx + 1) as u64;
let elem = ProjectionElem::ConstantIndex {
offset: if exact_size { min_length - end_offset } else { end_offset },
@ -80,19 +80,21 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
from_end: !exact_size,
};
let place = place.clone_project(elem);
MatchPairTree::for_pattern(place, subpattern, self)
}));
MatchPairTree::for_pattern(place, subpattern, self, match_pairs, extra_data)
}
}
}
impl<'tcx> MatchPairTree<'tcx> {
/// Recursively builds a match pair tree for the given pattern and its
/// subpatterns.
pub(in crate::builder) fn for_pattern(
pub(super) fn for_pattern(
mut place_builder: PlaceBuilder<'tcx>,
pattern: &Pat<'tcx>,
cx: &mut Builder<'_, 'tcx>,
) -> MatchPairTree<'tcx> {
match_pairs: &mut Vec<Self>, // Newly-created nodes are added to this vector
extra_data: &mut PatternExtraData<'tcx>, // Bindings/ascriptions are added here
) {
// Force the place type to the pattern's type.
// FIXME(oli-obk): can we use this to simplify slice/array pattern hacks?
if let Some(resolved) = place_builder.resolve_upvar(cx) {
@ -113,64 +115,102 @@ impl<'tcx> MatchPairTree<'tcx> {
place_builder = place_builder.project(ProjectionElem::OpaqueCast(pattern.ty));
}
// Place can be none if the pattern refers to a non-captured place in a closure.
let place = place_builder.try_to_place(cx);
let default_irrefutable = || TestCase::Irrefutable { binding: None, ascription: None };
let mut subpairs = Vec::new();
let test_case = match pattern.kind {
PatKind::Wild | PatKind::Error(_) => default_irrefutable(),
PatKind::Wild | PatKind::Error(_) => None,
PatKind::Or { ref pats } => TestCase::Or {
PatKind::Or { ref pats } => Some(TestCase::Or {
pats: pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(),
},
}),
PatKind::Range(ref range) => {
if range.is_full_range(cx.tcx) == Some(true) {
default_irrefutable()
None
} else {
TestCase::Range(Arc::clone(range))
Some(TestCase::Range(Arc::clone(range)))
}
}
PatKind::Constant { value } => TestCase::Constant { value },
PatKind::Constant { value } => Some(TestCase::Constant { value }),
PatKind::AscribeUserType {
ascription: Ascription { ref annotation, variance },
ref subpattern,
..
} => {
// Apply the type ascription to the value at `match_pair.place`
let ascription = place.map(|source| super::Ascription {
annotation: annotation.clone(),
source,
variance,
});
MatchPairTree::for_pattern(
place_builder,
subpattern,
cx,
&mut subpairs,
extra_data,
);
subpairs.push(MatchPairTree::for_pattern(place_builder, subpattern, cx));
TestCase::Irrefutable { ascription, binding: None }
// Apply the type ascription to the value at `match_pair.place`
if let Some(source) = place {
let annotation = annotation.clone();
extra_data.ascriptions.push(super::Ascription { source, annotation, variance });
}
None
}
PatKind::Binding { mode, var, ref subpattern, .. } => {
let binding = place.map(|source| super::Binding {
span: pattern.span,
source,
var_id: var,
binding_mode: mode,
});
// In order to please the borrow checker, when lowering a pattern
// like `x @ subpat` we must establish any bindings in `subpat`
// before establishing the binding for `x`.
//
// For example (from #69971):
//
// ```ignore (illustrative)
// struct NonCopyStruct {
// copy_field: u32,
// }
//
// fn foo1(x: NonCopyStruct) {
// let y @ NonCopyStruct { copy_field: z } = x;
// // the above should turn into
// let z = x.copy_field;
// let y = x;
// }
// ```
// First, recurse into the subpattern, if any.
if let Some(subpattern) = subpattern.as_ref() {
// this is the `x @ P` case; have to keep matching against `P` now
subpairs.push(MatchPairTree::for_pattern(place_builder, subpattern, cx));
MatchPairTree::for_pattern(
place_builder,
subpattern,
cx,
&mut subpairs,
extra_data,
);
}
TestCase::Irrefutable { ascription: None, binding }
// Then push this binding, after any bindings in the subpattern.
if let Some(source) = place {
extra_data.bindings.push(super::Binding {
span: pattern.span,
source,
var_id: var,
binding_mode: mode,
});
}
None
}
PatKind::ExpandedConstant { subpattern: ref pattern, def_id: _, is_inline: false } => {
subpairs.push(MatchPairTree::for_pattern(place_builder, pattern, cx));
default_irrefutable()
MatchPairTree::for_pattern(place_builder, pattern, cx, &mut subpairs, extra_data);
None
}
PatKind::ExpandedConstant { subpattern: ref pattern, def_id, is_inline: true } => {
MatchPairTree::for_pattern(place_builder, pattern, cx, &mut subpairs, extra_data);
// Apply a type ascription for the inline constant to the value at `match_pair.place`
let ascription = place.map(|source| {
if let Some(source) = place {
let span = pattern.span;
let parent_id = cx.tcx.typeck_root_def_id(cx.def_id.to_def_id());
let args = ty::InlineConstArgs::new(
@ -189,33 +229,47 @@ impl<'tcx> MatchPairTree<'tcx> {
span,
user_ty: Box::new(user_ty),
};
super::Ascription { annotation, source, variance: ty::Contravariant }
});
let variance = ty::Contravariant;
extra_data.ascriptions.push(super::Ascription { annotation, source, variance });
}
subpairs.push(MatchPairTree::for_pattern(place_builder, pattern, cx));
TestCase::Irrefutable { ascription, binding: None }
None
}
PatKind::Array { ref prefix, ref slice, ref suffix } => {
cx.prefix_slice_suffix(&mut subpairs, &place_builder, prefix, slice, suffix);
default_irrefutable()
cx.prefix_slice_suffix(
&mut subpairs,
extra_data,
&place_builder,
prefix,
slice,
suffix,
);
None
}
PatKind::Slice { ref prefix, ref slice, ref suffix } => {
cx.prefix_slice_suffix(&mut subpairs, &place_builder, prefix, slice, suffix);
cx.prefix_slice_suffix(
&mut subpairs,
extra_data,
&place_builder,
prefix,
slice,
suffix,
);
if prefix.is_empty() && slice.is_some() && suffix.is_empty() {
default_irrefutable()
None
} else {
TestCase::Slice {
Some(TestCase::Slice {
len: prefix.len() + suffix.len(),
variable_length: slice.is_some(),
}
})
}
}
PatKind::Variant { adt_def, variant_index, args, ref subpatterns } => {
let downcast_place = place_builder.downcast(adt_def, variant_index); // `(x as Variant)`
subpairs = cx.field_match_pairs(downcast_place, subpatterns);
cx.field_match_pairs(&mut subpairs, extra_data, downcast_place, subpatterns);
let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| {
i == variant_index
@ -225,21 +279,23 @@ impl<'tcx> MatchPairTree<'tcx> {
.apply_ignore_module(cx.tcx, cx.infcx.typing_env(cx.param_env))
}) && (adt_def.did().is_local()
|| !adt_def.is_variant_list_non_exhaustive());
if irrefutable {
default_irrefutable()
} else {
TestCase::Variant { adt_def, variant_index }
}
if irrefutable { None } else { Some(TestCase::Variant { adt_def, variant_index }) }
}
PatKind::Leaf { ref subpatterns } => {
subpairs = cx.field_match_pairs(place_builder, subpatterns);
default_irrefutable()
cx.field_match_pairs(&mut subpairs, extra_data, place_builder, subpatterns);
None
}
PatKind::Deref { ref subpattern } => {
subpairs.push(MatchPairTree::for_pattern(place_builder.deref(), subpattern, cx));
default_irrefutable()
MatchPairTree::for_pattern(
place_builder.deref(),
subpattern,
cx,
&mut subpairs,
extra_data,
);
None
}
PatKind::DerefPattern { ref subpattern, mutability } => {
@ -249,23 +305,32 @@ impl<'tcx> MatchPairTree<'tcx> {
Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, subpattern.ty, mutability),
pattern.span,
);
subpairs.push(MatchPairTree::for_pattern(
MatchPairTree::for_pattern(
PlaceBuilder::from(temp).deref(),
subpattern,
cx,
));
TestCase::Deref { temp, mutability }
&mut subpairs,
extra_data,
);
Some(TestCase::Deref { temp, mutability })
}
PatKind::Never => TestCase::Never,
PatKind::Never => Some(TestCase::Never),
};
MatchPairTree {
place,
test_case,
subpairs,
pattern_ty: pattern.ty,
pattern_span: pattern.span,
if let Some(test_case) = test_case {
// This pattern is refutable, so push a new match-pair node.
match_pairs.push(MatchPairTree {
place: place.expect("refutable patterns should always have a place to inspect"),
test_case,
subpairs,
pattern_ty: pattern.ty,
pattern_span: pattern.span,
})
} else {
// This pattern is irrefutable, so it doesn't need its own match-pair node.
// Just push its refutable subpatterns instead, if any.
match_pairs.extend(subpairs);
}
}
}

View file

@ -26,7 +26,6 @@ use crate::builder::{
// helper functions, broken out by category:
mod match_pair;
mod simplify;
mod test;
mod util;
@ -987,18 +986,16 @@ impl<'tcx> PatternExtraData<'tcx> {
}
/// A pattern in a form suitable for lowering the match tree, with all irrefutable
/// patterns simplified away, and or-patterns sorted to the end.
/// patterns simplified away.
///
/// Here, "flat" indicates that the pattern's match pairs have been recursively
/// simplified by [`Builder::simplify_match_pairs`]. They are not necessarily
/// flat in an absolute sense.
/// Here, "flat" indicates that irrefutable nodes in the pattern tree have been
/// recursively replaced with their refutable subpatterns. They are not
/// necessarily flat in an absolute sense.
///
/// Will typically be incorporated into a [`Candidate`].
#[derive(Debug, Clone)]
struct FlatPat<'tcx> {
/// To match the pattern, all of these must be satisfied...
// Invariant: all the match pairs are recursively simplified.
// Invariant: or-patterns must be sorted to the end.
match_pairs: Vec<MatchPairTree<'tcx>>,
extra_data: PatternExtraData<'tcx>,
@ -1008,17 +1005,15 @@ impl<'tcx> FlatPat<'tcx> {
/// Creates a `FlatPat` containing a simplified [`MatchPairTree`] list/forest
/// for the given pattern.
fn new(place: PlaceBuilder<'tcx>, pattern: &Pat<'tcx>, cx: &mut Builder<'_, 'tcx>) -> Self {
// First, recursively build a tree of match pairs for the given pattern.
let mut match_pairs = vec![MatchPairTree::for_pattern(place, pattern, cx)];
// Recursively build a tree of match pairs for the given pattern.
let mut match_pairs = vec![];
let mut extra_data = PatternExtraData {
span: pattern.span,
bindings: Vec::new(),
ascriptions: Vec::new(),
is_never: pattern.is_never_pattern(),
};
// Recursively remove irrefutable match pairs, while recording their
// bindings/ascriptions, and sort or-patterns after other match pairs.
cx.simplify_match_pairs(&mut match_pairs, &mut extra_data);
MatchPairTree::for_pattern(place, pattern, cx, &mut match_pairs, &mut extra_data);
Self { match_pairs, extra_data }
}
@ -1055,7 +1050,6 @@ struct Candidate<'tcx> {
/// (see [`Builder::test_remaining_match_pairs_after_or`]).
///
/// Invariants:
/// - All [`TestCase::Irrefutable`] patterns have been removed by simplification.
/// - All or-patterns ([`TestCase::Or`]) have been sorted to the end.
match_pairs: Vec<MatchPairTree<'tcx>>,
@ -1126,7 +1120,7 @@ impl<'tcx> Candidate<'tcx> {
/// Incorporates an already-simplified [`FlatPat`] into a new candidate.
fn from_flat_pat(flat_pat: FlatPat<'tcx>, has_guard: bool) -> Self {
Candidate {
let mut this = Candidate {
match_pairs: flat_pat.match_pairs,
extra_data: flat_pat.extra_data,
has_guard,
@ -1135,7 +1129,14 @@ impl<'tcx> Candidate<'tcx> {
otherwise_block: None,
pre_binding_block: None,
false_edge_start_block: None,
}
};
this.sort_match_pairs();
this
}
/// Restores the invariant that or-patterns must be sorted to the end.
fn sort_match_pairs(&mut self) {
self.match_pairs.sort_by_key(|pair| matches!(pair.test_case, TestCase::Or { .. }));
}
/// Returns whether the first match pair of this candidate is an or-pattern.
@ -1227,17 +1228,11 @@ struct Ascription<'tcx> {
/// - [`Builder::pick_test_for_match_pair`] (to choose a test)
/// - [`Builder::sort_candidate`] (to see how the test interacts with a match pair)
///
/// Two variants are unlike the others and deserve special mention:
///
/// - [`Self::Irrefutable`] is only used temporarily when building a [`MatchPairTree`].
/// They are then flattened away by [`Builder::simplify_match_pairs`], with any
/// bindings/ascriptions incorporated into the enclosing [`FlatPat`].
/// - [`Self::Or`] are not tested directly like the other variants. Instead they
/// participate in or-pattern expansion, where they are transformed into subcandidates.
/// - See [`Builder::expand_and_match_or_candidates`].
/// Note that or-patterns are not tested directly like the other variants.
/// Instead they participate in or-pattern expansion, where they are transformed into
/// subcandidates. See [`Builder::expand_and_match_or_candidates`].
#[derive(Debug, Clone)]
enum TestCase<'tcx> {
Irrefutable { binding: Option<Binding<'tcx>>, ascription: Option<Ascription<'tcx>> },
Variant { adt_def: ty::AdtDef<'tcx>, variant_index: VariantIdx },
Constant { value: mir::Const<'tcx> },
Range(Arc<PatRange<'tcx>>),
@ -1261,19 +1256,9 @@ impl<'tcx> TestCase<'tcx> {
#[derive(Debug, Clone)]
pub(crate) struct MatchPairTree<'tcx> {
/// This place...
///
/// ---
/// This can be `None` if it referred to a non-captured place in a closure.
///
/// Invariant: Can only be `None` when `test_case` is `Irrefutable`.
/// Therefore this must be `Some(_)` after simplification.
place: Option<Place<'tcx>>,
place: Place<'tcx>,
/// ... must pass this test...
///
/// ---
/// Invariant: after creation and simplification in [`FlatPat::new`],
/// this must not be [`TestCase::Irrefutable`].
test_case: TestCase<'tcx>,
/// ... and these subpairs must match.
@ -2091,11 +2076,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// Extract the match-pair from the highest priority candidate
let match_pair = &candidates[0].match_pairs[0];
let test = self.pick_test_for_match_pair(match_pair);
// Unwrap is ok after simplification.
let match_place = match_pair.place.unwrap();
debug!(?test, ?match_pair);
(match_place, test)
(match_pair.place, test)
}
/// Given a test, we partition the input candidates into several buckets.

View file

@ -1,71 +0,0 @@
//! Simplifying Candidates
//!
//! *Simplifying* a match pair `place @ pattern` means breaking it down
//! into bindings or other, simpler match pairs. For example:
//!
//! - `place @ (P1, P2)` can be simplified to `[place.0 @ P1, place.1 @ P2]`
//! - `place @ x` can be simplified to `[]` by binding `x` to `place`
//!
//! The `simplify_match_pairs` routine just repeatedly applies these
//! sort of simplifications until there is nothing left to
//! simplify. Match pairs cannot be simplified if they require some
//! sort of test: for example, testing which variant an enum is, or
//! testing a value against a constant.
use std::mem;
use tracing::{debug, instrument};
use crate::builder::Builder;
use crate::builder::matches::{MatchPairTree, PatternExtraData, TestCase};
impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Simplify a list of match pairs so they all require a test. Stores relevant bindings and
/// ascriptions in `extra_data`.
#[instrument(skip(self), level = "debug")]
pub(super) fn simplify_match_pairs(
&mut self,
match_pairs: &mut Vec<MatchPairTree<'tcx>>,
extra_data: &mut PatternExtraData<'tcx>,
) {
// In order to please the borrow checker, in a pattern like `x @ pat` we must lower the
// bindings in `pat` before `x`. E.g. (#69971):
//
// struct NonCopyStruct {
// copy_field: u32,
// }
//
// fn foo1(x: NonCopyStruct) {
// let y @ NonCopyStruct { copy_field: z } = x;
// // the above should turn into
// let z = x.copy_field;
// let y = x;
// }
//
// We therefore lower bindings from left-to-right, except we lower the `x` in `x @ pat`
// after any bindings in `pat`. This doesn't work for or-patterns: the current structure of
// match lowering forces us to lower bindings inside or-patterns last.
for mut match_pair in mem::take(match_pairs) {
self.simplify_match_pairs(&mut match_pair.subpairs, extra_data);
if let TestCase::Irrefutable { binding, ascription } = match_pair.test_case {
if let Some(binding) = binding {
extra_data.bindings.push(binding);
}
if let Some(ascription) = ascription {
extra_data.ascriptions.push(ascription);
}
// Simplifiable pattern; we replace it with its already simplified subpairs.
match_pairs.append(&mut match_pair.subpairs);
} else {
// Unsimplifiable pattern; we keep it.
match_pairs.push(match_pair);
}
}
// Move or-patterns to the end, because they can result in us
// creating additional candidates, so we want to test them as
// late as possible.
match_pairs.sort_by_key(|pair| matches!(pair.test_case, TestCase::Or { .. }));
debug!(simplified = ?match_pairs, "simplify_match_pairs");
}
}

View file

@ -55,12 +55,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// Or-patterns are not tested directly; instead they are expanded into subcandidates,
// which are then distinguished by testing whatever non-or patterns they contain.
TestCase::Or { .. } => bug!("or-patterns should have already been handled"),
TestCase::Irrefutable { .. } => span_bug!(
match_pair.pattern_span,
"simplifiable pattern found: {:?}",
match_pair.pattern_span
),
};
Test { span: match_pair.pattern_span, kind }
@ -546,11 +540,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// than one, but it'd be very unusual to have two sides that
// both require tests; you'd expect one side to be simplified
// away.)
let (match_pair_index, match_pair) = candidate
.match_pairs
.iter()
.enumerate()
.find(|&(_, mp)| mp.place == Some(test_place))?;
let (match_pair_index, match_pair) =
candidate.match_pairs.iter().enumerate().find(|&(_, mp)| mp.place == test_place)?;
// If true, the match pair is completely entailed by its corresponding test
// branch, so it can be removed. If false, the match pair is _compatible_
@ -593,7 +584,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
candidate
.match_pairs
.iter()
.any(|mp| mp.place == Some(test_place) && is_covering_range(&mp.test_case))
.any(|mp| mp.place == test_place && is_covering_range(&mp.test_case))
};
if sorted_candidates
.get(&TestBranch::Failure)
@ -769,7 +760,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let match_pair = candidate.match_pairs.remove(match_pair_index);
candidate.match_pairs.extend(match_pair.subpairs);
// Move or-patterns to the end.
candidate.match_pairs.sort_by_key(|pair| matches!(pair.test_case, TestCase::Or { .. }));
candidate.sort_match_pairs();
}
ret

View file

@ -173,14 +173,10 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> {
// }
// ```
// Hence we fake borrow using a deep borrow.
if let Some(place) = match_pair.place {
self.fake_borrow(place, FakeBorrowKind::Deep);
}
self.fake_borrow(match_pair.place, FakeBorrowKind::Deep);
} else {
// Insert a Shallow borrow of any place that is switched on.
if let Some(place) = match_pair.place {
self.fake_borrow(place, FakeBorrowKind::Shallow);
}
self.fake_borrow(match_pair.place, FakeBorrowKind::Shallow);
for subpair in &match_pair.subpairs {
self.visit_match_pair(subpair);

View file

@ -53,11 +53,7 @@ fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
// Rust calls cannot themselves create foreign unwinds.
// We assume this is true for intrinsics as well.
if let ExternAbi::RustIntrinsic
| ExternAbi::Rust
| ExternAbi::RustCall
| ExternAbi::RustCold = sig.abi()
{
if sig.abi().is_rustic_abi() {
continue;
};

View file

@ -513,6 +513,24 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> &
body.tainted_by_errors = Some(error_reported);
}
// Also taint the body if it's within a top-level item that is not well formed.
//
// We do this check here and not during `mir_promoted` because that may result
// in borrowck cycles if WF requires looking into an opaque hidden type.
let root = tcx.typeck_root_def_id(def.to_def_id());
match tcx.def_kind(root) {
DefKind::Fn
| DefKind::AssocFn
| DefKind::Static { .. }
| DefKind::Const
| DefKind::AssocConst => {
if let Err(guar) = tcx.check_well_formed(root.expect_local()) {
body.tainted_by_errors = Some(guar);
}
}
_ => {}
}
run_analysis_to_runtime_passes(tcx, &mut body);
tcx.alloc_steal_mir(body)

View file

@ -1,6 +1,6 @@
//! This module ensures that if a function's ABI requires a particular target feature,
//! that target feature is enabled both on the callee and all callers.
use rustc_abi::{BackendRepr, ExternAbi, RegKind};
use rustc_abi::{BackendRepr, RegKind};
use rustc_hir::CRATE_HIR_ID;
use rustc_middle::mir::{self, traversal};
use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt};
@ -115,8 +115,8 @@ fn check_call_site_abi<'tcx>(
span: Span,
caller: InstanceKind<'tcx>,
) {
if callee.fn_sig(tcx).abi() == ExternAbi::Rust {
// "Rust" ABI never passes arguments in vector registers.
if callee.fn_sig(tcx).abi().is_rustic_abi() {
// we directly handle the soundness of Rust ABIs
return;
}
let typing_env = ty::TypingEnv::fully_monomorphized();

View file

@ -786,13 +786,6 @@ where
)
}
// `(A, B, T)` -> `(A, B, U)` where `T: Unsize<U>`
(ty::Tuple(a_tys), ty::Tuple(b_tys))
if a_tys.len() == b_tys.len() && !a_tys.is_empty() =>
{
result_to_single(ecx.consider_builtin_tuple_unsize(goal, a_tys, b_tys))
}
_ => vec![],
}
})
@ -1084,48 +1077,6 @@ where
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
/// We generate the following builtin impl for tuples of all sizes.
///
/// This impl is still unstable and we emit a feature error when it
/// when it is used by a coercion.
/// ```ignore (builtin impl example)
/// impl<T: ?Sized, U: ?Sized, V: ?Sized> Unsize<(T, V)> for (T, U)
/// where
/// U: Unsize<V>,
/// {}
/// ```
fn consider_builtin_tuple_unsize(
&mut self,
goal: Goal<I, (I::Ty, I::Ty)>,
a_tys: I::Tys,
b_tys: I::Tys,
) -> Result<Candidate<I>, NoSolution> {
let cx = self.cx();
let Goal { predicate: (_a_ty, b_ty), .. } = goal;
let (&a_last_ty, a_rest_tys) = a_tys.split_last().unwrap();
let b_last_ty = b_tys.last().unwrap();
// Instantiate just the tail field of B., and require that they're equal.
let unsized_a_ty = Ty::new_tup_from_iter(cx, a_rest_tys.iter().copied().chain([b_last_ty]));
self.eq(goal.param_env, unsized_a_ty, b_ty)?;
// Similar to ADTs, require that we can unsize the tail.
self.add_goal(
GoalSource::ImplWhereBound,
goal.with(
cx,
ty::TraitRef::new(
cx,
cx.require_lang_item(TraitSolverLangItem::Unsize),
[a_last_ty, b_last_ty],
),
),
);
self.probe_builtin_trait_candidate(BuiltinImplSource::TupleUnsizing)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}
// Return `Some` if there is an impl (built-in or user provided) that may
// hold for the self type of the goal, which for coherence and soundness
// purposes must disqualify the built-in auto impl assembled by considering

View file

@ -477,8 +477,8 @@ fn remove_cycle(
/// Detects query cycles by using depth first search over all active query jobs.
/// If a query cycle is found it will break the cycle by finding an edge which
/// uses a query latch and then resuming that waiter.
/// There may be multiple cycles involved in a deadlock, so this searches
/// all active queries for cycles before finally resuming all the waiters at once.
/// There may be multiple cycles involved in a deadlock, but we only search
/// one cycle at a call and resume one waiter at once. See `FIXME` below.
pub fn break_query_cycles(query_map: QueryMap, registry: &rayon_core::Registry) {
let mut wakelist = Vec::new();
let mut jobs: Vec<QueryJobId> = query_map.keys().cloned().collect();
@ -488,6 +488,19 @@ pub fn break_query_cycles(query_map: QueryMap, registry: &rayon_core::Registry)
while jobs.len() > 0 {
if remove_cycle(&query_map, &mut jobs, &mut wakelist) {
found_cycle = true;
// FIXME(#137731): Resume all the waiters at once may cause deadlocks,
// so we resume one waiter at a call for now. It's still unclear whether
// it's due to possible issues in rustc-rayon or instead in the handling
// of query cycles.
// This seem to only appear when multiple query cycles errors
// are involved, so this reduction in parallelism, while suboptimal, is not
// universal and only the deadlock handler will encounter these cases.
// The workaround shows loss of potential gains, but there still are big
// improvements in the common case, and no regressions compared to the
// single-threaded case. More investigation is still needed, and once fixed,
// we can wake up all the waiters up.
break;
}
}

View file

@ -3126,8 +3126,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
Applicability::MachineApplicable,
);
}
ObligationCauseCode::ConstSized => {
err.note("constant expressions must have a statically known size");
ObligationCauseCode::SizedConstOrStatic => {
err.note("statics and constants must have a statically known size");
}
ObligationCauseCode::InlineAsmSized => {
err.note("all inline asm arguments must have a statically known size");

View file

@ -17,13 +17,7 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
param_env: ty::ParamEnv<'tcx>,
assumed_wf_tys: impl IntoIterator<Item = Ty<'tcx>>,
) -> Self {
Self::new_with_implied_bounds_compat(
infcx,
body_id,
param_env,
assumed_wf_tys,
!infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat,
)
Self::new_with_implied_bounds_compat(infcx, body_id, param_env, assumed_wf_tys, false)
}
fn new_with_implied_bounds_compat(
@ -31,7 +25,7 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
body_id: LocalDefId,
param_env: ty::ParamEnv<'tcx>,
assumed_wf_tys: impl IntoIterator<Item = Ty<'tcx>>,
implied_bounds_compat: bool,
disable_implied_bounds_hack: bool,
) -> Self {
let mut bounds = vec![];
@ -59,11 +53,11 @@ impl<'tcx> OutlivesEnvironment<'tcx> {
OutlivesEnvironment::from_normalized_bounds(
param_env,
bounds,
infcx.implied_bounds_tys_with_compat(
infcx.implied_bounds_tys(
body_id,
param_env,
assumed_wf_tys,
implied_bounds_compat,
disable_implied_bounds_hack,
),
)
}

View file

@ -36,7 +36,7 @@ fn implied_outlives_bounds<'a, 'tcx>(
param_env: ty::ParamEnv<'tcx>,
body_id: LocalDefId,
ty: Ty<'tcx>,
compat: bool,
disable_implied_bounds_hack: bool,
) -> Vec<OutlivesBound<'tcx>> {
let ty = infcx.resolve_vars_if_possible(ty);
let ty = OpportunisticRegionResolver::new(infcx).fold_ty(ty);
@ -52,11 +52,8 @@ fn implied_outlives_bounds<'a, 'tcx>(
let mut canonical_var_values = OriginalQueryValues::default();
let input = ImpliedOutlivesBounds { ty };
let canonical = infcx.canonicalize_query(param_env.and(input), &mut canonical_var_values);
let implied_bounds_result = if compat {
infcx.tcx.implied_outlives_bounds_compat(canonical)
} else {
infcx.tcx.implied_outlives_bounds(canonical)
};
let implied_bounds_result =
infcx.tcx.implied_outlives_bounds((canonical, disable_implied_bounds_hack));
let Ok(canonical_result) = implied_bounds_result else {
return vec![];
};
@ -110,14 +107,15 @@ fn implied_outlives_bounds<'a, 'tcx>(
impl<'tcx> InferCtxt<'tcx> {
/// Do *NOT* call this directly. You probably want to construct a `OutlivesEnvironment`
/// instead if you're interested in the implied bounds for a given signature.
fn implied_bounds_tys_with_compat<Tys: IntoIterator<Item = Ty<'tcx>>>(
fn implied_bounds_tys<Tys: IntoIterator<Item = Ty<'tcx>>>(
&self,
body_id: LocalDefId,
param_env: ParamEnv<'tcx>,
tys: Tys,
compat: bool,
disable_implied_bounds_hack: bool,
) -> impl Iterator<Item = OutlivesBound<'tcx>> {
tys.into_iter()
.flat_map(move |ty| implied_outlives_bounds(self, param_env, body_id, ty, compat))
tys.into_iter().flat_map(move |ty| {
implied_outlives_bounds(self, param_env, body_id, ty, disable_implied_bounds_hack)
})
}
}

View file

@ -1232,8 +1232,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
// why we special case object types.
false
}
ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
// These traits have no associated types.
selcx.tcx().dcx().span_delayed_bug(
obligation.cause.span,
@ -1325,8 +1324,7 @@ fn confirm_select_candidate<'cx, 'tcx>(
}
ImplSource::Builtin(BuiltinImplSource::Object { .. }, _)
| ImplSource::Param(..)
| ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
| ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => {
// we don't create Select candidates with this kind of resolution
span_bug!(
obligation.cause.span,

View file

@ -1,15 +1,16 @@
use std::ops::ControlFlow;
use rustc_infer::infer::RegionObligation;
use rustc_infer::infer::canonical::CanonicalQueryInput;
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
use rustc_infer::traits::query::OutlivesBound;
use rustc_infer::traits::query::type_op::ImpliedOutlivesBounds;
use rustc_middle::infer::canonical::CanonicalQueryResponse;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeFolder, TypeVisitableExt};
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitable, TypeVisitor};
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::{DUMMY_SP, Span};
use rustc_span::{DUMMY_SP, Span, sym};
use rustc_type_ir::outlives::{Component, push_outlives_components};
use smallvec::{SmallVec, smallvec};
use tracing::debug;
use crate::traits::query::NoSolution;
use crate::traits::{ObligationCtxt, wf};
@ -35,11 +36,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
tcx: TyCtxt<'tcx>,
canonicalized: CanonicalQueryInput<'tcx, ParamEnvAnd<'tcx, Self>>,
) -> Result<CanonicalQueryResponse<'tcx, Self::QueryResponse>, NoSolution> {
if tcx.sess.opts.unstable_opts.no_implied_bounds_compat {
tcx.implied_outlives_bounds(canonicalized)
} else {
tcx.implied_outlives_bounds_compat(canonicalized)
}
tcx.implied_outlives_bounds((canonicalized, false))
}
fn perform_locally_with_next_solver(
@ -47,11 +44,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
key: ParamEnvAnd<'tcx, Self>,
span: Span,
) -> Result<Self::QueryResponse, NoSolution> {
if ocx.infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat {
compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty, span)
} else {
compute_implied_outlives_bounds_compat_inner(ocx, key.param_env, key.value.ty, span)
}
compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty, span, false)
}
}
@ -60,18 +53,15 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
span: Span,
disable_implied_bounds_hack: bool,
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
let normalize_op = |ty| -> Result<_, NoSolution> {
let normalize_ty = |ty| -> Result<_, NoSolution> {
// We must normalize the type so we can compute the right outlives components.
// for example, if we have some constrained param type like `T: Trait<Out = U>`,
// and we know that `&'a T::Out` is WF, then we want to imply `U: 'a`.
let ty = ocx
.deeply_normalize(&ObligationCause::dummy_with_span(span), param_env, ty)
.map_err(|_| NoSolution)?;
if !ocx.select_all_or_error().is_empty() {
return Err(NoSolution);
}
let ty = OpportunisticRegionResolver::new(&ocx.infcx).fold_ty(ty);
Ok(ty)
};
@ -81,7 +71,7 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
// guaranteed to be a subset of the original type, so we need to store the
// WF args we've computed in a set.
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
let mut wf_args = vec![ty.into(), normalize_op(ty)?.into()];
let mut wf_args = vec![ty.into(), normalize_ty(ty)?.into()];
let mut outlives_bounds: Vec<OutlivesBound<'tcx>> = vec![];
@ -96,8 +86,14 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
.into_iter()
.flatten()
{
assert!(!obligation.has_escaping_bound_vars());
let Some(pred) = obligation.predicate.kind().no_bound_vars() else {
let pred = ocx
.deeply_normalize(
&ObligationCause::dummy_with_span(span),
param_env,
obligation.predicate,
)
.map_err(|_| NoSolution)?;
let Some(pred) = pred.kind().no_bound_vars() else {
continue;
};
match pred {
@ -130,7 +126,6 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
ty_a,
r_b,
))) => {
let ty_a = normalize_op(ty_a)?;
let mut components = smallvec![];
push_outlives_components(ocx.infcx.tcx, ty_a, &mut components);
outlives_bounds.extend(implied_bounds_from_components(r_b, components))
@ -139,141 +134,48 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
}
}
// If we detect `bevy_ecs::*::ParamSet` in the WF args list (and `disable_implied_bounds_hack`
// or `-Zno-implied-bounds-compat` are not set), then use the registered outlives obligations
// as implied bounds.
if !disable_implied_bounds_hack
&& !ocx.infcx.tcx.sess.opts.unstable_opts.no_implied_bounds_compat
&& ty.visit_with(&mut ContainsBevyParamSet { tcx: ocx.infcx.tcx }).is_break()
{
for RegionObligation { sup_type, sub_region, .. } in
ocx.infcx.take_registered_region_obligations()
{
let mut components = smallvec![];
push_outlives_components(ocx.infcx.tcx, sup_type, &mut components);
outlives_bounds.extend(implied_bounds_from_components(sub_region, components));
}
}
Ok(outlives_bounds)
}
pub fn compute_implied_outlives_bounds_compat_inner<'tcx>(
ocx: &ObligationCtxt<'_, 'tcx>,
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
span: Span,
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
let tcx = ocx.infcx.tcx;
struct ContainsBevyParamSet<'tcx> {
tcx: TyCtxt<'tcx>,
}
// Sometimes when we ask what it takes for T: WF, we get back that
// U: WF is required; in that case, we push U onto this stack and
// process it next. Because the resulting predicates aren't always
// guaranteed to be a subset of the original type, so we need to store the
// WF args we've computed in a set.
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
let mut wf_args = vec![ty.into()];
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsBevyParamSet<'tcx> {
type Result = ControlFlow<()>;
let mut outlives_bounds: Vec<ty::OutlivesPredicate<'tcx, ty::GenericArg<'tcx>>> = vec![];
while let Some(arg) = wf_args.pop() {
if !checked_wf_args.insert(arg) {
continue;
}
// Compute the obligations for `arg` to be well-formed. If `arg` is
// an unresolved inference variable, just instantiated an empty set
// -- because the return type here is going to be things we *add*
// to the environment, it's always ok for this set to be smaller
// than the ultimate set. (Note: normally there won't be
// unresolved inference variables here anyway, but there might be
// during typeck under some circumstances.)
//
// FIXME(@lcnr): It's not really "always fine", having fewer implied
// bounds can be backward incompatible, e.g. #101951 was caused by
// us not dealing with inference vars in `TypeOutlives` predicates.
let obligations =
wf::obligations(ocx.infcx, param_env, CRATE_DEF_ID, 0, arg, span).unwrap_or_default();
for obligation in obligations {
debug!(?obligation);
assert!(!obligation.has_escaping_bound_vars());
// While these predicates should all be implied by other parts of
// the program, they are still relevant as they may constrain
// inference variables, which is necessary to add the correct
// implied bounds in some cases, mostly when dealing with projections.
//
// Another important point here: we only register `Projection`
// predicates, since otherwise we might register outlives
// predicates containing inference variables, and we don't
// learn anything new from those.
if obligation.predicate.has_non_region_infer() {
match obligation.predicate.kind().skip_binder() {
ty::PredicateKind::Clause(ty::ClauseKind::Projection(..))
| ty::PredicateKind::AliasRelate(..) => {
ocx.register_obligation(obligation.clone());
}
_ => {}
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
// We only care to match `ParamSet<T>` or `&ParamSet<T>`.
match t.kind() {
ty::Adt(def, _) => {
if self.tcx.item_name(def.did()) == sym::ParamSet
&& self.tcx.crate_name(def.did().krate) == sym::bevy_ecs
{
return ControlFlow::Break(());
}
}
let pred = match obligation.predicate.kind().no_bound_vars() {
None => continue,
Some(pred) => pred,
};
match pred {
// FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound
// if we ever support that
ty::PredicateKind::Clause(ty::ClauseKind::Trait(..))
| ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(..))
| ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..))
| ty::PredicateKind::Subtype(..)
| ty::PredicateKind::Coerce(..)
| ty::PredicateKind::Clause(ty::ClauseKind::Projection(..))
| ty::PredicateKind::DynCompatible(..)
| ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..))
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::Ambiguous
| ty::PredicateKind::NormalizesTo(..)
| ty::PredicateKind::AliasRelate(..) => {}
// We need to search through *all* WellFormed predicates
ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => {
wf_args.push(arg);
}
// We need to register region relationships
ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(
ty::OutlivesPredicate(r_a, r_b),
)) => outlives_bounds.push(ty::OutlivesPredicate(r_a.into(), r_b)),
ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(
ty_a,
r_b,
))) => outlives_bounds.push(ty::OutlivesPredicate(ty_a.into(), r_b)),
}
ty::Ref(_, ty, _) => ty.visit_with(self)?,
_ => {}
}
}
// This call to `select_all_or_error` is necessary to constrain inference variables, which we
// use further down when computing the implied bounds.
match ocx.select_all_or_error().as_slice() {
[] => (),
_ => return Err(NoSolution),
ControlFlow::Continue(())
}
// We lazily compute the outlives components as
// `select_all_or_error` constrains inference variables.
let mut implied_bounds = Vec::new();
for ty::OutlivesPredicate(a, r_b) in outlives_bounds {
match a.unpack() {
ty::GenericArgKind::Lifetime(r_a) => {
implied_bounds.push(OutlivesBound::RegionSubRegion(r_b, r_a))
}
ty::GenericArgKind::Type(ty_a) => {
let mut ty_a = ocx.infcx.resolve_vars_if_possible(ty_a);
// Need to manually normalize in the new solver as `wf::obligations` does not.
if ocx.infcx.next_trait_solver() {
ty_a = ocx
.deeply_normalize(&ObligationCause::dummy_with_span(span), param_env, ty_a)
.map_err(|_| NoSolution)?;
}
let mut components = smallvec![];
push_outlives_components(tcx, ty_a, &mut components);
implied_bounds.extend(implied_bounds_from_components(r_b, components))
}
ty::GenericArgKind::Const(_) => {
unreachable!("consts do not participate in outlives bounds")
}
}
}
Ok(implied_bounds)
}
/// When we have an implied bound that `T: 'a`, we can further break

View file

@ -1017,13 +1017,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
}
}
// `(.., T)` -> `(.., U)`
(&ty::Tuple(tys_a), &ty::Tuple(tys_b)) => {
if tys_a.len() == tys_b.len() {
candidates.vec.push(BuiltinUnsizeCandidate);
}
}
_ => {}
};
}

View file

@ -7,7 +7,6 @@
//! [rustc dev guide]:
//! https://rustc-dev-guide.rust-lang.org/traits/resolution.html#confirmation
use std::iter;
use std::ops::ControlFlow;
use rustc_ast::Mutability;
@ -1315,34 +1314,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
ImplSource::Builtin(BuiltinImplSource::Misc, nested)
}
// `(.., T)` -> `(.., U)`
(&ty::Tuple(tys_a), &ty::Tuple(tys_b)) => {
assert_eq!(tys_a.len(), tys_b.len());
// The last field of the tuple has to exist.
let (&a_last, a_mid) = tys_a.split_last().ok_or(Unimplemented)?;
let &b_last = tys_b.last().unwrap();
// Check that the source tuple with the target's
// last element is equal to the target.
let new_tuple =
Ty::new_tup_from_iter(tcx, a_mid.iter().copied().chain(iter::once(b_last)));
let InferOk { mut obligations, .. } = self
.infcx
.at(&obligation.cause, obligation.param_env)
.eq(DefineOpaqueTypes::Yes, target, new_tuple)
.map_err(|_| Unimplemented)?;
// Add a nested `T: Unsize<U>` predicate.
let last_unsize_obligation = obligation.with(
tcx,
ty::TraitRef::new(tcx, obligation.predicate.def_id(), [a_last, b_last]),
);
obligations.push(last_unsize_obligation);
ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, obligations)
}
_ => bug!("source: {source}, target: {target}"),
})
}

View file

@ -10,38 +10,28 @@ use rustc_middle::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_span::DUMMY_SP;
use rustc_trait_selection::infer::InferCtxtBuilderExt;
use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::{
compute_implied_outlives_bounds_compat_inner, compute_implied_outlives_bounds_inner,
};
use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::compute_implied_outlives_bounds_inner;
use rustc_trait_selection::traits::query::{CanonicalImpliedOutlivesBoundsGoal, NoSolution};
pub(crate) fn provide(p: &mut Providers) {
*p = Providers { implied_outlives_bounds_compat, ..*p };
*p = Providers { implied_outlives_bounds, ..*p };
}
fn implied_outlives_bounds_compat<'tcx>(
tcx: TyCtxt<'tcx>,
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>,
) -> Result<
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
NoSolution,
> {
tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| {
let (param_env, ImpliedOutlivesBounds { ty }) = key.into_parts();
compute_implied_outlives_bounds_compat_inner(ocx, param_env, ty, DUMMY_SP)
})
}
fn implied_outlives_bounds<'tcx>(
tcx: TyCtxt<'tcx>,
goal: CanonicalImpliedOutlivesBoundsGoal<'tcx>,
(goal, disable_implied_bounds_hack): (CanonicalImpliedOutlivesBoundsGoal<'tcx>, bool),
) -> Result<
&'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec<OutlivesBound<'tcx>>>>,
NoSolution,
> {
tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| {
let (param_env, ImpliedOutlivesBounds { ty }) = key.into_parts();
compute_implied_outlives_bounds_inner(ocx, param_env, ty, DUMMY_SP)
compute_implied_outlives_bounds_inner(
ocx,
param_env,
ty,
DUMMY_SP,
disable_implied_bounds_hack,
)
})
}

View file

@ -436,10 +436,7 @@ fn fn_abi_sanity_check<'tcx>(
) {
let tcx = cx.tcx();
if spec_abi == ExternAbi::Rust
|| spec_abi == ExternAbi::RustCall
|| spec_abi == ExternAbi::RustCold
{
if spec_abi.is_rustic_abi() {
if arg.layout.is_zst() {
// Casting closures to function pointers depends on ZST closure types being
// omitted entirely in the calling convention.
@ -687,7 +684,7 @@ fn fn_abi_adjust_for_abi<'tcx>(
let tcx = cx.tcx();
if abi == ExternAbi::Rust || abi == ExternAbi::RustCall || abi == ExternAbi::RustIntrinsic {
if abi.is_rustic_abi() {
fn_abi.adjust_for_rust_abi(cx, abi);
// Look up the deduced parameter attributes for this function, if we have its def ID and

View file

@ -381,8 +381,7 @@ fn resolve_associated_item<'tcx>(
}
}
traits::ImplSource::Param(..)
| traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
| traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => None,
| traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) => None,
})
}

View file

@ -189,11 +189,6 @@ pub enum BuiltinImplSource {
///
/// The index is only used for winnowing.
TraitUpcasting(usize),
/// Unsizing a tuple like `(A, B, ..., X)` to `(A, B, ..., Y)` if `X` unsizes to `Y`.
///
/// This can be removed when `feature(tuple_unsizing)` is stabilized, since we only
/// use it to detect when unsizing tuples in hir typeck.
TupleUnsizing,
}
#[derive_where(Clone, Copy, Hash, PartialEq, Eq, Debug; I: Interner)]

View file

@ -189,6 +189,17 @@
# The default stage to use for the `bench` subcommand
#bench-stage = 2
# A descriptive string to be appended to version output (e.g., `rustc --version`),
# which is also used in places like debuginfo `DW_AT_producer`. This may be useful for
# supplementary build information, like distro-specific package versions.
#
# The Rust compiler will differentiate between versions of itself, including
# based on this string, which means that if you wish to be compatible with
# upstream Rust you need to set this to "". However, note that if you set this to "" but
# are not actually compatible -- for example if you've backported patches that change
# behavior -- this may lead to miscompilations or other bugs.
#description = ""
# Build triple for the pre-compiled snapshot compiler. If `rustc` is set, this must match its host
# triple (see `rustc --version --verbose`; cross-compiling the rust build system itself is NOT
# supported). If `rustc` is unset, this must be a platform with pre-compiled host tools
@ -615,17 +626,6 @@
# If using tarball sources, default value is "auto-detect", otherwise, it's "dev".
#channel = if "is a tarball source" { "auto-detect" } else { "dev" }
# A descriptive string to be appended to `rustc --version` output, which is
# also used in places like debuginfo `DW_AT_producer`. This may be useful for
# supplementary build information, like distro-specific package versions.
#
# The Rust compiler will differentiate between versions of itself, including
# based on this string, which means that if you wish to be compatible with
# upstream Rust you need to set this to "". However, note that if you are not
# actually compatible -- for example if you've backported patches that change
# behavior -- this may lead to miscompilations or other bugs.
#description = ""
# The root location of the musl installation directory. The library directory
# will also need to contain libunwind.a for an unwinding implementation. Note
# that this option only makes sense for musl targets that produce statically

View file

@ -151,9 +151,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.169"
version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
dependencies = [
"rustc-std-workspace-core",
]

View file

@ -1134,7 +1134,6 @@ impl String {
/// # Examples
///
/// ```
/// #![feature(string_extend_from_within)]
/// let mut string = String::from("abcde");
///
/// string.extend_from_within(2..);
@ -1147,7 +1146,7 @@ impl String {
/// assert_eq!(string, "abcdecdeabecde");
/// ```
#[cfg(not(no_global_oom_handling))]
#[unstable(feature = "string_extend_from_within", issue = "103806")]
#[stable(feature = "string_extend_from_within", since = "CURRENT_RUSTC_VERSION")]
pub fn extend_from_within<R>(&mut self, src: R)
where
R: RangeBounds<usize>,

View file

@ -1693,49 +1693,41 @@ impl<'a> Formatter<'a> {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn pad(&mut self, s: &str) -> Result {
// Make sure there's a fast path up front
// Make sure there's a fast path up front.
if self.options.width.is_none() && self.options.precision.is_none() {
return self.buf.write_str(s);
}
// The `precision` field can be interpreted as a `max-width` for the
// The `precision` field can be interpreted as a maximum width for the
// string being formatted.
let s = if let Some(max) = self.options.precision {
// If our string is longer that the precision, then we must have
// truncation. However other flags like `fill`, `width` and `align`
// must act as always.
if let Some((i, _)) = s.char_indices().nth(max) {
// LLVM here can't prove that `..i` won't panic `&s[..i]`, but
// we know that it can't panic. Use `get` + `unwrap_or` to avoid
// `unsafe` and otherwise don't emit any panic-related code
// here.
s.get(..i).unwrap_or(s)
} else {
&s
}
let (s, char_count) = if let Some(max_char_count) = self.options.precision {
let mut iter = s.char_indices();
let remaining = match iter.advance_by(max_char_count) {
Ok(()) => 0,
Err(remaining) => remaining.get(),
};
// SAFETY: The offset of `.char_indices()` is guaranteed to be
// in-bounds and between character boundaries.
let truncated = unsafe { s.get_unchecked(..iter.offset()) };
(truncated, max_char_count - remaining)
} else {
&s
// Use the optimized char counting algorithm for the full string.
(s, s.chars().count())
};
// The `width` field is more of a `min-width` parameter at this point.
match self.options.width {
// If we're under the maximum length, and there's no minimum length
// requirements, then we can just emit the string
None => self.buf.write_str(s),
Some(width) => {
let chars_count = s.chars().count();
// If we're under the maximum width, check if we're over the minimum
// width, if so it's as easy as just emitting the string.
if chars_count >= width {
self.buf.write_str(s)
}
// If we're under both the maximum and the minimum width, then fill
// up the minimum width with the specified string + some alignment.
else {
let align = Alignment::Left;
let post_padding = self.padding(width - chars_count, align)?;
self.buf.write_str(s)?;
post_padding.write(self)
}
}
// The `width` field is more of a minimum width parameter at this point.
if let Some(width) = self.options.width
&& char_count < width
{
// If we're under the minimum width, then fill up the minimum width
// with the specified string + some alignment.
let post_padding = self.padding(width - char_count, Alignment::Left)?;
self.buf.write_str(s)?;
post_padding.write(self)
} else {
// If we're over the minimum width or there is no minimum width, we
// can just emit the string.
self.buf.write_str(s)
}
}

View file

@ -8,12 +8,12 @@ pub(crate) trait ByteSlice {
/// Writes a 64-bit integer as 8 bytes in little-endian order.
fn write_u64(&mut self, value: u64);
/// Calculate the offset of a slice from another.
/// Calculate the difference in length between two slices.
fn offset_from(&self, other: &Self) -> isize;
/// Iteratively parse and consume digits from bytes.
/// Returns the same bytes with consumed digits being
/// elided.
///
/// Returns the same bytes with consumed digits being elided. Breaks on invalid digits.
fn parse_digits(&self, func: impl FnMut(u8)) -> &Self;
}
@ -39,11 +39,11 @@ impl ByteSlice for [u8] {
fn parse_digits(&self, mut func: impl FnMut(u8)) -> &Self {
let mut s = self;
while let Some((c, s_next)) = s.split_first() {
while let Some((c, rest)) = s.split_first() {
let c = c.wrapping_sub(b'0');
if c < 10 {
func(c);
s = s_next;
s = rest;
} else {
break;
}
@ -53,7 +53,9 @@ impl ByteSlice for [u8] {
}
}
/// Determine if 8 bytes are all decimal digits.
/// Determine if all characters in an 8-byte byte string (represented as a `u64`) are all decimal
/// digits.
///
/// This does not care about the order in which the bytes were loaded.
pub(crate) fn is_8digits(v: u64) -> bool {
let a = v.wrapping_add(0x4646_4646_4646_4646);
@ -61,19 +63,20 @@ pub(crate) fn is_8digits(v: u64) -> bool {
(a | b) & 0x8080_8080_8080_8080 == 0
}
/// A custom 64-bit floating point type, representing `f * 2^e`.
/// e is biased, so it be directly shifted into the exponent bits.
/// A custom 64-bit floating point type, representing `m * 2^p`.
/// p is biased, so it be directly shifted into the exponent bits.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub struct BiasedFp {
/// The significant digits.
pub f: u64,
pub m: u64,
/// The biased, binary exponent.
pub e: i32,
pub p_biased: i32,
}
impl BiasedFp {
/// Represent `0 ^ p`
#[inline]
pub const fn zero_pow2(e: i32) -> Self {
Self { f: 0, e }
pub const fn zero_pow2(p_biased: i32) -> Self {
Self { m: 0, p_biased }
}
}

View file

@ -1,358 +1,87 @@
//! Arbitrary-precision decimal class for fallback algorithms.
//!
//! This is only used if the fast-path (native floats) and
//! the Eisel-Lemire algorithm are unable to unambiguously
//! determine the float.
//!
//! The technique used is "Simple Decimal Conversion", developed
//! by Nigel Tao and Ken Thompson. A detailed description of the
//! algorithm can be found in "ParseNumberF64 by Simple Decimal Conversion",
//! available online: <https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html>.
//! Representation of a float as the significant digits and exponent.
use crate::num::dec2flt::common::{ByteSlice, is_8digits};
use crate::num::dec2flt::float::RawFloat;
use crate::num::dec2flt::fpu::set_precision;
#[derive(Clone)]
pub(super) struct Decimal {
/// The number of significant digits in the decimal.
pub num_digits: usize,
/// The offset of the decimal point in the significant digits.
pub decimal_point: i32,
/// If the number of significant digits stored in the decimal is truncated.
pub truncated: bool,
/// Buffer of the raw digits, in the range [0, 9].
pub digits: [u8; Self::MAX_DIGITS],
}
const INT_POW10: [u64; 16] = [
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000,
100000000000,
1000000000000,
10000000000000,
100000000000000,
1000000000000000,
];
impl Default for Decimal {
fn default() -> Self {
Self { num_digits: 0, decimal_point: 0, truncated: false, digits: [0; Self::MAX_DIGITS] }
}
/// A floating point number with up to 64 bits of mantissa and an `i64` exponent.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Decimal {
pub exponent: i64,
pub mantissa: u64,
pub negative: bool,
pub many_digits: bool,
}
impl Decimal {
/// The maximum number of digits required to unambiguously round a float.
///
/// For a double-precision IEEE 754 float, this required 767 digits,
/// so we store the max digits + 1.
///
/// We can exactly represent a float in radix `b` from radix 2 if
/// `b` is divisible by 2. This function calculates the exact number of
/// digits required to exactly represent that float.
///
/// According to the "Handbook of Floating Point Arithmetic",
/// for IEEE754, with emin being the min exponent, p2 being the
/// precision, and b being the radix, the number of digits follows as:
///
/// `emin + p2 + ⌊(emin + 1) log(2, b) log(1 2^(p2), b)⌋`
///
/// For f32, this follows as:
/// emin = -126
/// p2 = 24
///
/// For f64, this follows as:
/// emin = -1022
/// p2 = 53
///
/// In Python:
/// `-emin + p2 + math.floor((emin+ 1)*math.log(2, b)-math.log(1-2**(-p2), b))`
pub(super) const MAX_DIGITS: usize = 768;
/// The max digits that can be exactly represented in a 64-bit integer.
pub(super) const MAX_DIGITS_WITHOUT_OVERFLOW: usize = 19;
pub(super) const DECIMAL_POINT_RANGE: i32 = 2047;
/// Append a digit to the buffer.
pub(super) fn try_add_digit(&mut self, digit: u8) {
if self.num_digits < Self::MAX_DIGITS {
self.digits[self.num_digits] = digit;
}
self.num_digits += 1;
/// Detect if the float can be accurately reconstructed from native floats.
#[inline]
fn can_use_fast_path<F: RawFloat>(&self) -> bool {
F::MIN_EXPONENT_FAST_PATH <= self.exponent
&& self.exponent <= F::MAX_EXPONENT_DISGUISED_FAST_PATH
&& self.mantissa <= F::MAX_MANTISSA_FAST_PATH
&& !self.many_digits
}
/// Trim trailing zeros from the buffer.
pub(super) fn trim(&mut self) {
// All of the following calls to `Decimal::trim` can't panic because:
//
// 1. `parse_decimal` sets `num_digits` to a max of `Decimal::MAX_DIGITS`.
// 2. `right_shift` sets `num_digits` to `write_index`, which is bounded by `num_digits`.
// 3. `left_shift` `num_digits` to a max of `Decimal::MAX_DIGITS`.
//
// Trim is only called in `right_shift` and `left_shift`.
debug_assert!(self.num_digits <= Self::MAX_DIGITS);
while self.num_digits != 0 && self.digits[self.num_digits - 1] == 0 {
self.num_digits -= 1;
}
}
/// Try turning the decimal into an exact float representation, using machine-sized integers
/// and floats.
///
/// This is extracted into a separate function so that it can be attempted before constructing
/// a Decimal. This only works if both the mantissa and the exponent
/// can be exactly represented as a machine float, since IEE-754 guarantees
/// no rounding will occur.
///
/// There is an exception: disguised fast-path cases, where we can shift
/// powers-of-10 from the exponent to the significant digits.
pub fn try_fast_path<F: RawFloat>(&self) -> Option<F> {
// Here we need to work around <https://github.com/rust-lang/rust/issues/114479>.
// The fast path crucially depends on arithmetic being rounded to the correct number of bits
// without any intermediate rounding. On x86 (without SSE or SSE2) this requires the precision
// of the x87 FPU stack to be changed so that it directly rounds to 64/32 bit.
// The `set_precision` function takes care of setting the precision on architectures which
// require setting it by changing the global state (like the control word of the x87 FPU).
let _cw = set_precision::<F>();
pub(super) fn round(&self) -> u64 {
if self.num_digits == 0 || self.decimal_point < 0 {
return 0;
} else if self.decimal_point > 18 {
return 0xFFFF_FFFF_FFFF_FFFF_u64;
if !self.can_use_fast_path::<F>() {
return None;
}
let dp = self.decimal_point as usize;
let mut n = 0_u64;
for i in 0..dp {
n *= 10;
if i < self.num_digits {
n += self.digits[i] as u64;
}
}
let mut round_up = false;
if dp < self.num_digits {
round_up = self.digits[dp] >= 5;
if self.digits[dp] == 5 && dp + 1 == self.num_digits {
round_up = self.truncated || ((dp != 0) && (1 & self.digits[dp - 1] != 0))
}
}
if round_up {
n += 1;
}
n
}
/// Computes decimal * 2^shift.
pub(super) fn left_shift(&mut self, shift: usize) {
if self.num_digits == 0 {
return;
}
let num_new_digits = number_of_digits_decimal_left_shift(self, shift);
let mut read_index = self.num_digits;
let mut write_index = self.num_digits + num_new_digits;
let mut n = 0_u64;
while read_index != 0 {
read_index -= 1;
write_index -= 1;
n += (self.digits[read_index] as u64) << shift;
let quotient = n / 10;
let remainder = n - (10 * quotient);
if write_index < Self::MAX_DIGITS {
self.digits[write_index] = remainder as u8;
} else if remainder > 0 {
self.truncated = true;
}
n = quotient;
}
while n > 0 {
write_index -= 1;
let quotient = n / 10;
let remainder = n - (10 * quotient);
if write_index < Self::MAX_DIGITS {
self.digits[write_index] = remainder as u8;
} else if remainder > 0 {
self.truncated = true;
}
n = quotient;
}
self.num_digits += num_new_digits;
if self.num_digits > Self::MAX_DIGITS {
self.num_digits = Self::MAX_DIGITS;
}
self.decimal_point += num_new_digits as i32;
self.trim();
}
/// Computes decimal * 2^-shift.
pub(super) fn right_shift(&mut self, shift: usize) {
let mut read_index = 0;
let mut write_index = 0;
let mut n = 0_u64;
while (n >> shift) == 0 {
if read_index < self.num_digits {
n = (10 * n) + self.digits[read_index] as u64;
read_index += 1;
} else if n == 0 {
return;
let value = if self.exponent <= F::MAX_EXPONENT_FAST_PATH {
// normal fast path
let value = F::from_u64(self.mantissa);
if self.exponent < 0 {
value / F::pow10_fast_path((-self.exponent) as _)
} else {
while (n >> shift) == 0 {
n *= 10;
read_index += 1;
}
break;
value * F::pow10_fast_path(self.exponent as _)
}
}
self.decimal_point -= read_index as i32 - 1;
if self.decimal_point < -Self::DECIMAL_POINT_RANGE {
// `self = Self::Default()`, but without the overhead of clearing `digits`.
self.num_digits = 0;
self.decimal_point = 0;
self.truncated = false;
return;
}
let mask = (1_u64 << shift) - 1;
while read_index < self.num_digits {
let new_digit = (n >> shift) as u8;
n = (10 * (n & mask)) + self.digits[read_index] as u64;
read_index += 1;
self.digits[write_index] = new_digit;
write_index += 1;
}
while n > 0 {
let new_digit = (n >> shift) as u8;
n = 10 * (n & mask);
if write_index < Self::MAX_DIGITS {
self.digits[write_index] = new_digit;
write_index += 1;
} else if new_digit > 0 {
self.truncated = true;
}
}
self.num_digits = write_index;
self.trim();
}
}
/// Parse a big integer representation of the float as a decimal.
pub(super) fn parse_decimal(mut s: &[u8]) -> Decimal {
let mut d = Decimal::default();
let start = s;
while let Some((&b'0', s_next)) = s.split_first() {
s = s_next;
}
s = s.parse_digits(|digit| d.try_add_digit(digit));
if let Some((b'.', s_next)) = s.split_first() {
s = s_next;
let first = s;
// Skip leading zeros.
if d.num_digits == 0 {
while let Some((&b'0', s_next)) = s.split_first() {
s = s_next;
}
}
while s.len() >= 8 && d.num_digits + 8 < Decimal::MAX_DIGITS {
let v = s.read_u64();
if !is_8digits(v) {
break;
}
d.digits[d.num_digits..].write_u64(v - 0x3030_3030_3030_3030);
d.num_digits += 8;
s = &s[8..];
}
s = s.parse_digits(|digit| d.try_add_digit(digit));
d.decimal_point = s.len() as i32 - first.len() as i32;
}
if d.num_digits != 0 {
// Ignore the trailing zeros if there are any
let mut n_trailing_zeros = 0;
for &c in start[..(start.len() - s.len())].iter().rev() {
if c == b'0' {
n_trailing_zeros += 1;
} else if c != b'.' {
break;
}
}
d.decimal_point += n_trailing_zeros as i32;
d.num_digits -= n_trailing_zeros;
d.decimal_point += d.num_digits as i32;
if d.num_digits > Decimal::MAX_DIGITS {
d.truncated = true;
d.num_digits = Decimal::MAX_DIGITS;
}
}
if let Some((&ch, s_next)) = s.split_first() {
if ch == b'e' || ch == b'E' {
s = s_next;
let mut neg_exp = false;
if let Some((&ch, s_next)) = s.split_first() {
neg_exp = ch == b'-';
if ch == b'-' || ch == b'+' {
s = s_next;
}
}
let mut exp_num = 0_i32;
s.parse_digits(|digit| {
if exp_num < 0x10000 {
exp_num = 10 * exp_num + digit as i32;
}
});
d.decimal_point += if neg_exp { -exp_num } else { exp_num };
}
}
for i in d.num_digits..Decimal::MAX_DIGITS_WITHOUT_OVERFLOW {
d.digits[i] = 0;
}
d
}
fn number_of_digits_decimal_left_shift(d: &Decimal, mut shift: usize) -> usize {
#[rustfmt::skip]
const TABLE: [u16; 65] = [
0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, 0x181D, 0x2024,
0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, 0x3073, 0x3080, 0x388E, 0x389C,
0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169,
0x5180, 0x5998, 0x59B0, 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B,
0x72AA, 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, 0x8C02,
0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, 0x051C, 0x051C,
];
#[rustfmt::skip]
const TABLE_POW5: [u8; 0x051C] = [
5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, 9, 0, 6, 2, 5, 1,
9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5,
1, 2, 2, 0, 7, 0, 3, 1, 2, 5, 6, 1, 0, 3, 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2,
5, 1, 5, 2, 5, 8, 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, 3, 8, 1, 4, 6,
9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, 8, 1, 2, 5, 9, 5, 3, 6, 7, 4, 3, 1,
6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, 7, 1, 5, 8, 2, 0, 3, 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9,
1, 0, 1, 5, 6, 2, 5, 1, 1, 9, 2, 0, 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, 0, 4, 6,
4, 4, 7, 7, 5, 3, 9, 0, 6, 2, 5, 2, 9, 8, 0, 2, 3, 2, 2, 3, 8, 7, 6, 9, 5, 3, 1, 2, 5, 1,
4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, 0, 5, 9, 6, 9, 2,
3, 8, 2, 8, 1, 2, 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, 6,
2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, 2, 2, 5, 7, 4, 6, 1, 5,
4, 7, 8, 5, 1, 5, 6, 2, 5, 4, 6, 5, 6, 6, 1, 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2,
5, 2, 3, 2, 8, 3, 0, 6, 4, 3, 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, 1, 1, 6, 4, 1, 5,
3, 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, 5, 5, 8, 2, 0, 7, 6, 6, 0, 9, 1, 3, 4,
6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, 2, 9, 1, 0, 3, 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6,
1, 3, 2, 8, 1, 2, 5, 1, 4, 5, 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0,
6, 2, 5, 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, 0, 3, 1, 2, 5, 3,
6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, 6, 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8,
9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4,
7, 0, 1, 7, 7, 2, 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, 3, 5,
0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, 2, 2, 7, 3, 7, 3, 6, 7, 5,
4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, 9, 7, 6, 5, 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7,
7, 2, 1, 6, 1, 6, 0, 2, 9, 7, 3, 9, 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8,
8, 6, 0, 8, 0, 8, 0, 1, 4, 8, 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, 2, 8, 4, 2, 1, 7, 0,
9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, 9, 7, 0, 7, 0, 3, 1, 2, 5, 1, 4, 2, 1, 0,
8, 5, 4, 7, 1, 5, 2, 0, 2, 0, 0, 3, 7, 1, 7, 4, 2, 2, 4, 8, 5, 3, 5, 1, 5, 6, 2, 5, 7, 1,
0, 5, 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, 5, 7, 8, 1, 2,
5, 3, 5, 5, 2, 7, 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8,
9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, 6, 7, 7, 8, 1, 0,
6, 6, 8, 9, 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, 9, 7, 0, 0, 1, 2, 5, 2, 3, 2, 3, 3,
8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, 6, 2, 5, 4, 4, 4, 0, 8, 9, 2, 0, 9, 8, 5, 0, 0, 6, 2,
6, 1, 6, 1, 6, 9, 4, 5, 2, 6, 6, 7, 2, 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4,
9, 2, 5, 0, 3, 1, 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, 0, 6, 2, 5, 1, 1,
1, 0, 2, 2, 3, 0, 2, 4, 6, 2, 5, 1, 5, 6, 5, 4, 0, 4, 2, 3, 6, 3, 1, 6, 6, 8, 0, 9, 0, 8,
2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, 5, 1, 2, 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5,
8, 3, 4, 0, 4, 5, 4, 1, 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, 6, 2, 8, 9, 1,
3, 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, 3, 8, 7, 7, 7, 8,
7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, 9, 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0,
6, 2, 5, 6, 9, 3, 8, 8, 9, 3, 9, 0, 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2,
5, 5, 6, 7, 6, 2, 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, 6, 1, 4, 1,
8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, 6, 5, 6, 2, 5, 1, 7, 3, 4, 7, 2,
3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, 4, 4, 1, 1, 9, 2, 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, 7, 3,
8, 2, 8, 1, 2, 5, 8, 6, 7, 3, 6, 1, 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, 6, 2,
2, 4, 0, 6, 9, 5, 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5,
];
shift &= 63;
let x_a = TABLE[shift];
let x_b = TABLE[shift + 1];
let num_new_digits = (x_a >> 11) as _;
let pow5_a = (0x7FF & x_a) as usize;
let pow5_b = (0x7FF & x_b) as usize;
let pow5 = &TABLE_POW5[pow5_a..];
for (i, &p5) in pow5.iter().enumerate().take(pow5_b - pow5_a) {
if i >= d.num_digits {
return num_new_digits - 1;
} else if d.digits[i] == p5 {
continue;
} else if d.digits[i] < p5 {
return num_new_digits - 1;
} else {
return num_new_digits;
}
// disguised fast path
let shift = self.exponent - F::MAX_EXPONENT_FAST_PATH;
let mantissa = self.mantissa.checked_mul(INT_POW10[shift as usize])?;
if mantissa > F::MAX_MANTISSA_FAST_PATH {
return None;
}
F::from_u64(mantissa) * F::pow10_fast_path(F::MAX_EXPONENT_FAST_PATH as _)
};
if self.negative { Some(-value) } else { Some(value) }
}
num_new_digits
}

View file

@ -0,0 +1,379 @@
//! Arbitrary-precision decimal type used by fallback algorithms.
//!
//! This is only used if the fast-path (native floats) and
//! the Eisel-Lemire algorithm are unable to unambiguously
//! determine the float.
//!
//! The technique used is "Simple Decimal Conversion", developed
//! by Nigel Tao and Ken Thompson. A detailed description of the
//! algorithm can be found in "ParseNumberF64 by Simple Decimal Conversion",
//! available online: <https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html>.
use crate::num::dec2flt::common::{ByteSlice, is_8digits};
/// A decimal floating-point number, represented as a sequence of decimal digits.
#[derive(Clone, Debug, PartialEq)]
pub struct DecimalSeq {
/// The number of significant digits in the decimal.
pub num_digits: usize,
/// The offset of the decimal point in the significant digits.
pub decimal_point: i32,
/// If the number of significant digits stored in the decimal is truncated.
pub truncated: bool,
/// Buffer of the raw digits, in the range [0, 9].
pub digits: [u8; Self::MAX_DIGITS],
}
impl Default for DecimalSeq {
fn default() -> Self {
Self { num_digits: 0, decimal_point: 0, truncated: false, digits: [0; Self::MAX_DIGITS] }
}
}
impl DecimalSeq {
/// The maximum number of digits required to unambiguously round up to a 64-bit float.
///
/// For an IEEE 754 binary64 float, this required 767 digits. So we store the max digits + 1.
///
/// We can exactly represent a float in radix `b` from radix 2 if
/// `b` is divisible by 2. This function calculates the exact number of
/// digits required to exactly represent that float.
///
/// According to the "Handbook of Floating Point Arithmetic",
/// for IEEE754, with `emin` being the min exponent, `p2` being the
/// precision, and `b` being the radix, the number of digits follows as:
///
/// `emin + p2 + ⌊(emin + 1) log(2, b) log(1 2^(p2), b)⌋`
///
/// For f32, this follows as:
/// emin = -126
/// p2 = 24
///
/// For f64, this follows as:
/// emin = -1022
/// p2 = 53
///
/// In Python:
/// `-emin + p2 + math.floor((emin+ 1)*math.log(2, b)-math.log(1-2**(-p2), b))`
pub const MAX_DIGITS: usize = 768;
/// The max decimal digits that can be exactly represented in a 64-bit integer.
pub(super) const MAX_DIGITS_WITHOUT_OVERFLOW: usize = 19;
pub(super) const DECIMAL_POINT_RANGE: i32 = 2047;
/// Append a digit to the buffer if it fits.
// FIXME(tgross35): it may be better for this to return an option
// FIXME(tgross35): incrementing the digit counter even if we don't push anything
// seems incorrect.
pub(super) fn try_add_digit(&mut self, digit: u8) {
if self.num_digits < Self::MAX_DIGITS {
self.digits[self.num_digits] = digit;
}
self.num_digits += 1;
}
/// Trim trailing zeros from the buffer.
// FIXME(tgross35): this could be `.rev().position()` if perf is okay
pub fn trim(&mut self) {
// All of the following calls to `DecimalSeq::trim` can't panic because:
//
// 1. `parse_decimal` sets `num_digits` to a max of `DecimalSeq::MAX_DIGITS`.
// 2. `right_shift` sets `num_digits` to `write_index`, which is bounded by `num_digits`.
// 3. `left_shift` `num_digits` to a max of `DecimalSeq::MAX_DIGITS`.
//
// Trim is only called in `right_shift` and `left_shift`.
debug_assert!(self.num_digits <= Self::MAX_DIGITS);
while self.num_digits != 0 && self.digits[self.num_digits - 1] == 0 {
self.num_digits -= 1;
}
}
pub(super) fn round(&self) -> u64 {
if self.num_digits == 0 || self.decimal_point < 0 {
return 0;
} else if self.decimal_point >= Self::MAX_DIGITS_WITHOUT_OVERFLOW as i32 {
return 0xFFFF_FFFF_FFFF_FFFF_u64;
}
let dp = self.decimal_point as usize;
let mut n = 0_u64;
for i in 0..dp {
n *= 10;
if i < self.num_digits {
n += self.digits[i] as u64;
}
}
let mut round_up = false;
if dp < self.num_digits {
round_up = self.digits[dp] >= 5;
if self.digits[dp] == 5 && dp + 1 == self.num_digits {
round_up = self.truncated || ((dp != 0) && (1 & self.digits[dp - 1] != 0))
}
}
if round_up {
n += 1;
}
n
}
/// Computes decimal * 2^shift.
pub(super) fn left_shift(&mut self, shift: usize) {
if self.num_digits == 0 {
return;
}
let num_new_digits = number_of_digits_decimal_left_shift(self, shift);
let mut read_index = self.num_digits;
let mut write_index = self.num_digits + num_new_digits;
let mut n = 0_u64;
while read_index != 0 {
read_index -= 1;
write_index -= 1;
n += (self.digits[read_index] as u64) << shift;
let quotient = n / 10;
let remainder = n - (10 * quotient);
if write_index < Self::MAX_DIGITS {
self.digits[write_index] = remainder as u8;
} else if remainder > 0 {
self.truncated = true;
}
n = quotient;
}
while n > 0 {
write_index -= 1;
let quotient = n / 10;
let remainder = n - (10 * quotient);
if write_index < Self::MAX_DIGITS {
self.digits[write_index] = remainder as u8;
} else if remainder > 0 {
self.truncated = true;
}
n = quotient;
}
self.num_digits += num_new_digits;
if self.num_digits > Self::MAX_DIGITS {
self.num_digits = Self::MAX_DIGITS;
}
self.decimal_point += num_new_digits as i32;
self.trim();
}
/// Computes decimal * 2^-shift.
pub(super) fn right_shift(&mut self, shift: usize) {
let mut read_index = 0;
let mut write_index = 0;
let mut n = 0_u64;
while (n >> shift) == 0 {
if read_index < self.num_digits {
n = (10 * n) + self.digits[read_index] as u64;
read_index += 1;
} else if n == 0 {
return;
} else {
while (n >> shift) == 0 {
n *= 10;
read_index += 1;
}
break;
}
}
self.decimal_point -= read_index as i32 - 1;
if self.decimal_point < -Self::DECIMAL_POINT_RANGE {
// `self = Self::Default()`, but without the overhead of clearing `digits`.
self.num_digits = 0;
self.decimal_point = 0;
self.truncated = false;
return;
}
let mask = (1_u64 << shift) - 1;
while read_index < self.num_digits {
let new_digit = (n >> shift) as u8;
n = (10 * (n & mask)) + self.digits[read_index] as u64;
read_index += 1;
self.digits[write_index] = new_digit;
write_index += 1;
}
while n > 0 {
let new_digit = (n >> shift) as u8;
n = 10 * (n & mask);
if write_index < Self::MAX_DIGITS {
self.digits[write_index] = new_digit;
write_index += 1;
} else if new_digit > 0 {
self.truncated = true;
}
}
self.num_digits = write_index;
self.trim();
}
}
/// Parse a big integer representation of the float as a decimal.
pub fn parse_decimal_seq(mut s: &[u8]) -> DecimalSeq {
let mut d = DecimalSeq::default();
let start = s;
while let Some((&b'0', s_next)) = s.split_first() {
s = s_next;
}
s = s.parse_digits(|digit| d.try_add_digit(digit));
if let Some((b'.', s_next)) = s.split_first() {
s = s_next;
let first = s;
// Skip leading zeros.
if d.num_digits == 0 {
while let Some((&b'0', s_next)) = s.split_first() {
s = s_next;
}
}
while s.len() >= 8 && d.num_digits + 8 < DecimalSeq::MAX_DIGITS {
let v = s.read_u64();
if !is_8digits(v) {
break;
}
d.digits[d.num_digits..].write_u64(v - 0x3030_3030_3030_3030);
d.num_digits += 8;
s = &s[8..];
}
s = s.parse_digits(|digit| d.try_add_digit(digit));
d.decimal_point = s.len() as i32 - first.len() as i32;
}
if d.num_digits != 0 {
// Ignore the trailing zeros if there are any
let mut n_trailing_zeros = 0;
for &c in start[..(start.len() - s.len())].iter().rev() {
if c == b'0' {
n_trailing_zeros += 1;
} else if c != b'.' {
break;
}
}
d.decimal_point += n_trailing_zeros as i32;
d.num_digits -= n_trailing_zeros;
d.decimal_point += d.num_digits as i32;
if d.num_digits > DecimalSeq::MAX_DIGITS {
d.truncated = true;
d.num_digits = DecimalSeq::MAX_DIGITS;
}
}
if let Some((&ch, s_next)) = s.split_first() {
if ch == b'e' || ch == b'E' {
s = s_next;
let mut neg_exp = false;
if let Some((&ch, s_next)) = s.split_first() {
neg_exp = ch == b'-';
if ch == b'-' || ch == b'+' {
s = s_next;
}
}
let mut exp_num = 0_i32;
s.parse_digits(|digit| {
if exp_num < 0x10000 {
exp_num = 10 * exp_num + digit as i32;
}
});
d.decimal_point += if neg_exp { -exp_num } else { exp_num };
}
}
for i in d.num_digits..DecimalSeq::MAX_DIGITS_WITHOUT_OVERFLOW {
d.digits[i] = 0;
}
d
}
fn number_of_digits_decimal_left_shift(d: &DecimalSeq, mut shift: usize) -> usize {
#[rustfmt::skip]
const TABLE: [u16; 65] = [
0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, 0x181D, 0x2024,
0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, 0x3073, 0x3080, 0x388E, 0x389C,
0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169,
0x5180, 0x5998, 0x59B0, 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B,
0x72AA, 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, 0x8C02,
0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, 0x051C, 0x051C,
];
#[rustfmt::skip]
const TABLE_POW5: [u8; 0x051C] = [
5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, 9, 0, 6, 2, 5, 1,
9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5,
1, 2, 2, 0, 7, 0, 3, 1, 2, 5, 6, 1, 0, 3, 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2,
5, 1, 5, 2, 5, 8, 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, 3, 8, 1, 4, 6,
9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, 8, 1, 2, 5, 9, 5, 3, 6, 7, 4, 3, 1,
6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, 7, 1, 5, 8, 2, 0, 3, 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9,
1, 0, 1, 5, 6, 2, 5, 1, 1, 9, 2, 0, 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, 0, 4, 6,
4, 4, 7, 7, 5, 3, 9, 0, 6, 2, 5, 2, 9, 8, 0, 2, 3, 2, 2, 3, 8, 7, 6, 9, 5, 3, 1, 2, 5, 1,
4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, 0, 5, 9, 6, 9, 2,
3, 8, 2, 8, 1, 2, 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, 6,
2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, 2, 2, 5, 7, 4, 6, 1, 5,
4, 7, 8, 5, 1, 5, 6, 2, 5, 4, 6, 5, 6, 6, 1, 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2,
5, 2, 3, 2, 8, 3, 0, 6, 4, 3, 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, 1, 1, 6, 4, 1, 5,
3, 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, 5, 5, 8, 2, 0, 7, 6, 6, 0, 9, 1, 3, 4,
6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, 2, 9, 1, 0, 3, 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6,
1, 3, 2, 8, 1, 2, 5, 1, 4, 5, 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0,
6, 2, 5, 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, 0, 3, 1, 2, 5, 3,
6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, 6, 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8,
9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4,
7, 0, 1, 7, 7, 2, 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, 3, 5,
0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, 2, 2, 7, 3, 7, 3, 6, 7, 5,
4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, 9, 7, 6, 5, 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7,
7, 2, 1, 6, 1, 6, 0, 2, 9, 7, 3, 9, 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8,
8, 6, 0, 8, 0, 8, 0, 1, 4, 8, 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, 2, 8, 4, 2, 1, 7, 0,
9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, 9, 7, 0, 7, 0, 3, 1, 2, 5, 1, 4, 2, 1, 0,
8, 5, 4, 7, 1, 5, 2, 0, 2, 0, 0, 3, 7, 1, 7, 4, 2, 2, 4, 8, 5, 3, 5, 1, 5, 6, 2, 5, 7, 1,
0, 5, 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, 5, 7, 8, 1, 2,
5, 3, 5, 5, 2, 7, 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8,
9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, 6, 7, 7, 8, 1, 0,
6, 6, 8, 9, 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, 9, 7, 0, 0, 1, 2, 5, 2, 3, 2, 3, 3,
8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, 6, 2, 5, 4, 4, 4, 0, 8, 9, 2, 0, 9, 8, 5, 0, 0, 6, 2,
6, 1, 6, 1, 6, 9, 4, 5, 2, 6, 6, 7, 2, 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4,
9, 2, 5, 0, 3, 1, 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, 0, 6, 2, 5, 1, 1,
1, 0, 2, 2, 3, 0, 2, 4, 6, 2, 5, 1, 5, 6, 5, 4, 0, 4, 2, 3, 6, 3, 1, 6, 6, 8, 0, 9, 0, 8,
2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, 5, 1, 2, 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5,
8, 3, 4, 0, 4, 5, 4, 1, 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, 6, 2, 8, 9, 1,
3, 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, 3, 8, 7, 7, 7, 8,
7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, 9, 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0,
6, 2, 5, 6, 9, 3, 8, 8, 9, 3, 9, 0, 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2,
5, 5, 6, 7, 6, 2, 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, 6, 1, 4, 1,
8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, 6, 5, 6, 2, 5, 1, 7, 3, 4, 7, 2,
3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, 4, 4, 1, 1, 9, 2, 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, 7, 3,
8, 2, 8, 1, 2, 5, 8, 6, 7, 3, 6, 1, 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, 6, 2,
2, 4, 0, 6, 9, 5, 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5,
];
shift &= 63;
let x_a = TABLE[shift];
let x_b = TABLE[shift + 1];
let num_new_digits = (x_a >> 11) as _;
let pow5_a = (0x7FF & x_a) as usize;
let pow5_b = (0x7FF & x_b) as usize;
let pow5 = &TABLE_POW5[pow5_a..];
for (i, &p5) in pow5.iter().enumerate().take(pow5_b - pow5_a) {
if i >= d.num_digits {
return num_new_digits - 1;
} else if d.digits[i] == p5 {
continue;
} else if d.digits[i] < p5 {
return num_new_digits - 1;
} else {
return num_new_digits;
}
}
num_new_digits
}

View file

@ -1,14 +1,57 @@
//! Helper trait for generic float types.
use core::f64;
use crate::fmt::{Debug, LowerExp};
use crate::num::FpCategory;
use crate::ops::{Add, Div, Mul, Neg};
use crate::ops::{self, Add, Div, Mul, Neg};
/// A helper trait to avoid duplicating basically all the conversion code for `f32` and `f64`.
/// Lossy `as` casting between two types.
pub trait CastInto<T: Copy>: Copy {
fn cast(self) -> T;
}
/// Collection of traits that allow us to be generic over integer size.
pub trait Integer:
Sized
+ Clone
+ Copy
+ Debug
+ ops::Shr<u32, Output = Self>
+ ops::Shl<u32, Output = Self>
+ ops::BitAnd<Output = Self>
+ ops::BitOr<Output = Self>
+ PartialEq
+ CastInto<i16>
{
const ZERO: Self;
const ONE: Self;
}
macro_rules! int {
($($ty:ty),+) => {
$(
impl CastInto<i16> for $ty {
fn cast(self) -> i16 {
self as i16
}
}
impl Integer for $ty {
const ZERO: Self = 0;
const ONE: Self = 1;
}
)+
}
}
int!(u32, u64);
/// A helper trait to avoid duplicating basically all the conversion code for IEEE floats.
///
/// See the parent module's doc comment for why this is necessary.
///
/// Should **never ever** be implemented for other types or be used outside the dec2flt module.
/// Should **never ever** be implemented for other types or be used outside the `dec2flt` module.
#[doc(hidden)]
pub trait RawFloat:
Sized
@ -24,62 +67,107 @@ pub trait RawFloat:
+ Copy
+ Debug
{
/// The unsigned integer with the same size as the float
type Int: Integer + Into<u64>;
/* general constants */
const INFINITY: Self;
const NEG_INFINITY: Self;
const NAN: Self;
const NEG_NAN: Self;
/// The number of bits in the significand, *excluding* the hidden bit.
const MANTISSA_EXPLICIT_BITS: usize;
/// Bit width of the float
const BITS: u32;
// Round-to-even only happens for negative values of q
// when q ≥ 4 in the 64-bit case and when q ≥ 17 in
// the 32-bitcase.
//
// When q ≥ 0,we have that 5^q ≤ 2m+1. In the 64-bit case,we
// have 5^q ≤ 2m+1 ≤ 2^54 or q ≤ 23. In the 32-bit case,we have
// 5^q ≤ 2m+1 ≤ 2^25 or q ≤ 10.
//
// When q < 0, we have w ≥ (2m+1)×5^q. We must have that w < 2^64
// so (2m+1)×5^q < 2^64. We have that 2m+1 > 2^53 (64-bit case)
// or 2m+1 > 2^24 (32-bit case). Hence,we must have 2^53×5^q < 2^64
// (64-bit) and 2^24×5^q < 2^64 (32-bit). Hence we have 5^q < 2^11
// or q ≥ 4 (64-bit case) and 5^q < 2^40 or q ≥ 17 (32-bitcase).
//
// Thus we have that we only need to round ties to even when
// we have that q ∈ [4,23](in the 64-bit case) or q∈[17,10]
// (in the 32-bit case). In both cases,the power of five(5^|q|)
// fits in a 64-bit word.
/// The number of bits in the significand, *including* the hidden bit.
const SIG_TOTAL_BITS: u32;
const EXP_MASK: Self::Int;
const SIG_MASK: Self::Int;
/// The number of bits in the significand, *excluding* the hidden bit.
const SIG_BITS: u32 = Self::SIG_TOTAL_BITS - 1;
/// Number of bits in the exponent.
const EXP_BITS: u32 = Self::BITS - Self::SIG_BITS - 1;
/// The saturated (maximum bitpattern) value of the exponent, i.e. the infinite
/// representation.
///
/// This shifted fully right, use `EXP_MASK` for the shifted value.
const EXP_SAT: u32 = (1 << Self::EXP_BITS) - 1;
/// Signed version of `EXP_SAT` since we convert a lot.
const INFINITE_POWER: i32 = Self::EXP_SAT as i32;
/// The exponent bias value. This is also the maximum value of the exponent.
const EXP_BIAS: u32 = Self::EXP_SAT >> 1;
/// Minimum exponent value of normal values.
const EXP_MIN: i32 = -(Self::EXP_BIAS as i32 - 1);
/// Round-to-even only happens for negative values of q
/// when q ≥ 4 in the 64-bit case and when q ≥ 17 in
/// the 32-bitcase.
///
/// When q ≥ 0,we have that 5^q ≤ 2m+1. In the 64-bit case,we
/// have 5^q ≤ 2m+1 ≤ 2^54 or q ≤ 23. In the 32-bit case,we have
/// 5^q ≤ 2m+1 ≤ 2^25 or q ≤ 10.
///
/// When q < 0, we have w ≥ (2m+1)×5^q. We must have that w < 2^64
/// so (2m+1)×5^q < 2^64. We have that 2m+1 > 2^53 (64-bit case)
/// or 2m+1 > 2^24 (32-bit case). Hence,we must have 2^53×5^q < 2^64
/// (64-bit) and 2^24×5^q < 2^64 (32-bit). Hence we have 5^q < 2^11
/// or q ≥ 4 (64-bit case) and 5^q < 2^40 or q ≥ 17 (32-bitcase).
///
/// Thus we have that we only need to round ties to even when
/// we have that q ∈ [4,23](in the 64-bit case) or q∈[17,10]
/// (in the 32-bit case). In both cases,the power of five(5^|q|)
/// fits in a 64-bit word.
const MIN_EXPONENT_ROUND_TO_EVEN: i32;
const MAX_EXPONENT_ROUND_TO_EVEN: i32;
// Minimum exponent that for a fast path case, or `-⌊(MANTISSA_EXPLICIT_BITS+1)/log2(5)⌋`
const MIN_EXPONENT_FAST_PATH: i64;
/* limits related to Fast pathing */
// Maximum exponent that for a fast path case, or `⌊(MANTISSA_EXPLICIT_BITS+1)/log2(5)⌋`
const MAX_EXPONENT_FAST_PATH: i64;
/// Largest decimal exponent for a non-infinite value.
///
/// This is the max exponent in binary converted to the max exponent in decimal. Allows fast
/// pathing anything larger than `10^LARGEST_POWER_OF_TEN`, which will round to infinity.
const LARGEST_POWER_OF_TEN: i32 = {
let largest_pow2 = Self::EXP_BIAS + 1;
pow2_to_pow10(largest_pow2 as i64) as i32
};
// Maximum exponent that can be represented for a disguised-fast path case.
// This is `MAX_EXPONENT_FAST_PATH + ⌊(MANTISSA_EXPLICIT_BITS+1)/log2(10)⌋`
const MAX_EXPONENT_DISGUISED_FAST_PATH: i64;
// Minimum exponent value `-(1 << (EXP_BITS - 1)) + 1`.
const MINIMUM_EXPONENT: i32;
// Largest exponent value `(1 << EXP_BITS) - 1`.
const INFINITE_POWER: i32;
// Index (in bits) of the sign.
const SIGN_INDEX: usize;
// Smallest decimal exponent for a non-zero value.
/// Smallest decimal exponent for a non-zero value. This allows for fast pathing anything
/// smaller than `10^SMALLEST_POWER_OF_TEN`, which will round to zero.
///
/// The smallest power of ten is represented by `⌊log10(2^-n / (2^64 - 1))⌋`, where `n` is
/// the smallest power of two. The `2^64 - 1)` denomenator comes from the number of values
/// that are representable by the intermediate storage format. I don't actually know _why_
/// the storage format is relevant here.
///
/// The values may be calculated using the formula. Unfortunately we cannot calculate them at
/// compile time since intermediates exceed the range of an `f64`.
const SMALLEST_POWER_OF_TEN: i32;
// Largest decimal exponent for a non-infinite value.
const LARGEST_POWER_OF_TEN: i32;
/// Maximum exponent for a fast path case, or `⌊(SIG_BITS+1)/log2(5)⌋`
// assuming FLT_EVAL_METHOD = 0
const MAX_EXPONENT_FAST_PATH: i64 = {
let log2_5 = f64::consts::LOG2_10 - 1.0;
(Self::SIG_TOTAL_BITS as f64 / log2_5) as i64
};
// Maximum mantissa for the fast-path (`1 << 53` for f64).
const MAX_MANTISSA_FAST_PATH: u64 = 2_u64 << Self::MANTISSA_EXPLICIT_BITS;
/// Minimum exponent for a fast path case, or `-⌊(SIG_BITS+1)/log2(5)⌋`
const MIN_EXPONENT_FAST_PATH: i64 = -Self::MAX_EXPONENT_FAST_PATH;
/// Maximum exponent that can be represented for a disguised-fast path case.
/// This is `MAX_EXPONENT_FAST_PATH + ⌊(SIG_BITS+1)/log2(10)⌋`
const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 =
Self::MAX_EXPONENT_FAST_PATH + (Self::SIG_TOTAL_BITS as f64 / f64::consts::LOG2_10) as i64;
/// Maximum mantissa for the fast-path (`1 << 53` for f64).
const MAX_MANTISSA_FAST_PATH: u64 = 1 << Self::SIG_TOTAL_BITS;
/// Converts integer into float through an as cast.
/// This is only called in the fast-path algorithm, and therefore
@ -96,27 +184,51 @@ pub trait RawFloat:
/// Returns the category that this number falls into.
fn classify(self) -> FpCategory;
/// Transmute to the integer representation
fn to_bits(self) -> Self::Int;
/// Returns the mantissa, exponent and sign as integers.
fn integer_decode(self) -> (u64, i16, i8);
///
/// That is, this returns `(m, p, s)` such that `s * m * 2^p` represents the original float.
/// For 0, the exponent will be `-(EXP_BIAS + SIG_BITS`, which is the
/// minimum subnormal power.
fn integer_decode(self) -> (u64, i16, i8) {
let bits = self.to_bits();
let sign: i8 = if bits >> (Self::BITS - 1) == Self::Int::ZERO { 1 } else { -1 };
let mut exponent: i16 = ((bits & Self::EXP_MASK) >> Self::SIG_BITS).cast();
let mantissa = if exponent == 0 {
(bits & Self::SIG_MASK) << 1
} else {
(bits & Self::SIG_MASK) | (Self::Int::ONE << Self::SIG_BITS)
};
// Exponent bias + mantissa shift
exponent -= (Self::EXP_BIAS + Self::SIG_BITS) as i16;
(mantissa.into(), exponent, sign)
}
}
/// Solve for `b` in `10^b = 2^a`
const fn pow2_to_pow10(a: i64) -> i64 {
let res = (a as f64) / f64::consts::LOG2_10;
res as i64
}
impl RawFloat for f32 {
type Int = u32;
const INFINITY: Self = f32::INFINITY;
const NEG_INFINITY: Self = f32::NEG_INFINITY;
const NAN: Self = f32::NAN;
const NEG_NAN: Self = -f32::NAN;
const MANTISSA_EXPLICIT_BITS: usize = 23;
const BITS: u32 = 32;
const SIG_TOTAL_BITS: u32 = Self::MANTISSA_DIGITS;
const EXP_MASK: Self::Int = Self::EXP_MASK;
const SIG_MASK: Self::Int = Self::MAN_MASK;
const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -17;
const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 10;
const MIN_EXPONENT_FAST_PATH: i64 = -10; // assuming FLT_EVAL_METHOD = 0
const MAX_EXPONENT_FAST_PATH: i64 = 10;
const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = 17;
const MINIMUM_EXPONENT: i32 = -127;
const INFINITE_POWER: i32 = 0xFF;
const SIGN_INDEX: usize = 31;
const SMALLEST_POWER_OF_TEN: i32 = -65;
const LARGEST_POWER_OF_TEN: i32 = 38;
#[inline]
fn from_u64(v: u64) -> Self {
@ -136,16 +248,8 @@ impl RawFloat for f32 {
TABLE[exponent & 15]
}
/// Returns the mantissa, exponent and sign as integers.
fn integer_decode(self) -> (u64, i16, i8) {
let bits = self.to_bits();
let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };
let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
let mantissa =
if exponent == 0 { (bits & 0x7fffff) << 1 } else { (bits & 0x7fffff) | 0x800000 };
// Exponent bias + mantissa shift
exponent -= 127 + 23;
(mantissa as u64, exponent, sign)
fn to_bits(self) -> Self::Int {
self.to_bits()
}
fn classify(self) -> FpCategory {
@ -154,22 +258,21 @@ impl RawFloat for f32 {
}
impl RawFloat for f64 {
const INFINITY: Self = f64::INFINITY;
const NEG_INFINITY: Self = f64::NEG_INFINITY;
const NAN: Self = f64::NAN;
const NEG_NAN: Self = -f64::NAN;
type Int = u64;
const INFINITY: Self = Self::INFINITY;
const NEG_INFINITY: Self = Self::NEG_INFINITY;
const NAN: Self = Self::NAN;
const NEG_NAN: Self = -Self::NAN;
const BITS: u32 = 64;
const SIG_TOTAL_BITS: u32 = Self::MANTISSA_DIGITS;
const EXP_MASK: Self::Int = Self::EXP_MASK;
const SIG_MASK: Self::Int = Self::MAN_MASK;
const MANTISSA_EXPLICIT_BITS: usize = 52;
const MIN_EXPONENT_ROUND_TO_EVEN: i32 = -4;
const MAX_EXPONENT_ROUND_TO_EVEN: i32 = 23;
const MIN_EXPONENT_FAST_PATH: i64 = -22; // assuming FLT_EVAL_METHOD = 0
const MAX_EXPONENT_FAST_PATH: i64 = 22;
const MAX_EXPONENT_DISGUISED_FAST_PATH: i64 = 37;
const MINIMUM_EXPONENT: i32 = -1023;
const INFINITE_POWER: i32 = 0x7FF;
const SIGN_INDEX: usize = 63;
const SMALLEST_POWER_OF_TEN: i32 = -342;
const LARGEST_POWER_OF_TEN: i32 = 308;
#[inline]
fn from_u64(v: u64) -> Self {
@ -190,19 +293,8 @@ impl RawFloat for f64 {
TABLE[exponent & 31]
}
/// Returns the mantissa, exponent and sign as integers.
fn integer_decode(self) -> (u64, i16, i8) {
let bits = self.to_bits();
let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 };
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
let mantissa = if exponent == 0 {
(bits & 0xfffffffffffff) << 1
} else {
(bits & 0xfffffffffffff) | 0x10000000000000
};
// Exponent bias + mantissa shift
exponent -= 1023 + 52;
(mantissa, exponent, sign)
fn to_bits(self) -> Self::Int {
self.to_bits()
}
fn classify(self) -> FpCategory {

View file

@ -38,7 +38,7 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
// Normalize our significant digits, so the most-significant bit is set.
let lz = w.leading_zeros();
w <<= lz;
let (lo, hi) = compute_product_approx(q, w, F::MANTISSA_EXPLICIT_BITS + 3);
let (lo, hi) = compute_product_approx(q, w, F::SIG_BITS as usize + 3);
if lo == 0xFFFF_FFFF_FFFF_FFFF {
// If we have failed to approximate w x 5^-q with our 128-bit value.
// Since the addition of 1 could lead to an overflow which could then
@ -61,8 +61,8 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
}
}
let upperbit = (hi >> 63) as i32;
let mut mantissa = hi >> (upperbit + 64 - F::MANTISSA_EXPLICIT_BITS as i32 - 3);
let mut power2 = power(q as i32) + upperbit - lz as i32 - F::MINIMUM_EXPONENT;
let mut mantissa = hi >> (upperbit + 64 - F::SIG_BITS as i32 - 3);
let mut power2 = power(q as i32) + upperbit - lz as i32 - F::EXP_MIN + 1;
if power2 <= 0 {
if -power2 + 1 >= 64 {
// Have more than 64 bits below the minimum exponent, must be 0.
@ -72,8 +72,8 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
mantissa >>= -power2 + 1;
mantissa += mantissa & 1;
mantissa >>= 1;
power2 = (mantissa >= (1_u64 << F::MANTISSA_EXPLICIT_BITS)) as i32;
return BiasedFp { f: mantissa, e: power2 };
power2 = (mantissa >= (1_u64 << F::SIG_BITS)) as i32;
return BiasedFp { m: mantissa, p_biased: power2 };
}
// Need to handle rounding ties. Normally, we need to round up,
// but if we fall right in between and we have an even basis, we
@ -89,8 +89,8 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
if lo <= 1
&& q >= F::MIN_EXPONENT_ROUND_TO_EVEN as i64
&& q <= F::MAX_EXPONENT_ROUND_TO_EVEN as i64
&& mantissa & 3 == 1
&& (mantissa << (upperbit + 64 - F::MANTISSA_EXPLICIT_BITS as i32 - 3)) == hi
&& mantissa & 0b11 == 0b01
&& (mantissa << (upperbit + 64 - F::SIG_BITS as i32 - 3)) == hi
{
// Zero the lowest bit, so we don't round up.
mantissa &= !1_u64;
@ -98,20 +98,20 @@ pub fn compute_float<F: RawFloat>(q: i64, mut w: u64) -> BiasedFp {
// Round-to-even, then shift the significant digits into place.
mantissa += mantissa & 1;
mantissa >>= 1;
if mantissa >= (2_u64 << F::MANTISSA_EXPLICIT_BITS) {
if mantissa >= (2_u64 << F::SIG_BITS) {
// Rounding up overflowed, so the carry bit is set. Set the
// mantissa to 1 (only the implicit, hidden bit is set) and
// increase the exponent.
mantissa = 1_u64 << F::MANTISSA_EXPLICIT_BITS;
mantissa = 1_u64 << F::SIG_BITS;
power2 += 1;
}
// Zero out the hidden bit.
mantissa &= !(1_u64 << F::MANTISSA_EXPLICIT_BITS);
mantissa &= !(1_u64 << F::SIG_BITS);
if power2 >= F::INFINITE_POWER {
// Exponent is above largest normal value, must be infinite.
return fp_inf;
}
BiasedFp { f: mantissa, e: power2 }
BiasedFp { m: mantissa, p_biased: power2 }
}
/// Calculate a base 2 exponent from a decimal exponent.

View file

@ -3,8 +3,8 @@
//! # Problem statement
//!
//! We are given a decimal string such as `12.34e56`. This string consists of integral (`12`),
//! fractional (`34`), and exponent (`56`) parts. All parts are optional and interpreted as zero
//! when missing.
//! fractional (`34`), and exponent (`56`) parts. All parts are optional and interpreted as a
//! default value (1 or 0) when missing.
//!
//! We seek the IEEE 754 floating point number that is closest to the exact value of the decimal
//! string. It is well-known that many decimal strings do not have terminating representations in
@ -67,6 +67,18 @@
//! "such that the exponent +/- the number of decimal digits fits into a 64 bit integer".
//! Larger exponents are accepted, but we don't do arithmetic with them, they are immediately
//! turned into {positive,negative} {zero,infinity}.
//!
//! # Notation
//!
//! This module uses the same notation as the Lemire paper:
//!
//! - `m`: binary mantissa; always nonnegative
//! - `p`: binary exponent; a signed integer
//! - `w`: decimal significand; always nonnegative
//! - `q`: decimal exponent; a signed integer
//!
//! This gives `m * 2^p` for the binary floating-point number, with `w * 10^q` as the decimal
//! equivalent.
#![doc(hidden)]
#![unstable(
@ -85,14 +97,14 @@ use crate::fmt;
use crate::str::FromStr;
mod common;
mod decimal;
pub mod decimal;
pub mod decimal_seq;
mod fpu;
mod slow;
mod table;
// float is used in flt2dec, and all are used in unit tests.
pub mod float;
pub mod lemire;
pub mod number;
pub mod parse;
macro_rules! from_str_float_impl {
@ -220,10 +232,10 @@ pub fn pfe_invalid() -> ParseFloatError {
}
/// Converts a `BiasedFp` to the closest machine float type.
fn biased_fp_to_float<T: RawFloat>(x: BiasedFp) -> T {
let mut word = x.f;
word |= (x.e as u64) << T::MANTISSA_EXPLICIT_BITS;
T::from_u64_bits(word)
fn biased_fp_to_float<F: RawFloat>(x: BiasedFp) -> F {
let mut word = x.m;
word |= (x.p_biased as u64) << F::SIG_BITS;
F::from_u64_bits(word)
}
/// Converts a decimal string into a floating point number.
@ -260,12 +272,15 @@ pub fn dec2flt<F: RawFloat>(s: &str) -> Result<F, ParseFloatError> {
// redundantly using the Eisel-Lemire algorithm if it was unable to
// correctly round on the first pass.
let mut fp = compute_float::<F>(num.exponent, num.mantissa);
if num.many_digits && fp.e >= 0 && fp != compute_float::<F>(num.exponent, num.mantissa + 1) {
fp.e = -1;
if num.many_digits
&& fp.p_biased >= 0
&& fp != compute_float::<F>(num.exponent, num.mantissa + 1)
{
fp.p_biased = -1;
}
// Unable to correctly round the float using the Eisel-Lemire algorithm.
// Fallback to a slower, but always correct algorithm.
if fp.e < 0 {
if fp.p_biased < 0 {
fp = parse_long_mantissa::<F>(s);
}

View file

@ -1,88 +0,0 @@
//! Representation of a float as the significant digits and exponent.
use crate::num::dec2flt::float::RawFloat;
use crate::num::dec2flt::fpu::set_precision;
#[rustfmt::skip]
const INT_POW10: [u64; 16] = [
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000,
100000000000,
1000000000000,
10000000000000,
100000000000000,
1000000000000000,
];
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Number {
pub exponent: i64,
pub mantissa: u64,
pub negative: bool,
pub many_digits: bool,
}
impl Number {
/// Detect if the float can be accurately reconstructed from native floats.
#[inline]
fn is_fast_path<F: RawFloat>(&self) -> bool {
F::MIN_EXPONENT_FAST_PATH <= self.exponent
&& self.exponent <= F::MAX_EXPONENT_DISGUISED_FAST_PATH
&& self.mantissa <= F::MAX_MANTISSA_FAST_PATH
&& !self.many_digits
}
/// The fast path algorithm using machine-sized integers and floats.
///
/// This is extracted into a separate function so that it can be attempted before constructing
/// a Decimal. This only works if both the mantissa and the exponent
/// can be exactly represented as a machine float, since IEE-754 guarantees
/// no rounding will occur.
///
/// There is an exception: disguised fast-path cases, where we can shift
/// powers-of-10 from the exponent to the significant digits.
pub fn try_fast_path<F: RawFloat>(&self) -> Option<F> {
// Here we need to work around <https://github.com/rust-lang/rust/issues/114479>.
// The fast path crucially depends on arithmetic being rounded to the correct number of bits
// without any intermediate rounding. On x86 (without SSE or SSE2) this requires the precision
// of the x87 FPU stack to be changed so that it directly rounds to 64/32 bit.
// The `set_precision` function takes care of setting the precision on architectures which
// require setting it by changing the global state (like the control word of the x87 FPU).
let _cw = set_precision::<F>();
if self.is_fast_path::<F>() {
let mut value = if self.exponent <= F::MAX_EXPONENT_FAST_PATH {
// normal fast path
let value = F::from_u64(self.mantissa);
if self.exponent < 0 {
value / F::pow10_fast_path((-self.exponent) as _)
} else {
value * F::pow10_fast_path(self.exponent as _)
}
} else {
// disguised fast path
let shift = self.exponent - F::MAX_EXPONENT_FAST_PATH;
let mantissa = self.mantissa.checked_mul(INT_POW10[shift as usize])?;
if mantissa > F::MAX_MANTISSA_FAST_PATH {
return None;
}
F::from_u64(mantissa) * F::pow10_fast_path(F::MAX_EXPONENT_FAST_PATH as _)
};
if self.negative {
value = -value;
}
Some(value)
} else {
None
}
}
}

View file

@ -1,8 +1,8 @@
//! Functions to parse floating-point numbers.
use crate::num::dec2flt::common::{ByteSlice, is_8digits};
use crate::num::dec2flt::decimal::Decimal;
use crate::num::dec2flt::float::RawFloat;
use crate::num::dec2flt::number::Number;
const MIN_19DIGIT_INT: u64 = 100_0000_0000_0000_0000;
@ -100,7 +100,7 @@ fn parse_scientific(s_ref: &mut &[u8]) -> Option<i64> {
///
/// This creates a representation of the float as the
/// significant digits and the decimal exponent.
fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> {
fn parse_partial_number(mut s: &[u8]) -> Option<(Decimal, usize)> {
debug_assert!(!s.is_empty());
// parse initial digits before dot
@ -146,7 +146,7 @@ fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> {
// handle uncommon case with many digits
if n_digits <= 19 {
return Some((Number { exponent, mantissa, negative: false, many_digits: false }, len));
return Some((Decimal { exponent, mantissa, negative: false, many_digits: false }, len));
}
n_digits -= 19;
@ -179,13 +179,13 @@ fn parse_partial_number(mut s: &[u8]) -> Option<(Number, usize)> {
exponent += exp_number;
}
Some((Number { exponent, mantissa, negative: false, many_digits }, len))
Some((Decimal { exponent, mantissa, negative: false, many_digits }, len))
}
/// Try to parse a non-special floating point number,
/// as well as two slices with integer and fractional parts
/// and the parsed exponent.
pub fn parse_number(s: &[u8]) -> Option<Number> {
pub fn parse_number(s: &[u8]) -> Option<Decimal> {
if let Some((float, rest)) = parse_partial_number(s) {
if rest == s.len() {
return Some(float);

View file

@ -1,7 +1,7 @@
//! Slow, fallback algorithm for cases the Eisel-Lemire algorithm cannot round.
use crate::num::dec2flt::common::BiasedFp;
use crate::num::dec2flt::decimal::{Decimal, parse_decimal};
use crate::num::dec2flt::decimal_seq::{DecimalSeq, parse_decimal_seq};
use crate::num::dec2flt::float::RawFloat;
/// Parse the significant digits and biased, binary exponent of a float.
@ -36,7 +36,7 @@ pub(crate) fn parse_long_mantissa<F: RawFloat>(s: &[u8]) -> BiasedFp {
let fp_zero = BiasedFp::zero_pow2(0);
let fp_inf = BiasedFp::zero_pow2(F::INFINITE_POWER);
let mut d = parse_decimal(s);
let mut d = parse_decimal_seq(s);
// Short-circuit if the value can only be a literal 0 or infinity.
if d.num_digits == 0 || d.decimal_point < -324 {
@ -50,7 +50,7 @@ pub(crate) fn parse_long_mantissa<F: RawFloat>(s: &[u8]) -> BiasedFp {
let n = d.decimal_point as usize;
let shift = get_shift(n);
d.right_shift(shift);
if d.decimal_point < -Decimal::DECIMAL_POINT_RANGE {
if d.decimal_point < -DecimalSeq::DECIMAL_POINT_RANGE {
return fp_zero;
}
exp2 += shift as i32;
@ -67,43 +67,43 @@ pub(crate) fn parse_long_mantissa<F: RawFloat>(s: &[u8]) -> BiasedFp {
get_shift((-d.decimal_point) as _)
};
d.left_shift(shift);
if d.decimal_point > Decimal::DECIMAL_POINT_RANGE {
if d.decimal_point > DecimalSeq::DECIMAL_POINT_RANGE {
return fp_inf;
}
exp2 -= shift as i32;
}
// We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2].
exp2 -= 1;
while (F::MINIMUM_EXPONENT + 1) > exp2 {
let mut n = ((F::MINIMUM_EXPONENT + 1) - exp2) as usize;
while F::EXP_MIN > exp2 {
let mut n = (F::EXP_MIN - exp2) as usize;
if n > MAX_SHIFT {
n = MAX_SHIFT;
}
d.right_shift(n);
exp2 += n as i32;
}
if (exp2 - F::MINIMUM_EXPONENT) >= F::INFINITE_POWER {
if (exp2 - F::EXP_MIN + 1) >= F::INFINITE_POWER {
return fp_inf;
}
// Shift the decimal to the hidden bit, and then round the value
// to get the high mantissa+1 bits.
d.left_shift(F::MANTISSA_EXPLICIT_BITS + 1);
d.left_shift(F::SIG_BITS as usize + 1);
let mut mantissa = d.round();
if mantissa >= (1_u64 << (F::MANTISSA_EXPLICIT_BITS + 1)) {
if mantissa >= (1_u64 << (F::SIG_BITS + 1)) {
// Rounding up overflowed to the carry bit, need to
// shift back to the hidden bit.
d.right_shift(1);
exp2 += 1;
mantissa = d.round();
if (exp2 - F::MINIMUM_EXPONENT) >= F::INFINITE_POWER {
if (exp2 - F::EXP_MIN + 1) >= F::INFINITE_POWER {
return fp_inf;
}
}
let mut power2 = exp2 - F::MINIMUM_EXPONENT;
if mantissa < (1_u64 << F::MANTISSA_EXPLICIT_BITS) {
let mut power2 = exp2 - F::EXP_MIN + 1;
if mantissa < (1_u64 << F::SIG_BITS) {
power2 -= 1;
}
// Zero out all the bits above the explicit mantissa bits.
mantissa &= (1_u64 << F::MANTISSA_EXPLICIT_BITS) - 1;
BiasedFp { f: mantissa, e: power2 }
mantissa &= (1_u64 << F::SIG_BITS) - 1;
BiasedFp { m: mantissa, p_biased: power2 }
}

View file

@ -493,13 +493,13 @@ impl f32 {
pub const NEG_INFINITY: f32 = -1.0_f32 / 0.0_f32;
/// Sign bit
const SIGN_MASK: u32 = 0x8000_0000;
pub(crate) const SIGN_MASK: u32 = 0x8000_0000;
/// Exponent mask
const EXP_MASK: u32 = 0x7f80_0000;
pub(crate) const EXP_MASK: u32 = 0x7f80_0000;
/// Mantissa mask
const MAN_MASK: u32 = 0x007f_ffff;
pub(crate) const MAN_MASK: u32 = 0x007f_ffff;
/// Minimum representable positive value (min subnormal)
const TINY_BITS: u32 = 0x1;

View file

@ -492,13 +492,13 @@ impl f64 {
pub const NEG_INFINITY: f64 = -1.0_f64 / 0.0_f64;
/// Sign bit
const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
pub(crate) const SIGN_MASK: u64 = 0x8000_0000_0000_0000;
/// Exponent mask
const EXP_MASK: u64 = 0x7ff0_0000_0000_0000;
pub(crate) const EXP_MASK: u64 = 0x7ff0_0000_0000_0000;
/// Mantissa mask
const MAN_MASK: u64 = 0x000f_ffff_ffff_ffff;
pub(crate) const MAN_MASK: u64 = 0x000f_ffff_ffff_ffff;
/// Minimum representable positive value (min subnormal)
const TINY_BITS: u64 = 0x1;

View file

@ -87,7 +87,6 @@
#![feature(try_find)]
#![feature(try_trait_v2)]
#![feature(unsize)]
#![feature(unsized_tuple_coercion)]
#![feature(unwrap_infallible)]
// tidy-alphabetical-end
#![allow(internal_features)]

View file

@ -0,0 +1,28 @@
use core::num::dec2flt::decimal::Decimal;
type FPath<F> = ((i64, u64, bool, bool), Option<F>);
const FPATHS_F32: &[FPath<f32>] =
&[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))];
const FPATHS_F64: &[FPath<f64>] =
&[((0, 0, false, false), Some(0.0)), ((0, 0, false, false), Some(0.0))];
#[test]
fn check_fast_path_f32() {
for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F32.iter().copied() {
let dec = Decimal { exponent, mantissa, negative, many_digits };
let actual = dec.try_fast_path::<f32>();
assert_eq!(actual, expected);
}
}
#[test]
fn check_fast_path_f64() {
for ((exponent, mantissa, negative, many_digits), expected) in FPATHS_F64.iter().copied() {
let dec = Decimal { exponent, mantissa, negative, many_digits };
let actual = dec.try_fast_path::<f64>();
assert_eq!(actual, expected);
}
}

View file

@ -0,0 +1,30 @@
use core::num::dec2flt::decimal_seq::{DecimalSeq, parse_decimal_seq};
#[test]
fn test_trim() {
let mut dec = DecimalSeq::default();
let digits = [1, 2, 3, 4];
dec.digits[0..4].copy_from_slice(&digits);
dec.num_digits = 8;
dec.trim();
assert_eq!(dec.digits[0..4], digits);
assert_eq!(dec.num_digits, 4);
}
#[test]
fn test_parse() {
let tests = [("1.234", [1, 2, 3, 4], 1)];
for (s, exp_digits, decimal_point) in tests {
let actual = parse_decimal_seq(s.as_bytes());
let mut digits = [0; DecimalSeq::MAX_DIGITS];
digits[..exp_digits.len()].copy_from_slice(&exp_digits);
let expected =
DecimalSeq { num_digits: exp_digits.len(), decimal_point, truncated: false, digits };
assert_eq!(actual, expected);
}
}

View file

@ -12,8 +12,8 @@ fn test_f32_integer_decode() {
// Ignore the "sign" (quiet / signalling flag) of NAN.
// It can vary between runtime operations and LLVM folding.
let (nan_m, nan_e, _nan_s) = f32::NAN.integer_decode();
assert_eq!((nan_m, nan_e), (12582912, 105));
let (nan_m, nan_p, _nan_s) = f32::NAN.integer_decode();
assert_eq!((nan_m, nan_p), (12582912, 105));
}
#[test]
@ -28,6 +28,46 @@ fn test_f64_integer_decode() {
// Ignore the "sign" (quiet / signalling flag) of NAN.
// It can vary between runtime operations and LLVM folding.
let (nan_m, nan_e, _nan_s) = f64::NAN.integer_decode();
assert_eq!((nan_m, nan_e), (6755399441055744, 972));
let (nan_m, nan_p, _nan_s) = f64::NAN.integer_decode();
assert_eq!((nan_m, nan_p), (6755399441055744, 972));
}
/* Sanity checks of computed magic numbers */
#[test]
fn test_f32_consts() {
assert_eq!(<f32 as RawFloat>::INFINITY, f32::INFINITY);
assert_eq!(<f32 as RawFloat>::NEG_INFINITY, -f32::INFINITY);
assert_eq!(<f32 as RawFloat>::NAN.to_bits(), f32::NAN.to_bits());
assert_eq!(<f32 as RawFloat>::NEG_NAN.to_bits(), (-f32::NAN).to_bits());
assert_eq!(<f32 as RawFloat>::SIG_BITS, 23);
assert_eq!(<f32 as RawFloat>::MIN_EXPONENT_ROUND_TO_EVEN, -17);
assert_eq!(<f32 as RawFloat>::MAX_EXPONENT_ROUND_TO_EVEN, 10);
assert_eq!(<f32 as RawFloat>::MIN_EXPONENT_FAST_PATH, -10);
assert_eq!(<f32 as RawFloat>::MAX_EXPONENT_FAST_PATH, 10);
assert_eq!(<f32 as RawFloat>::MAX_EXPONENT_DISGUISED_FAST_PATH, 17);
assert_eq!(<f32 as RawFloat>::EXP_MIN, -126);
assert_eq!(<f32 as RawFloat>::EXP_SAT, 0xff);
assert_eq!(<f32 as RawFloat>::SMALLEST_POWER_OF_TEN, -65);
assert_eq!(<f32 as RawFloat>::LARGEST_POWER_OF_TEN, 38);
assert_eq!(<f32 as RawFloat>::MAX_MANTISSA_FAST_PATH, 16777216);
}
#[test]
fn test_f64_consts() {
assert_eq!(<f64 as RawFloat>::INFINITY, f64::INFINITY);
assert_eq!(<f64 as RawFloat>::NEG_INFINITY, -f64::INFINITY);
assert_eq!(<f64 as RawFloat>::NAN.to_bits(), f64::NAN.to_bits());
assert_eq!(<f64 as RawFloat>::NEG_NAN.to_bits(), (-f64::NAN).to_bits());
assert_eq!(<f64 as RawFloat>::SIG_BITS, 52);
assert_eq!(<f64 as RawFloat>::MIN_EXPONENT_ROUND_TO_EVEN, -4);
assert_eq!(<f64 as RawFloat>::MAX_EXPONENT_ROUND_TO_EVEN, 23);
assert_eq!(<f64 as RawFloat>::MIN_EXPONENT_FAST_PATH, -22);
assert_eq!(<f64 as RawFloat>::MAX_EXPONENT_FAST_PATH, 22);
assert_eq!(<f64 as RawFloat>::MAX_EXPONENT_DISGUISED_FAST_PATH, 37);
assert_eq!(<f64 as RawFloat>::EXP_MIN, -1022);
assert_eq!(<f64 as RawFloat>::EXP_SAT, 0x7ff);
assert_eq!(<f64 as RawFloat>::SMALLEST_POWER_OF_TEN, -342);
assert_eq!(<f64 as RawFloat>::LARGEST_POWER_OF_TEN, 308);
assert_eq!(<f64 as RawFloat>::MAX_MANTISSA_FAST_PATH, 9007199254740992);
}

View file

@ -1,13 +1,14 @@
use core::num::dec2flt::float::RawFloat;
use core::num::dec2flt::lemire::compute_float;
fn compute_float32(q: i64, w: u64) -> (i32, u64) {
let fp = compute_float::<f32>(q, w);
(fp.e, fp.f)
(fp.p_biased, fp.m)
}
fn compute_float64(q: i64, w: u64) -> (i32, u64) {
let fp = compute_float::<f64>(q, w);
(fp.e, fp.f)
(fp.p_biased, fp.m)
}
#[test]
@ -27,6 +28,11 @@ fn compute_float_f32_rounding() {
// Let's check the lines to see if anything is different in table...
assert_eq!(compute_float32(-10, 167772190000000000), (151, 2));
assert_eq!(compute_float32(-10, 167772200000000000), (151, 2));
// Check the rounding point between infinity and the next representable number down
assert_eq!(compute_float32(38, 3), (f32::INFINITE_POWER - 1, 6402534));
assert_eq!(compute_float32(38, 4), (f32::INFINITE_POWER, 0)); // infinity
assert_eq!(compute_float32(20, 3402823470000000000), (f32::INFINITE_POWER - 1, 8388607));
}
#[test]

View file

@ -1,5 +1,7 @@
#![allow(overflowing_literals)]
mod decimal;
mod decimal_seq;
mod float;
mod lemire;
mod parse;

View file

@ -1,9 +1,9 @@
use core::num::dec2flt::number::Number;
use core::num::dec2flt::decimal::Decimal;
use core::num::dec2flt::parse::parse_number;
use core::num::dec2flt::{dec2flt, pfe_invalid};
fn new_number(e: i64, m: u64) -> Number {
Number { exponent: e, mantissa: m, negative: false, many_digits: false }
fn new_dec(e: i64, m: u64) -> Decimal {
Decimal { exponent: e, mantissa: m, negative: false, many_digits: false }
}
#[test]
@ -31,23 +31,23 @@ fn invalid_chars() {
}
}
fn parse_positive(s: &[u8]) -> Option<Number> {
fn parse_positive(s: &[u8]) -> Option<Decimal> {
parse_number(s)
}
#[test]
fn valid() {
assert_eq!(parse_positive(b"123.456e789"), Some(new_number(786, 123456)));
assert_eq!(parse_positive(b"123.456e+789"), Some(new_number(786, 123456)));
assert_eq!(parse_positive(b"123.456e-789"), Some(new_number(-792, 123456)));
assert_eq!(parse_positive(b".050"), Some(new_number(-3, 50)));
assert_eq!(parse_positive(b"999"), Some(new_number(0, 999)));
assert_eq!(parse_positive(b"1.e300"), Some(new_number(300, 1)));
assert_eq!(parse_positive(b".1e300"), Some(new_number(299, 1)));
assert_eq!(parse_positive(b"101e-33"), Some(new_number(-33, 101)));
assert_eq!(parse_positive(b"123.456e789"), Some(new_dec(786, 123456)));
assert_eq!(parse_positive(b"123.456e+789"), Some(new_dec(786, 123456)));
assert_eq!(parse_positive(b"123.456e-789"), Some(new_dec(-792, 123456)));
assert_eq!(parse_positive(b".050"), Some(new_dec(-3, 50)));
assert_eq!(parse_positive(b"999"), Some(new_dec(0, 999)));
assert_eq!(parse_positive(b"1.e300"), Some(new_dec(300, 1)));
assert_eq!(parse_positive(b".1e300"), Some(new_dec(299, 1)));
assert_eq!(parse_positive(b"101e-33"), Some(new_dec(-33, 101)));
let zeros = "0".repeat(25);
let s = format!("1.5e{zeros}");
assert_eq!(parse_positive(s.as_bytes()), Some(new_number(-1, 15)));
assert_eq!(parse_positive(s.as_bytes()), Some(new_dec(-1, 15)));
}
macro_rules! assert_float_result_bits_eq {
@ -57,6 +57,21 @@ macro_rules! assert_float_result_bits_eq {
}};
}
#[test]
fn regression() {
// These showed up in fuzz tests when the minimum exponent was incorrect.
assert_float_result_bits_eq!(
0x0,
f64,
"3313756768023998018398807867233977556112078681253148176737587500333136120852692315608454494981109839693784033457129423181787087843504060087613228932431e-475"
);
assert_float_result_bits_eq!(
0x0,
f64,
"5298127456259331337220.92759278003098321644501973966679724599271041396379712108366679824365568578569680024083293475291869842408884554511641179110778276695274832779269225510492006696321279587846006535230380114430977056662212751544508159333199129106162019382177820713609e-346"
);
}
#[test]
fn issue31109() {
// Regression test for #31109.

View file

@ -525,31 +525,24 @@ fn ptr_metadata() {
assert_eq!(metadata("foo"), 3_usize);
assert_eq!(metadata(&[4, 7][..]), 2_usize);
let dst_tuple: &(bool, [u8]) = &(true, [0x66, 0x6F, 0x6F]);
let dst_struct: &Pair<bool, [u8]> = &Pair(true, [0x66, 0x6F, 0x6F]);
assert_eq!(metadata(dst_tuple), 3_usize);
assert_eq!(metadata(dst_struct), 3_usize);
unsafe {
let dst_tuple: &(bool, str) = std::mem::transmute(dst_tuple);
let dst_struct: &Pair<bool, str> = std::mem::transmute(dst_struct);
assert_eq!(&dst_tuple.1, "foo");
assert_eq!(&dst_struct.1, "foo");
assert_eq!(metadata(dst_tuple), 3_usize);
assert_eq!(metadata(dst_struct), 3_usize);
}
let vtable_1: DynMetadata<dyn Debug> = metadata(&4_u16 as &dyn Debug);
let vtable_2: DynMetadata<dyn Display> = metadata(&4_u16 as &dyn Display);
let vtable_3: DynMetadata<dyn Display> = metadata(&4_u32 as &dyn Display);
let vtable_4: DynMetadata<dyn Display> = metadata(&(true, 7_u32) as &(bool, dyn Display));
let vtable_5: DynMetadata<dyn Display> =
let vtable_4: DynMetadata<dyn Display> =
metadata(&Pair(true, 7_u32) as &Pair<bool, dyn Display>);
unsafe {
let address_1: *const () = std::mem::transmute(vtable_1);
let address_2: *const () = std::mem::transmute(vtable_2);
let address_3: *const () = std::mem::transmute(vtable_3);
let address_4: *const () = std::mem::transmute(vtable_4);
let address_5: *const () = std::mem::transmute(vtable_5);
// Different trait => different vtable pointer
assert_ne!(address_1, address_2);
// Different erased type => different vtable pointer
@ -558,7 +551,6 @@ fn ptr_metadata() {
// This is *not guaranteed*, so we skip it in Miri.
if !cfg!(miri) {
assert_eq!(address_3, address_4);
assert_eq!(address_3, address_5);
}
}
}

View file

@ -5,8 +5,6 @@ use core::num::NonZero;
use core::ops::{Range, RangeInclusive};
use core::slice;
use rand::seq::IndexedRandom;
#[test]
fn test_position() {
let b = [1, 2, 3, 5, 5];
@ -1810,6 +1808,7 @@ fn select_nth_unstable() {
use core::cmp::Ordering::{Equal, Greater, Less};
use rand::Rng;
use rand::seq::IndexedRandom;
let mut rng = crate::test_rng();

View file

@ -35,7 +35,7 @@ miniz_oxide = { version = "0.8.0", optional = true, default-features = false }
addr2line = { version = "0.24.0", optional = true, default-features = false }
[target.'cfg(not(all(windows, target_env = "msvc")))'.dependencies]
libc = { version = "0.2.169", default-features = false, features = [
libc = { version = "0.2.170", default-features = false, features = [
'rustc-dep-of-std',
], public = true }

View file

@ -2857,9 +2857,11 @@ pub fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
///
/// See [`fs::remove_file`] and [`fs::remove_dir`].
///
/// `remove_dir_all` will fail if `remove_dir` or `remove_file` fail on any constituent paths, including the root `path`.
/// As a result, the directory you are deleting must exist, meaning that this function is not idempotent.
/// Additionally, `remove_dir_all` will also fail if the `path` is not a directory.
/// [`remove_dir_all`] will fail if [`remove_dir`] or [`remove_file`] fail on *any* constituent
/// paths, *including* the root `path`. Consequently,
///
/// - The directory you are deleting *must* exist, meaning that this function is *not idempotent*.
/// - [`remove_dir_all`] will fail if the `path` is *not* a directory.
///
/// Consider ignoring the error if validating the removal is not required for your use case.
///

View file

@ -575,6 +575,11 @@ impl fmt::Debug for StdinLock<'_> {
/// output stream. Access is also synchronized via a lock and explicit control
/// over locking is available via the [`lock`] method.
///
/// By default, the handle is line-buffered when connected to a terminal, meaning
/// it flushes automatically when a newline (`\n`) is encountered. For immediate
/// output, you can manually call the [`flush`] method. When the handle goes out
/// of scope, the buffer is automatically flushed.
///
/// Created by the [`io::stdout`] method.
///
/// ### Note: Windows Portability Considerations
@ -590,6 +595,7 @@ impl fmt::Debug for StdinLock<'_> {
/// standard library or via raw Windows API calls, will fail.
///
/// [`lock`]: Stdout::lock
/// [`flush`]: Write::flush
/// [`io::stdout`]: stdout
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Stdout {
@ -604,6 +610,11 @@ pub struct Stdout {
/// This handle implements the [`Write`] trait, and is constructed via
/// the [`Stdout::lock`] method. See its documentation for more.
///
/// By default, the handle is line-buffered when connected to a terminal, meaning
/// it flushes automatically when a newline (`\n`) is encountered. For immediate
/// output, you can manually call the [`flush`] method. When the handle goes out
/// of scope, the buffer is automatically flushed.
///
/// ### Note: Windows Portability Considerations
///
/// When operating in a console, the Windows implementation of this stream does not support
@ -615,6 +626,8 @@ pub struct Stdout {
/// the contained handle will be null. In such cases, the standard library's `Read` and
/// `Write` will do nothing and silently succeed. All other I/O operations, via the
/// standard library or via raw Windows API calls, will fail.
///
/// [`flush`]: Write::flush
#[must_use = "if unused stdout will immediately unlock"]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct StdoutLock<'a> {
@ -629,6 +642,11 @@ static STDOUT: OnceLock<ReentrantLock<RefCell<LineWriter<StdoutRaw>>>> = OnceLoc
/// is synchronized via a mutex. If you need more explicit control over
/// locking, see the [`Stdout::lock`] method.
///
/// By default, the handle is line-buffered when connected to a terminal, meaning
/// it flushes automatically when a newline (`\n`) is encountered. For immediate
/// output, you can manually call the [`flush`] method. When the handle goes out
/// of scope, the buffer is automatically flushed.
///
/// ### Note: Windows Portability Considerations
///
/// When operating in a console, the Windows implementation of this stream does not support
@ -669,6 +687,22 @@ static STDOUT: OnceLock<ReentrantLock<RefCell<LineWriter<StdoutRaw>>>> = OnceLoc
/// Ok(())
/// }
/// ```
///
/// Ensuring output is flushed immediately:
///
/// ```no_run
/// use std::io::{self, Write};
///
/// fn main() -> io::Result<()> {
/// let mut stdout = io::stdout();
/// stdout.write_all(b"hello, ")?;
/// stdout.flush()?; // Manual flush
/// stdout.write_all(b"world!\n")?; // Automatically flushed
/// Ok(())
/// }
/// ```
///
/// [`flush`]: Write::flush
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "io_stdout")]

View file

@ -10,7 +10,7 @@
//! - More information about protocols can be found [here](https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/3_foundation/36_protocols_and_handles)
use r_efi::efi::{self, Guid};
use r_efi::protocols::{device_path, device_path_to_text, shell};
use r_efi::protocols::{device_path, device_path_to_text, service_binding, shell};
use crate::ffi::{OsStr, OsString};
use crate::io::{self, const_error};
@ -500,3 +500,62 @@ pub(crate) fn get_device_path_from_map(map: &Path) -> io::Result<BorrowedDeviceP
Ok(BorrowedDevicePath::new(protocol))
}
/// Helper for UEFI Protocols which are created and destroyed using
/// [EFI_SERVICE_BINDING_PROTCOL](https://uefi.org/specs/UEFI/2.11/11_Protocols_UEFI_Driver_Model.html#efi-service-binding-protocol)
pub(crate) struct ServiceProtocol {
service_guid: r_efi::efi::Guid,
handle: NonNull<crate::ffi::c_void>,
child_handle: NonNull<crate::ffi::c_void>,
}
impl ServiceProtocol {
#[expect(dead_code)]
pub(crate) fn open(service_guid: r_efi::efi::Guid) -> io::Result<Self> {
let handles = locate_handles(service_guid)?;
for handle in handles {
if let Ok(protocol) = open_protocol::<service_binding::Protocol>(handle, service_guid) {
let Ok(child_handle) = Self::create_child(protocol) else {
continue;
};
return Ok(Self { service_guid, handle, child_handle });
}
}
Err(io::const_error!(io::ErrorKind::NotFound, "no service binding protocol found"))
}
#[expect(dead_code)]
pub(crate) fn child_handle(&self) -> NonNull<crate::ffi::c_void> {
self.child_handle
}
fn create_child(
sbp: NonNull<service_binding::Protocol>,
) -> io::Result<NonNull<crate::ffi::c_void>> {
let mut child_handle: r_efi::efi::Handle = crate::ptr::null_mut();
// SAFETY: A new handle is allocated if a pointer to NULL is passed.
let r = unsafe { ((*sbp.as_ptr()).create_child)(sbp.as_ptr(), &mut child_handle) };
if r.is_error() {
Err(crate::io::Error::from_raw_os_error(r.as_usize()))
} else {
NonNull::new(child_handle)
.ok_or(const_error!(io::ErrorKind::Other, "null child handle"))
}
}
}
impl Drop for ServiceProtocol {
fn drop(&mut self) {
if let Ok(sbp) = open_protocol::<service_binding::Protocol>(self.handle, self.service_guid)
{
// SAFETY: Child handle must be allocated by the current service binding protocol.
let _ = unsafe {
((*sbp.as_ptr()).destroy_child)(sbp.as_ptr(), self.child_handle.as_ptr())
};
}
}
}

View file

@ -410,6 +410,7 @@ impl Command {
#[cfg(not(any(
target_os = "freebsd",
target_os = "illumos",
all(target_os = "linux", target_env = "gnu"),
all(target_os = "linux", target_env = "musl"),
target_os = "nto",
@ -427,6 +428,7 @@ impl Command {
// directly.
#[cfg(any(
target_os = "freebsd",
target_os = "illumos",
all(target_os = "linux", target_env = "gnu"),
all(target_os = "linux", target_env = "musl"),
target_os = "nto",
@ -584,6 +586,10 @@ impl Command {
fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> {
use crate::sys::weak::weak;
// POSIX.1-2024 standardizes this function:
// https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_addchdir.html.
// The _np version is more widely available, though, so try that first.
weak! {
fn posix_spawn_file_actions_addchdir_np(
*mut libc::posix_spawn_file_actions_t,
@ -591,7 +597,16 @@ impl Command {
) -> libc::c_int
}
posix_spawn_file_actions_addchdir_np.get()
weak! {
fn posix_spawn_file_actions_addchdir(
*mut libc::posix_spawn_file_actions_t,
*const libc::c_char
) -> libc::c_int
}
posix_spawn_file_actions_addchdir_np
.get()
.or_else(|| posix_spawn_file_actions_addchdir.get())
}
/// Get the function pointer for adding a chdir action to a

View file

@ -292,7 +292,7 @@ v(
v("release-channel", "rust.channel", "the name of the release channel to build")
v(
"release-description",
"rust.description",
"build.description",
"optional descriptive string for version output",
)
v("dist-compression-formats", None, "List of compression formats to use")

View file

@ -90,7 +90,7 @@ impl Step for CrateBootstrap {
);
let crate_name = path.rsplit_once('/').unwrap().1;
run_cargo_test(cargo, &[], &[], crate_name, crate_name, bootstrap_host, builder);
run_cargo_test(cargo, &[], &[], crate_name, bootstrap_host, builder);
}
}
@ -140,15 +140,7 @@ You can skip linkcheck with --skip src/tools/linkchecker"
SourceType::InTree,
&[],
);
run_cargo_test(
cargo,
&[],
&[],
"linkchecker",
"linkchecker self tests",
bootstrap_host,
builder,
);
run_cargo_test(cargo, &[], &[], "linkchecker self tests", bootstrap_host, builder);
if builder.doc_tests == DocTests::No {
return;
@ -337,7 +329,7 @@ impl Step for Cargo {
);
// NOTE: can't use `run_cargo_test` because we need to overwrite `PATH`
let mut cargo = prepare_cargo_test(cargo, &[], &[], "cargo", self.host, builder);
let mut cargo = prepare_cargo_test(cargo, &[], &[], self.host, builder);
// Don't run cross-compile tests, we may not have cross-compiled libstd libs
// available.
@ -423,7 +415,7 @@ impl Step for RustAnalyzer {
cargo.env("SKIP_SLOW_TESTS", "1");
cargo.add_rustc_lib_path(builder);
run_cargo_test(cargo, &[], &[], "rust-analyzer", "rust-analyzer", host, builder);
run_cargo_test(cargo, &[], &[], "rust-analyzer", host, builder);
}
}
@ -472,7 +464,7 @@ impl Step for Rustfmt {
cargo.add_rustc_lib_path(builder);
run_cargo_test(cargo, &[], &[], "rustfmt", "rustfmt", host, builder);
run_cargo_test(cargo, &[], &[], "rustfmt", host, builder);
}
}
@ -588,7 +580,7 @@ impl Step for Miri {
// We can NOT use `run_cargo_test` since Miri's integration tests do not use the usual test
// harness and therefore do not understand the flags added by `add_flags_and_try_run_test`.
let mut cargo = prepare_cargo_test(cargo, &[], &[], "miri", host, builder);
let mut cargo = prepare_cargo_test(cargo, &[], &[], host, builder);
// miri tests need to know about the stage sysroot
cargo.env("MIRI_SYSROOT", &miri_sysroot);
@ -736,7 +728,7 @@ impl Step for CompiletestTest {
&[],
);
cargo.allow_features("test");
run_cargo_test(cargo, &[], &[], "compiletest", "compiletest self test", host, builder);
run_cargo_test(cargo, &[], &[], "compiletest self test", host, builder);
}
}
@ -797,7 +789,7 @@ impl Step for Clippy {
cargo.env("HOST_LIBS", host_libs);
cargo.add_rustc_lib_path(builder);
let cargo = prepare_cargo_test(cargo, &[], &[], "clippy", host, builder);
let cargo = prepare_cargo_test(cargo, &[], &[], host, builder);
let _guard = builder.msg_sysroot_tool(Kind::Test, compiler.stage, "clippy", host, host);
@ -1277,15 +1269,7 @@ impl Step for CrateRunMakeSupport {
&[],
);
cargo.allow_features("test");
run_cargo_test(
cargo,
&[],
&[],
"run-make-support",
"run-make-support self test",
host,
builder,
);
run_cargo_test(cargo, &[], &[], "run-make-support self test", host, builder);
}
}
@ -1322,7 +1306,7 @@ impl Step for CrateBuildHelper {
&[],
);
cargo.allow_features("test");
run_cargo_test(cargo, &[], &[], "build_helper", "build_helper self test", host, builder);
run_cargo_test(cargo, &[], &[], "build_helper self test", host, builder);
}
}
@ -2507,13 +2491,12 @@ fn run_cargo_test<'a>(
cargo: builder::Cargo,
libtest_args: &[&str],
crates: &[String],
primary_crate: &str,
description: impl Into<Option<&'a str>>,
target: TargetSelection,
builder: &Builder<'_>,
) -> bool {
let compiler = cargo.compiler();
let mut cargo = prepare_cargo_test(cargo, libtest_args, crates, primary_crate, target, builder);
let mut cargo = prepare_cargo_test(cargo, libtest_args, crates, target, builder);
let _time = helpers::timeit(builder);
let _group = description.into().and_then(|what| {
builder.msg_sysroot_tool(Kind::Test, compiler.stage, what, compiler.host, target)
@ -2537,7 +2520,6 @@ fn prepare_cargo_test(
cargo: builder::Cargo,
libtest_args: &[&str],
crates: &[String],
primary_crate: &str,
target: TargetSelection,
builder: &Builder<'_>,
) -> BootstrapCommand {
@ -2567,13 +2549,6 @@ fn prepare_cargo_test(
cargo.arg("--doc");
}
DocTests::No => {
let krate = &builder
.crates
.get(primary_crate)
.unwrap_or_else(|| panic!("missing crate {primary_crate}"));
if krate.has_lib {
cargo.arg("--lib");
}
cargo.args(["--bins", "--examples", "--tests", "--benches"]);
}
DocTests::Yes => {}
@ -2748,15 +2723,15 @@ impl Step for Crate {
_ => panic!("can only test libraries"),
};
run_cargo_test(
cargo,
&[],
&self.crates,
&self.crates[0],
&*crate_description(&self.crates),
target,
builder,
);
let mut crates = self.crates.clone();
// The core crate can't directly be tested. We could silently
// ignore it, but adding it's own test crate is less confusing
// for users. We still keep core itself for doctests.
if crates.iter().any(|crate_| crate_ == "core") {
crates.push("coretests".to_owned());
}
run_cargo_test(cargo, &[], &crates, &*crate_description(&self.crates), target, builder);
}
}
@ -2849,15 +2824,7 @@ impl Step for CrateRustdoc {
dylib_path.insert(0, PathBuf::from(&*libdir));
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
run_cargo_test(
cargo,
&[],
&["rustdoc:0.0.0".to_string()],
"rustdoc",
"rustdoc",
target,
builder,
);
run_cargo_test(cargo, &[], &["rustdoc:0.0.0".to_string()], "rustdoc", target, builder);
}
}
@ -2914,7 +2881,6 @@ impl Step for CrateRustdocJsonTypes {
libtest_args,
&["rustdoc-json-types".to_string()],
"rustdoc-json-types",
"rustdoc-json-types",
target,
builder,
);
@ -3094,7 +3060,7 @@ impl Step for Bootstrap {
// bootstrap tests are racy on directory creation so just run them one at a time.
// Since there's not many this shouldn't be a problem.
run_cargo_test(cargo, &["--test-threads=1"], &[], "bootstrap", None, host, builder);
run_cargo_test(cargo, &["--test-threads=1"], &[], None, host, builder);
}
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
@ -3219,7 +3185,7 @@ impl Step for RustInstaller {
bootstrap_host,
bootstrap_host,
);
run_cargo_test(cargo, &[], &[], "installer", None, bootstrap_host, builder);
run_cargo_test(cargo, &[], &[], None, bootstrap_host, builder);
// We currently don't support running the test.sh script outside linux(?) environments.
// Eventually this should likely migrate to #[test]s in rust-installer proper rather than a
@ -3610,7 +3576,7 @@ impl Step for TestFloatParse {
&[],
);
run_cargo_test(cargo_test, &[], &[], crate_name, crate_name, bootstrap_host, builder);
run_cargo_test(cargo_test, &[], &[], crate_name, bootstrap_host, builder);
// Run the actual parse tests.
let mut cargo_run = tool::prepare_tool_cargo(

View file

@ -248,23 +248,32 @@ pub fn prepare_tool_cargo(
cargo.env("CFG_VERSION", builder.rust_version());
cargo.env("CFG_RELEASE_NUM", &builder.version);
cargo.env("DOC_RUST_LANG_ORG_CHANNEL", builder.doc_rust_lang_org_channel());
if let Some(ref ver_date) = builder.rust_info().commit_date() {
cargo.env("CFG_VER_DATE", ver_date);
}
if let Some(ref ver_hash) = builder.rust_info().sha() {
cargo.env("CFG_VER_HASH", ver_hash);
}
if let Some(description) = &builder.config.description {
cargo.env("CFG_VER_DESCRIPTION", description);
}
let info = GitInfo::new(builder.config.omit_git_hash, &dir);
if let Some(sha) = info.sha() {
cargo.env("CFG_COMMIT_HASH", sha);
}
if let Some(sha_short) = info.sha_short() {
cargo.env("CFG_SHORT_COMMIT_HASH", sha_short);
}
if let Some(date) = info.commit_date() {
cargo.env("CFG_COMMIT_DATE", date);
}
if !features.is_empty() {
cargo.arg("--features").arg(features.join(", "));
}

View file

@ -894,6 +894,7 @@ define_config! {
#[derive(Default)]
struct Build {
build: Option<String> = "build",
description: Option<String> = "description",
host: Option<Vec<String>> = "host",
target: Option<Vec<String>> = "target",
build_dir: Option<String> = "build-dir",
@ -1176,6 +1177,7 @@ define_config! {
incremental: Option<bool> = "incremental",
default_linker: Option<String> = "default-linker",
channel: Option<String> = "channel",
// FIXME: Remove this field at Q2 2025, it has been replaced by build.description
description: Option<String> = "description",
musl_root: Option<String> = "musl-root",
rpath: Option<bool> = "rpath",
@ -1583,6 +1585,7 @@ impl Config {
config.change_id = toml.change_id.inner;
let Build {
mut description,
build,
host,
target,
@ -1831,7 +1834,7 @@ impl Config {
randomize_layout,
default_linker,
channel: _, // already handled above
description,
description: rust_description,
musl_root,
rpath,
verbose_tests,
@ -1924,7 +1927,12 @@ impl Config {
set(&mut config.jemalloc, jemalloc);
set(&mut config.test_compare_mode, test_compare_mode);
set(&mut config.backtrace, backtrace);
config.description = description;
if rust_description.is_some() {
eprintln!(
"Warning: rust.description is deprecated. Use build.description instead."
);
}
description = description.or(rust_description);
set(&mut config.rust_dist_src, dist_src);
set(&mut config.verbose_tests, verbose_tests);
// in the case "false" is set explicitly, do not overwrite the command line args
@ -1990,6 +1998,7 @@ impl Config {
}
config.reproducible_artifacts = flags.reproducible_artifact;
config.description = description;
// We need to override `rust.channel` if it's manually specified when using the CI rustc.
// This is because if the compiler uses a different channel than the one specified in config.toml,

View file

@ -28,7 +28,6 @@ struct Package {
source: Option<String>,
manifest_path: String,
dependencies: Vec<Dependency>,
targets: Vec<Target>,
features: BTreeMap<String, Vec<String>>,
}
@ -40,11 +39,6 @@ struct Dependency {
source: Option<String>,
}
#[derive(Debug, Deserialize)]
struct Target {
kind: Vec<String>,
}
/// Collects and stores package metadata of each workspace members into `build`,
/// by executing `cargo metadata` commands.
pub fn build(build: &mut Build) {
@ -59,12 +53,10 @@ pub fn build(build: &mut Build) {
.filter(|dep| dep.source.is_none())
.map(|dep| dep.name)
.collect();
let has_lib = package.targets.iter().any(|t| t.kind.iter().any(|k| k == "lib"));
let krate = Crate {
name: name.clone(),
deps,
path,
has_lib,
features: package.features.keys().cloned().collect(),
};
let relative_path = krate.local_path(build);

View file

@ -185,7 +185,6 @@ struct Crate {
name: String,
deps: HashSet<String>,
path: PathBuf,
has_lib: bool,
features: Vec<String>,
}

View file

@ -365,4 +365,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
severity: ChangeSeverity::Info,
summary: "`rust.channel` now supports \"auto-detect\" to load the channel from `src/ci/channel`",
},
ChangeInfo {
change_id: 137723,
severity: ChangeSeverity::Info,
summary: "The rust.description option has moved to build.description and rust.description is now deprecated.",
},
];

View file

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "anstream"
version = "0.6.18"
@ -38,7 +44,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
@ -49,7 +55,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
@ -58,6 +64,12 @@ version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "build_helper"
version = "0.1.0"
@ -66,6 +78,27 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "bytes"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
[[package]]
name = "cc"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "citool"
version = "0.1.0"
@ -73,10 +106,12 @@ dependencies = [
"anyhow",
"build_helper",
"clap",
"csv",
"insta",
"serde",
"serde_json",
"serde_yaml",
"ureq",
]
[[package]]
@ -134,7 +169,95 @@ dependencies = [
"encode_unicode",
"libc",
"once_cell",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "cookie_store"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
dependencies = [
"cookie",
"document-features",
"idna",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"time",
"url",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "csv"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
dependencies = [
"memchr",
]
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]]
@ -149,6 +272,42 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "flate2"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
@ -161,6 +320,162 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "http"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.7.1"
@ -208,18 +523,57 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "litemap"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "log"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project"
version = "1.1.9"
@ -240,6 +594,12 @@ dependencies = [
"syn",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.93"
@ -258,6 +618,61 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "ring"
version = "0.17.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.23.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
[[package]]
name = "rustls-webpki"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "ryu"
version = "1.0.19"
@ -309,18 +724,42 @@ dependencies = [
"unsafe-libyaml",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "smallvec"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.98"
@ -332,6 +771,58 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb041120f25f8fbe8fd2dbe4671c7c2ed74d83be2e7a77529bf7e0790ae3f472"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
[[package]]
name = "time-macros"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "unicode-ident"
version = "1.0.16"
@ -344,12 +835,110 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06f78313c985f2fba11100dd06d60dd402d0cabb458af4d94791b8e09c025323"
dependencies = [
"base64",
"cookie_store",
"flate2",
"log",
"percent-encoding",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"serde",
"serde_json",
"ureq-proto",
"utf-8",
"webpki-roots",
]
[[package]]
name = "ureq-proto"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64adb55464bad1ab1aa9229133d0d59d2f679180f4d15f0d9debe616f541f25e"
dependencies = [
"base64",
"http",
"httparse",
"log",
]
[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "webpki-roots"
version = "0.26.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@ -422,3 +1011,88 @@ name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "yoke"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -6,9 +6,11 @@ edition = "2021"
[dependencies]
anyhow = "1"
clap = { version = "4.5", features = ["derive"] }
csv = "1"
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.9"
serde_json = "1"
ureq = { version = "3", features = ["json"] }
build_helper = { path = "../../build_helper" }
@ -19,3 +21,7 @@ insta = "1"
# If this is omitted, cargo will look for a workspace elsewhere.
# We want to avoid this, since citool is independent of the other crates.
[workspace]
# Make compilation faster
[profile.dev]
debug = 0

View file

@ -0,0 +1,24 @@
use std::path::Path;
/// Loads CPU usage records from a CSV generated by the `src/ci/scripts/collect-cpu-stats.sh`
/// script.
pub fn load_cpu_usage(path: &Path) -> anyhow::Result<Vec<f64>> {
let reader = csv::ReaderBuilder::new().flexible(true).from_path(path)?;
let mut entries = vec![];
for row in reader.into_records() {
let row = row?;
let cols = row.into_iter().collect::<Vec<&str>>();
// The log might contain incomplete rows or some Python exception
if cols.len() == 2 {
if let Ok(idle) = cols[1].parse::<f64>() {
entries.push(100.0 - idle);
} else {
eprintln!("Warning: cannot parse CPU CSV entry {}", cols[1]);
}
}
}
Ok(entries)
}

View file

@ -0,0 +1,43 @@
use anyhow::Context;
use crate::utils::load_env_var;
/// Uploads a custom CI pipeline metric to Datadog.
/// Expects to be executed from within the context of a GitHub Actions job.
pub fn upload_datadog_metric(name: &str, value: f64) -> anyhow::Result<()> {
let datadog_api_key = load_env_var("DATADOG_API_KEY")?;
let github_server_url = load_env_var("GITHUB_SERVER_URL")?;
let github_repository = load_env_var("GITHUB_REPOSITORY")?;
let github_run_id = load_env_var("GITHUB_RUN_ID")?;
let github_run_attempt = load_env_var("GITHUB_RUN_ATTEMPT")?;
let github_job = load_env_var("GITHUB_JOB")?;
let dd_github_job_name = load_env_var("DD_GITHUB_JOB_NAME")?;
// This API endpoint is not documented in Datadog's API reference currently.
// It was reverse-engineered from the `datadog-ci measure` npm command.
ureq::post("https://api.datadoghq.com/api/v2/ci/pipeline/metrics")
.header("DD-API-KEY", datadog_api_key)
.send_json(serde_json::json!({
"data": {
"attributes": {
"ci_env": {
"GITHUB_SERVER_URL": github_server_url,
"GITHUB_REPOSITORY": github_repository,
"GITHUB_RUN_ID": github_run_id,
"GITHUB_RUN_ATTEMPT": github_run_attempt,
"GITHUB_JOB": github_job,
"DD_GITHUB_JOB_NAME": dd_github_job_name
},
// Job level
"ci_level": 1,
"metrics": {
name: value
},
"provider": "github"
},
"type": "ci_custom_metric"
}
}))
.context("cannot send metric to DataDog")?;
Ok(())
}

View file

@ -1,4 +1,7 @@
mod cpu_usage;
mod datadog;
mod metrics;
mod utils;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
@ -8,7 +11,10 @@ use anyhow::Context;
use clap::Parser;
use serde_yaml::Value;
use crate::cpu_usage::load_cpu_usage;
use crate::datadog::upload_datadog_metric;
use crate::metrics::postprocess_metrics;
use crate::utils::load_env_var;
const CI_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
const DOCKER_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../docker");
@ -75,7 +81,7 @@ impl JobDatabase {
}
fn load_job_db(path: &Path) -> anyhow::Result<JobDatabase> {
let db = read_to_string(path)?;
let db = utils::read_to_string(path)?;
let mut db: Value = serde_yaml::from_str(&db)?;
// We need to expand merge keys (<<), because serde_yaml can't deal with them
@ -148,10 +154,6 @@ impl GitHubContext {
}
}
fn load_env_var(name: &str) -> anyhow::Result<String> {
std::env::var(name).with_context(|| format!("Cannot find variable {name}"))
}
fn load_github_ctx() -> anyhow::Result<GitHubContext> {
let event_name = load_env_var("GITHUB_EVENT_NAME")?;
let commit_message =
@ -325,6 +327,18 @@ fn run_workflow_locally(db: JobDatabase, job_type: JobType, name: String) -> any
if !result.success() { Err(anyhow::anyhow!("Job failed")) } else { Ok(()) }
}
fn upload_ci_metrics(cpu_usage_csv: &Path) -> anyhow::Result<()> {
let usage = load_cpu_usage(cpu_usage_csv).context("Cannot load CPU usage from input CSV")?;
eprintln!("CPU usage\n{usage:?}");
let avg = if !usage.is_empty() { usage.iter().sum::<f64>() / usage.len() as f64 } else { 0.0 };
eprintln!("CPU usage average: {avg}");
upload_datadog_metric("avg-cpu-usage", avg).context("Cannot upload Datadog metric")?;
Ok(())
}
#[derive(clap::Parser)]
enum Args {
/// Calculate a list of jobs that should be executed on CI.
@ -350,6 +364,11 @@ enum Args {
/// Usually, this will be GITHUB_STEP_SUMMARY on CI.
summary_path: PathBuf,
},
/// Upload CI metrics to Datadog.
UploadBuildMetrics {
/// Path to a CSV containing the CI job CPU usage.
cpu_usage_csv: PathBuf,
},
}
#[derive(clap::ValueEnum, Clone)]
@ -370,7 +389,7 @@ fn main() -> anyhow::Result<()> {
let jobs_path = jobs_file.as_deref().unwrap_or(default_jobs_file);
let gh_ctx = load_github_ctx()
.context("Cannot load environment variables from GitHub Actions")?;
let channel = read_to_string(Path::new(CI_DIRECTORY).join("channel"))
let channel = utils::read_to_string(Path::new(CI_DIRECTORY).join("channel"))
.context("Cannot read channel file")?
.trim()
.to_string();
@ -379,7 +398,10 @@ fn main() -> anyhow::Result<()> {
.context("Failed to calculate job matrix")?;
}
Args::RunJobLocally { job_type, name } => {
run_workflow_locally(load_db(default_jobs_file)?, job_type, name)?
run_workflow_locally(load_db(default_jobs_file)?, job_type, name)?;
}
Args::UploadBuildMetrics { cpu_usage_csv } => {
upload_ci_metrics(&cpu_usage_csv)?;
}
Args::PostprocessMetrics { metrics_path, summary_path } => {
postprocess_metrics(&metrics_path, &summary_path)?;
@ -388,8 +410,3 @@ fn main() -> anyhow::Result<()> {
Ok(())
}
fn read_to_string<P: AsRef<Path>>(path: P) -> anyhow::Result<String> {
let error = format!("Cannot read file {:?}", path.as_ref());
std::fs::read_to_string(path).context(error)
}

View file

@ -0,0 +1,11 @@
use std::path::Path;
use anyhow::Context;
pub fn load_env_var(name: &str) -> anyhow::Result<String> {
std::env::var(name).with_context(|| format!("Cannot find environment variable `{name}`"))
}
pub fn read_to_string<P: AsRef<Path>>(path: P) -> anyhow::Result<String> {
std::fs::read_to_string(&path).with_context(|| format!("Cannot read file {:?}", path.as_ref()))
}

5004
src/ci/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
{
"dependencies": {
"@datadog/datadog-ci": "^2.45.1"
}
}

View file

@ -1,86 +0,0 @@
"""
This script postprocesses data gathered during a CI run, computes certain metrics
from them, and uploads these metrics to DataDog.
This script is expected to be executed from within a GitHub Actions job.
It expects the following environment variables:
- DATADOG_SITE: path to the DataDog API endpoint
- DATADOG_API_KEY: DataDog API token
- DD_GITHUB_JOB_NAME: Name of the current GitHub Actions job
It expects the presence of a binary called `datadog-ci` inside `node_modules`.
It can be installed with `npm ci` at `src/ci`.
Usage:
```bash
$ python3 upload-build-metrics.py <path-to-CPU-usage-CSV>
```
`path-to-CPU-usage-CSV` is a path to a CSV generated by the `src/ci/cpu-usage-over-time.py` script.
"""
import argparse
import csv
import os
import subprocess
import sys
from pathlib import Path
from typing import List
def load_cpu_usage(path: Path) -> List[float]:
usage = []
with open(path) as f:
reader = csv.reader(f, delimiter=",")
for row in reader:
# The log might contain incomplete rows or some Python exception
if len(row) == 2:
try:
idle = float(row[1])
usage.append(100.0 - idle)
except ValueError:
pass
return usage
def upload_datadog_measure(name: str, value: float):
"""
Uploads a single numeric metric for the current GitHub Actions job to DataDog.
"""
print(f"Metric {name}: {value:.4f}")
cmd = "npx"
if os.getenv("GITHUB_ACTIONS") is not None and sys.platform.lower().startswith(
"win"
):
# Due to weird interaction of MSYS2 and Python, we need to use an absolute path,
# and also specify the ".cmd" at the end. See https://github.com/rust-lang/rust/pull/125771.
cmd = "C:\\Program Files\\nodejs\\npx.cmd"
subprocess.run(
[
cmd,
"datadog-ci",
"measure",
"--level",
"job",
"--measures",
f"{name}:{value}",
],
check=False,
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(prog="DataDog metric uploader")
parser.add_argument("cpu-usage-history-csv")
args = parser.parse_args()
build_usage_csv = vars(args)["cpu-usage-history-csv"]
usage_timeseries = load_cpu_usage(Path(build_usage_csv))
if len(usage_timeseries) > 0:
avg_cpu_usage = sum(usage_timeseries) / len(usage_timeseries)
else:
avg_cpu_usage = 0
upload_datadog_measure("avg-cpu-usage", avg_cpu_usage)

View file

@ -74,8 +74,7 @@ The following test suites are available, with links for more information:
### General purpose test suite
[`run-make`](#run-make-tests) are general purpose tests using Rust programs (or
Makefiles (legacy)).
[`run-make`](#run-make-tests) are general purpose tests using Rust programs.
### Rustdoc test suites
@ -396,14 +395,6 @@ your test, causing separate files to be generated for 32bit and 64bit systems.
### `run-make` tests
> **Note on phasing out `Makefile`s**
>
> We are planning to migrate all existing Makefile-based `run-make` tests
> to Rust programs. You should not be adding new Makefile-based `run-make`
> tests.
>
> See <https://github.com/rust-lang/rust/issues/121876>.
The tests in [`tests/run-make`] are general-purpose tests using Rust *recipes*,
which are small programs (`rmake.rs`) allowing arbitrary Rust code such as
`rustc` invocations, and is supported by a [`run_make_support`] library. Using
@ -424,11 +415,6 @@ Compiletest directives like `//@ only-<target>` or `//@ ignore-<target>` are
supported in `rmake.rs`, like in UI tests. However, revisions or building
auxiliary via directives are not currently supported.
Two `run-make` tests are ported over to Rust recipes as examples:
- <https://github.com/rust-lang/rust/tree/master/tests/run-make/CURRENT_RUSTC_VERSION>
- <https://github.com/rust-lang/rust/tree/master/tests/run-make/a-b-a-linker-guard>
#### Quickly check if `rmake.rs` tests can be compiled
You can quickly check if `rmake.rs` tests can be compiled without having to
@ -481,20 +467,6 @@ Then add a corresponding entry to `"rust-analyzer.linkedProjects"`
],
```
#### Using Makefiles (legacy)
<div class="warning">
You should avoid writing new Makefile-based `run-make` tests.
</div>
Each test should be in a separate directory with a `Makefile` indicating the
commands to run.
There is a [`tools.mk`] Makefile which you can include which provides a bunch of
utilities to make it easier to run commands and compare outputs. Take a look at
some of the other tests for some examples on how to get started.
[`tools.mk`]: https://github.com/rust-lang/rust/blob/master/tests/run-make/tools.mk
[`tests/run-make`]: https://github.com/rust-lang/rust/tree/master/tests/run-make
[`run_make_support`]: https://github.com/rust-lang/rust/tree/master/src/tools/run-make-support

View file

@ -6,10 +6,7 @@
FIXME(jieyouxu) completely revise this chapter.
-->
Directives are special comments that tell compiletest how to build and interpret
a test. They must appear before the Rust source in the test. They may also
appear in `rmake.rs` or legacy Makefiles for [run-make
tests](compiletest.md#run-make-tests).
Directives are special comments that tell compiletest how to build and interpret a test. They must appear before the Rust source in the test. They may also appear in `rmake.rs` [run-make tests](compiletest.md#run-make-tests).
They are normally put after the short comment that explains the point of this
test. Compiletest test suites use `//@` to signal that a comment is a directive.
@ -222,8 +219,6 @@ The following directives will check LLVM support:
[`aarch64-gnu-debug`]), which only runs a
subset of `run-make` tests. Other tests with this directive will not
run at all, which is usually not what you want.
- Notably, the [`aarch64-gnu-debug`] CI job *currently* only runs `run-make`
tests which additionally contain `clang` in their test name.
See also [Debuginfo tests](compiletest.md#debuginfo-tests) for directives for
ignoring debuggers.

View file

@ -238,30 +238,6 @@ This is much faster, but doesn't always work. For example, some tests include
directives that specify specific compiler flags, or which rely on other crates,
and they may not run the same without those options.
## Running `run-make` tests
### Windows
Running the `run-make` test suite on Windows is a currently bit more involved.
There are numerous prerequisites and environmental requirements:
- Install msys2: <https://www.msys2.org/>
- Specify `MSYS2_PATH_TYPE=inherit` in `msys2.ini` in the msys2 installation directory, run the
following with `MSYS2 MSYS`:
- `pacman -Syuu`
- `pacman -S make`
- `pacman -S diffutils`
- `pacman -S binutils`
- `./x test run-make` (`./x test tests/run-make` doesn't work)
There is [on-going work][port-run-make] to not rely on `Makefile`s in the
run-make test suite. Once this work is completed, you can run the entire
`run-make` test suite on native Windows inside `cmd` or `PowerShell` without
needing to install and use MSYS2. As of <!--date-check --> Oct 2024, it is
already possible to run the vast majority of the `run-make` test suite outside
of MSYS2, but there will be failures for the tests that still use `Makefile`s
due to not finding `make`.
## Running tests on a remote machine
Tests may be run on a remote machine (e.g. to test builds for a different
@ -406,4 +382,3 @@ If you encounter bugs or problems, don't hesitate to open issues on the
repository](https://github.com/rust-lang/rustc_codegen_gcc/).
[`tests/ui`]: https://github.com/rust-lang/rust/tree/master/tests/ui
[port-run-make]: https://github.com/rust-lang/rust/issues/121876

View file

@ -1,27 +0,0 @@
# `unsized_tuple_coercion`
The tracking issue for this feature is: [#42877]
[#42877]: https://github.com/rust-lang/rust/issues/42877
------------------------
This is a part of [RFC0401]. According to the RFC, there should be an implementation like this:
```rust,ignore (partial-example)
impl<..., T, U: ?Sized> Unsized<(..., U)> for (..., T) where T: Unsized<U> {}
```
This implementation is currently gated behind `#[feature(unsized_tuple_coercion)]` to avoid insta-stability. Therefore you can use it like this:
```rust
#![feature(unsized_tuple_coercion)]
fn main() {
let x : ([i32; 3], [i32; 3]) = ([1, 2, 3], [4, 5, 6]);
let y : &([i32; 3], [i32]) = &x;
assert_eq!(y.1[0], 4);
}
```
[RFC0401]: https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md

View file

@ -147,12 +147,12 @@ pub trait Float:
}
macro_rules! impl_float {
($($fty:ty, $ity:ty, $bits:literal);+) => {
($($fty:ty, $ity:ty);+) => {
$(
impl Float for $fty {
type Int = $ity;
type SInt = <Self::Int as Int>::Signed;
const BITS: u32 = $bits;
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);
@ -168,7 +168,7 @@ macro_rules! impl_float {
}
}
impl_float!(f32, u32, 32; f64, u64, 64);
impl_float!(f32, u32; f64, u64);
/// A test generator. Should provide an iterator that produces unique patterns to parse.
///

View file

@ -709,11 +709,11 @@ impl TestProps {
/// returns a struct containing various parts of the directive.
fn line_directive<'line>(
line_number: usize,
comment: &str,
original_line: &'line str,
) -> Option<DirectiveLine<'line>> {
// Ignore lines that don't start with the comment prefix.
let after_comment = original_line.trim_start().strip_prefix(comment)?.trim_start();
let after_comment =
original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start();
let revision;
let raw_directive;
@ -722,7 +722,7 @@ fn line_directive<'line>(
// A comment like `//@[foo]` only applies to revision `foo`.
let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
panic!(
"malformed condition directive: expected `{comment}[foo]`, found `{original_line}`"
"malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`"
)
};
@ -836,6 +836,8 @@ pub(crate) fn check_directive<'a>(
CheckDirectiveResult { is_known_directive: is_known(&directive_name), trailing_directive }
}
const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
fn iter_header(
mode: Mode,
_suite: &str,
@ -849,8 +851,7 @@ fn iter_header(
}
// Coverage tests in coverage-run mode always have these extra directives, without needing to
// specify them manually in every test file. (Some of the comments below have been copied over
// from the old `tests/run-make/coverage-reports/Makefile`, which no longer exists.)
// specify them manually in every test file.
//
// FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
if mode == Mode::CoverageRun {
@ -867,9 +868,6 @@ fn iter_header(
}
}
// NOTE(jieyouxu): once we get rid of `Makefile`s we can unconditionally check for `//@`.
let comment = if testfile.extension().is_some_and(|e| e == "rs") { "//@" } else { "#" };
let mut rdr = BufReader::with_capacity(1024, rdr);
let mut ln = String::new();
let mut line_number = 0;
@ -882,7 +880,7 @@ fn iter_header(
}
let ln = ln.trim();
let Some(directive_line) = line_directive(line_number, comment, ln) else {
let Some(directive_line) = line_directive(line_number, ln) else {
continue;
};

View file

@ -239,11 +239,6 @@ fn check_ignore(config: &Config, contents: &str) -> bool {
d.ignore
}
fn parse_makefile(config: &Config, contents: &str) -> EarlyProps {
let bytes = contents.as_bytes();
EarlyProps::from_reader(config, Path::new("Makefile"), bytes)
}
#[test]
fn should_fail() {
let config: Config = cfg().build();
@ -261,10 +256,6 @@ fn revisions() {
let config: Config = cfg().build();
assert_eq!(parse_rs(&config, "//@ revisions: a b c").revisions, vec!["a", "b", "c"],);
assert_eq!(
parse_makefile(&config, "# revisions: hello there").revisions,
vec!["hello", "there"],
);
}
#[test]

View file

@ -21,7 +21,7 @@ pub mod util;
use core::panic;
use std::collections::HashSet;
use std::ffi::{OsStr, OsString};
use std::ffi::OsString;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
@ -274,12 +274,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
let path = Path::new(f);
let mut iter = path.iter().skip(1);
// We skip the test folder and check if the user passed `rmake.rs` or `Makefile`.
if iter
.next()
.is_some_and(|s| s == OsStr::new("rmake.rs") || s == OsStr::new("Makefile"))
&& iter.next().is_none()
{
// We skip the test folder and check if the user passed `rmake.rs`.
if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
path.parent().unwrap().to_str().unwrap().to_string()
} else {
f.to_string()
@ -783,16 +779,9 @@ fn collect_tests_from_dir(
return Ok(());
}
// For run-make tests, a "test file" is actually a directory that contains
// an `rmake.rs` or `Makefile`"
// For run-make tests, a "test file" is actually a directory that contains an `rmake.rs`.
if cx.config.mode == Mode::RunMake {
if dir.join("Makefile").exists() && dir.join("rmake.rs").exists() {
return Err(io::Error::other(
"run-make tests cannot have both `Makefile` and `rmake.rs`",
));
}
if dir.join("Makefile").exists() || dir.join("rmake.rs").exists() {
if dir.join("rmake.rs").exists() {
let paths = TestPaths {
file: dir.to_path_buf(),
relative_dir: relative_dir_path.parent().unwrap().to_path_buf(),
@ -861,24 +850,14 @@ pub fn is_test(file_name: &OsString) -> bool {
!invalid_prefixes.iter().any(|p| file_name.starts_with(p))
}
/// For a single test file, creates one or more test structures (one per revision)
/// that can be handed over to libtest to run, possibly in parallel.
/// For a single test file, creates one or more test structures (one per revision) that can be
/// handed over to libtest to run, possibly in parallel.
fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &TestPaths) {
// For run-make tests, each "test file" is actually a _directory_ containing
// an `rmake.rs` or `Makefile`. But for the purposes of directive parsing,
// we want to look at that recipe file, not the directory itself.
// For run-make tests, each "test file" is actually a _directory_ containing an `rmake.rs`. But
// for the purposes of directive parsing, we want to look at that recipe file, not the directory
// itself.
let test_path = if cx.config.mode == Mode::RunMake {
if testpaths.file.join("rmake.rs").exists() && testpaths.file.join("Makefile").exists() {
panic!("run-make tests cannot have both `rmake.rs` and `Makefile`");
}
if testpaths.file.join("rmake.rs").exists() {
// Parse directives in rmake.rs.
testpaths.file.join("rmake.rs")
} else {
// Parse directives in the Makefile.
testpaths.file.join("Makefile")
}
testpaths.file.join("rmake.rs")
} else {
PathBuf::from(&testpaths.file)
};

View file

@ -2412,8 +2412,9 @@ impl<'test> TestCx<'test> {
let rust_src_dir = rust_src_dir.read_link().unwrap_or(rust_src_dir.to_path_buf());
normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
// eg. /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui
normalize_path(&self.config.build_test_suite_root, "$TEST_BUILD_DIR");
// eg.
// /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui/<test_dir>/$name.$revision.$mode/
normalize_path(&self.output_base_dir(), "$TEST_BUILD_DIR");
// eg. /home/user/rust/build
normalize_path(&self.config.build_root, "$BUILD_DIR");
@ -2434,6 +2435,18 @@ impl<'test> TestCx<'test> {
.into_owned();
normalized = Self::normalize_platform_differences(&normalized);
// Normalize long type name hash.
normalized =
static_regex!(r"\$TEST_BUILD_DIR/(?P<filename>[^\.]+).long-type-(?P<hash>\d+).txt")
.replace_all(&normalized, |caps: &Captures<'_>| {
format!(
"$TEST_BUILD_DIR/{filename}.long-type-$LONG_TYPE_HASH.txt",
filename = &caps["filename"]
)
})
.into_owned();
normalized = normalized.replace("\t", "\\t"); // makes tabs visible
// Remove test annotations like `//~ ERROR text` from the output,

View file

@ -9,168 +9,6 @@ use crate::util::{copy_dir_all, dylib_env_var};
impl TestCx<'_> {
pub(super) fn run_rmake_test(&self) {
let test_dir = &self.testpaths.file;
if test_dir.join("rmake.rs").exists() {
self.run_rmake_v2_test();
} else if test_dir.join("Makefile").exists() {
self.run_rmake_legacy_test();
} else {
self.fatal("failed to find either `rmake.rs` or `Makefile`")
}
}
fn run_rmake_legacy_test(&self) {
let cwd = env::current_dir().unwrap();
// FIXME(Zalathar): This should probably be `output_base_dir` to avoid
// an unnecessary extra subdirectory, but since legacy Makefile tests
// are hopefully going away, it seems safer to leave this perilous code
// as-is until it can all be deleted.
let tmpdir = cwd.join(self.output_base_name());
ignore_not_found(|| recursive_remove(&tmpdir)).unwrap();
fs::create_dir_all(&tmpdir).unwrap();
let host = &self.config.host;
let make = if host.contains("dragonfly")
|| host.contains("freebsd")
|| host.contains("netbsd")
|| host.contains("openbsd")
|| host.contains("aix")
{
"gmake"
} else {
"make"
};
let mut cmd = Command::new(make);
cmd.current_dir(&self.testpaths.file)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.env("TARGET", &self.config.target)
.env("PYTHON", &self.config.python)
.env("S", &self.config.src_root)
.env("RUST_BUILD_STAGE", &self.config.stage_id)
.env("RUSTC", cwd.join(&self.config.rustc_path))
.env("TMPDIR", &tmpdir)
.env("LD_LIB_PATH_ENVVAR", dylib_env_var())
.env("HOST_RPATH_DIR", cwd.join(&self.config.compile_lib_path))
.env("TARGET_RPATH_DIR", cwd.join(&self.config.run_lib_path))
.env("LLVM_COMPONENTS", &self.config.llvm_components)
// We for sure don't want these tests to run in parallel, so make
// sure they don't have access to these vars if we run via `make`
// at the top level
.env_remove("MAKEFLAGS")
.env_remove("MFLAGS")
.env_remove("CARGO_MAKEFLAGS");
if let Some(ref cargo) = self.config.cargo_path {
cmd.env("CARGO", cwd.join(cargo));
}
if let Some(ref rustdoc) = self.config.rustdoc_path {
cmd.env("RUSTDOC", cwd.join(rustdoc));
}
if let Some(ref node) = self.config.nodejs {
cmd.env("NODE", node);
}
if let Some(ref linker) = self.config.target_linker {
cmd.env("RUSTC_LINKER", linker);
}
if let Some(ref clang) = self.config.run_clang_based_tests_with {
cmd.env("CLANG", clang);
}
if let Some(ref filecheck) = self.config.llvm_filecheck {
cmd.env("LLVM_FILECHECK", filecheck);
}
if let Some(ref llvm_bin_dir) = self.config.llvm_bin_dir {
cmd.env("LLVM_BIN_DIR", llvm_bin_dir);
}
if let Some(ref remote_test_client) = self.config.remote_test_client {
cmd.env("REMOTE_TEST_CLIENT", remote_test_client);
}
// We don't want RUSTFLAGS set from the outside to interfere with
// compiler flags set in the test cases:
cmd.env_remove("RUSTFLAGS");
// Use dynamic musl for tests because static doesn't allow creating dylibs
if self.config.host.contains("musl") {
cmd.env("RUSTFLAGS", "-Ctarget-feature=-crt-static").env("IS_MUSL_HOST", "1");
}
if self.config.bless {
cmd.env("RUSTC_BLESS_TEST", "--bless");
// Assume this option is active if the environment variable is "defined", with _any_ value.
// As an example, a `Makefile` can use this option by:
//
// ifdef RUSTC_BLESS_TEST
// cp "$(TMPDIR)"/actual_something.ext expected_something.ext
// else
// $(DIFF) expected_something.ext "$(TMPDIR)"/actual_something.ext
// endif
}
if self.config.target.contains("msvc") && !self.config.cc.is_empty() {
// We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe`
// and that `lib.exe` lives next to it.
let lib = Path::new(&self.config.cc).parent().unwrap().join("lib.exe");
// MSYS doesn't like passing flags of the form `/foo` as it thinks it's
// a path and instead passes `C:\msys64\foo`, so convert all
// `/`-arguments to MSVC here to `-` arguments.
let cflags = self
.config
.cflags
.split(' ')
.map(|s| s.replace("/", "-"))
.collect::<Vec<_>>()
.join(" ");
let cxxflags = self
.config
.cxxflags
.split(' ')
.map(|s| s.replace("/", "-"))
.collect::<Vec<_>>()
.join(" ");
cmd.env("IS_MSVC", "1")
.env("IS_WINDOWS", "1")
.env("MSVC_LIB", format!("'{}' -nologo", lib.display()))
.env("MSVC_LIB_PATH", format!("{}", lib.display()))
.env("CC", format!("'{}' {}", self.config.cc, cflags))
.env("CXX", format!("'{}' {}", &self.config.cxx, cxxflags));
} else {
cmd.env("CC", format!("{} {}", self.config.cc, self.config.cflags))
.env("CXX", format!("{} {}", self.config.cxx, self.config.cxxflags))
.env("AR", &self.config.ar);
if self.config.target.contains("windows") {
cmd.env("IS_WINDOWS", "1");
}
}
let (output, truncated) =
self.read2_abbreviated(cmd.spawn().expect("failed to spawn `make`"));
if !output.status.success() {
let res = ProcRes {
status: output.status,
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
truncated,
cmdline: format!("{:?}", cmd),
};
self.fatal_proc_rec("make failed", &res);
}
}
fn run_rmake_v2_test(&self) {
// For `run-make` V2, we need to perform 2 steps to build and run a `run-make` V2 recipe
// (`rmake.rs`) to run the actual tests. The support library is already built as a tool rust
// library and is available under
@ -192,8 +30,6 @@ impl TestCx<'_> {
// recipes to `remove_dir_all($TMPDIR)` without running into issues related trying to remove
// a currently running executable because the recipe executable is not under the
// `rmake_out/` directory.
//
// This setup intentionally diverges from legacy Makefile run-make tests.
let base_dir = self.output_base_dir();
ignore_not_found(|| recursive_remove(&base_dir)).unwrap();

View file

@ -1,22 +1,8 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
#![feature(unsized_tuple_coercion)]
#![feature(unsized_fn_params)]
#![feature(custom_mir, core_intrinsics)]
use std::mem;
fn unsized_tuple() {
let x: &(i32, i32, [i32]) = &(0, 1, [2, 3]);
let y: &(i32, i32, [i32]) = &(0, 1, [2, 3, 4]);
let mut a = [y, x];
a.sort();
assert_eq!(a, [x, y]);
assert_eq!(&format!("{:?}", a), "[(0, 1, [2, 3]), (0, 1, [2, 3, 4])]");
assert_eq!(mem::size_of_val(x), 16);
}
fn unsized_params() {
pub fn f0(_f: dyn FnOnce()) {}
pub fn f1(_s: str) {}
@ -56,7 +42,6 @@ fn unsized_field_projection() {
}
fn main() {
unsized_tuple();
unsized_params();
unsized_field_projection();
}

View file

@ -8,23 +8,6 @@ use crate::targets::is_msvc;
/// Construct the static library name based on the target.
#[must_use]
pub fn static_lib_name(name: &str) -> String {
// See tools.mk (irrelevant lines omitted):
//
// ```makefile
// ifeq ($(UNAME),Darwin)
// STATICLIB = $(TMPDIR)/lib$(1).a
// else
// ifdef IS_WINDOWS
// ifdef IS_MSVC
// STATICLIB = $(TMPDIR)/$(1).lib
// else
// STATICLIB = $(TMPDIR)/lib$(1).a
// endif
// else
// STATICLIB = $(TMPDIR)/lib$(1).a
// endif
// endif
// ```
assert!(!name.contains(char::is_whitespace), "static library name cannot contain whitespace");
if is_msvc() { format!("{name}.lib") } else { format!("lib{name}.a") }

View file

@ -80,17 +80,6 @@ impl Cc {
/// Specify `-o` or `-Fe`/`-Fo` depending on platform/compiler.
pub fn out_exe(&mut self, name: &str) -> &mut Self {
// Ref: tools.mk (irrelevant lines omitted):
//
// ```makefile
// ifdef IS_MSVC
// OUT_EXE=-Fe:`cygpath -w $(TMPDIR)/$(call BIN,$(1))` \
// -Fo:`cygpath -w $(TMPDIR)/$(1).obj`
// else
// OUT_EXE=-o $(TMPDIR)/$(1)
// endif
// ```
let mut path = std::path::PathBuf::from(name);
if is_msvc() {

Some files were not shown because too many files have changed in this diff Show more