diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 192e458775a93..82236d2e30678 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -398,7 +398,8 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { &self, exclusive_range_pattern, pattern.span, - "exclusive range pattern syntax is experimental" + "exclusive range pattern syntax is experimental", + "use an inclusive range pattern, like N..=M" ); } _ => {} diff --git a/compiler/rustc_hir_analysis/src/astconv/bounds.rs b/compiler/rustc_hir_analysis/src/astconv/bounds.rs index 401efff9242e9..3aad510f3660f 100644 --- a/compiler/rustc_hir_analysis/src/astconv/bounds.rs +++ b/compiler/rustc_hir_analysis/src/astconv/bounds.rs @@ -329,7 +329,7 @@ impl<'tcx> dyn AstConv<'tcx> + '_ { } let projection_ty = if let ty::AssocKind::Fn = assoc_kind { - let mut emitted_bad_param_err = false; + let mut emitted_bad_param_err = None; // If we have an method return type bound, then we need to substitute // the method's early bound params with suitable late-bound params. let mut num_bound_vars = candidate.bound_vars().len(); @@ -346,46 +346,30 @@ impl<'tcx> dyn AstConv<'tcx> + '_ { ) .into(), ty::GenericParamDefKind::Type { .. } => { - if !emitted_bad_param_err { + let guar = *emitted_bad_param_err.get_or_insert_with(|| { tcx.dcx().emit_err( crate::errors::ReturnTypeNotationIllegalParam::Type { span: path_span, param_span: tcx.def_span(param.def_id), }, - ); - emitted_bad_param_err = true; - } - Ty::new_bound( - tcx, - ty::INNERMOST, - ty::BoundTy { - var: ty::BoundVar::from_usize(num_bound_vars), - kind: ty::BoundTyKind::Param(param.def_id, param.name), - }, - ) - .into() + ) + }); + Ty::new_error(tcx, guar).into() } ty::GenericParamDefKind::Const { .. } => { - if !emitted_bad_param_err { + let guar = *emitted_bad_param_err.get_or_insert_with(|| { tcx.dcx().emit_err( crate::errors::ReturnTypeNotationIllegalParam::Const { span: path_span, param_span: tcx.def_span(param.def_id), }, - ); - emitted_bad_param_err = true; - } + ) + }); let ty = tcx .type_of(param.def_id) .no_bound_vars() .expect("ct params cannot have early bound vars"); - ty::Const::new_bound( - tcx, - ty::INNERMOST, - ty::BoundVar::from_usize(num_bound_vars), - ty, - ) - .into() + ty::Const::new_error(tcx, guar, ty).into() } }; num_bound_vars += 1; diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index e917e7cb02b39..e6d837ecd92bd 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -2755,6 +2755,11 @@ declare_lint! { pub UNSAFE_OP_IN_UNSAFE_FN, Allow, "unsafe operations in unsafe functions without an explicit unsafe block are deprecated", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), + reference: "issue #71668 ", + explain_reason: false + }; @edition Edition2024 => Warn; } @@ -4604,3 +4609,50 @@ declare_lint! { reference: "issue #X ", }; } + +declare_lint! { + /// The `private_macro_use` lint detects private macros that are imported + /// with `#[macro_use]`. + /// + /// ### Example + /// + /// ```rust,ignore (needs extern crate) + /// // extern_macro.rs + /// macro_rules! foo_ { () => {}; } + /// use foo_ as foo; + /// + /// // code.rs + /// + /// #![deny(private_macro_use)] + /// + /// #[macro_use] + /// extern crate extern_macro; + /// + /// fn main() { + /// foo!(); + /// } + /// ``` + /// + /// This will produce: + /// + /// ```text + /// error: cannot find macro `foo` in this scope + /// ``` + /// + /// ### Explanation + /// + /// This lint arises from overlooking visibility checks for macros + /// in an external crate. + /// + /// This is a [future-incompatible] lint to transition this to a + /// hard error in the future. + /// + /// [future-incompatible]: ../index.md#future-incompatible-lints + pub PRIVATE_MACRO_USE, + Warn, + "detects certain macro bindings that should not be re-exported", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps, + reference: "issue #120192 ", + }; +} diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index e3cc21cef11c7..e1b998b24718c 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -430,7 +430,7 @@ impl AddToDiagnostic for UnsafeNotInheritedLintNote { diag.tool_only_multipart_suggestion( fluent::mir_build_wrap_suggestion, vec![(body_start, "{ unsafe ".into()), (body_end, "}".into())], - Applicability::MaybeIncorrect, + Applicability::MachineApplicable, ); } } diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index a4e2f9e3ff8cd..b855ec8f920b2 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -1029,9 +1029,9 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { } } - let macro_use_import = |this: &Self, span| { + let macro_use_import = |this: &Self, span, warn_private| { this.r.arenas.alloc_import(ImportData { - kind: ImportKind::MacroUse, + kind: ImportKind::MacroUse { warn_private }, root_id: item.id, parent_scope: this.parent_scope, imported_module: Cell::new(Some(ModuleOrUniformRoot::Module(module))), @@ -1048,11 +1048,25 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { let allow_shadowing = self.parent_scope.expansion == LocalExpnId::ROOT; if let Some(span) = import_all { - let import = macro_use_import(self, span); + let import = macro_use_import(self, span, false); self.r.potentially_unused_imports.push(import); module.for_each_child(self, |this, ident, ns, binding| { if ns == MacroNS { - let imported_binding = this.r.import(binding, import); + let imported_binding = + if this.r.is_accessible_from(binding.vis, this.parent_scope.module) { + this.r.import(binding, import) + } else if !this.r.is_builtin_macro(binding.res()) + && !this.r.macro_use_prelude.contains_key(&ident.name) + { + // - `!r.is_builtin_macro(res)` excluding the built-in macros such as `Debug` or `Hash`. + // - `!r.macro_use_prelude.contains_key(name)` excluding macros defined in other extern + // crates such as `std`. + // FIXME: This branch should eventually be removed. + let import = macro_use_import(this, span, true); + this.r.import(binding, import) + } else { + return; + }; this.add_macro_use_binding(ident.name, imported_binding, span, allow_shadowing); } }); @@ -1065,7 +1079,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { &self.parent_scope, ); if let Ok(binding) = result { - let import = macro_use_import(self, ident.span); + let import = macro_use_import(self, ident.span, false); self.r.potentially_unused_imports.push(import); let imported_binding = self.r.import(binding, import); self.add_macro_use_binding( diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index 0e43a35ce7389..fc72d76c3a797 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -290,7 +290,7 @@ impl Resolver<'_, '_> { || import.expect_vis().is_public() || import.span.is_dummy() => { - if let ImportKind::MacroUse = import.kind { + if let ImportKind::MacroUse { .. } = import.kind { if !import.span.is_dummy() { self.lint_buffer.buffer_lint( MACRO_USE_EXTERN_CRATE, @@ -315,7 +315,7 @@ impl Resolver<'_, '_> { maybe_unused_extern_crates.insert(id, import.span); } } - ImportKind::MacroUse => { + ImportKind::MacroUse { .. } => { let msg = "unused `#[macro_use]` import"; self.lint_buffer.buffer_lint(UNUSED_IMPORTS, import.root_id, import.span, msg); } diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 4a0c522b6ec5d..66dfee6c062d8 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -285,7 +285,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { use NameBindingKind::Import; let can_suggest = |binding: NameBinding<'_>, import: self::Import<'_>| { !binding.span.is_dummy() - && !matches!(import.kind, ImportKind::MacroUse | ImportKind::MacroExport) + && !matches!(import.kind, ImportKind::MacroUse { .. } | ImportKind::MacroExport) }; let import = match (&new_binding.kind, &old_binding.kind) { // If there are two imports where one or both have attributes then prefer removing the @@ -1819,9 +1819,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { next_ident = source; Some(binding) } - ImportKind::Glob { .. } | ImportKind::MacroUse | ImportKind::MacroExport => { - Some(binding) - } + ImportKind::Glob { .. } + | ImportKind::MacroUse { .. } + | ImportKind::MacroExport => Some(binding), ImportKind::ExternCrate { .. } => None, }, _ => None, diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index f846dbec2c697..ad60caed354fc 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -80,7 +80,11 @@ pub(crate) enum ImportKind<'a> { target: Ident, id: NodeId, }, - MacroUse, + MacroUse { + /// A field has been added indicating whether it should be reported as a lint, + /// addressing issue#119301. + warn_private: bool, + }, MacroExport, } @@ -127,7 +131,7 @@ impl<'a> std::fmt::Debug for ImportKind<'a> { .field("target", target) .field("id", id) .finish(), - MacroUse => f.debug_struct("MacroUse").finish(), + MacroUse { .. } => f.debug_struct("MacroUse").finish(), MacroExport => f.debug_struct("MacroExport").finish(), } } @@ -197,7 +201,7 @@ impl<'a> ImportData<'a> { ImportKind::Single { id, .. } | ImportKind::Glob { id, .. } | ImportKind::ExternCrate { id, .. } => Some(id), - ImportKind::MacroUse | ImportKind::MacroExport => None, + ImportKind::MacroUse { .. } | ImportKind::MacroExport => None, } } @@ -207,7 +211,7 @@ impl<'a> ImportData<'a> { ImportKind::Single { id, .. } => Reexport::Single(to_def_id(id)), ImportKind::Glob { id, .. } => Reexport::Glob(to_def_id(id)), ImportKind::ExternCrate { id, .. } => Reexport::ExternCrate(to_def_id(id)), - ImportKind::MacroUse => Reexport::MacroUse, + ImportKind::MacroUse { .. } => Reexport::MacroUse, ImportKind::MacroExport => Reexport::MacroExport, } } @@ -1482,7 +1486,7 @@ fn import_kind_to_string(import_kind: &ImportKind<'_>) -> String { ImportKind::Single { source, .. } => source.to_string(), ImportKind::Glob { .. } => "*".to_string(), ImportKind::ExternCrate { .. } => "".to_string(), - ImportKind::MacroUse => "#[macro_use]".to_string(), + ImportKind::MacroUse { .. } => "#[macro_use]".to_string(), ImportKind::MacroExport => "#[macro_export]".to_string(), } } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 506612a538220..1c00f545eb82b 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -55,6 +55,7 @@ use rustc_middle::span_bug; use rustc_middle::ty::{self, MainDefinition, RegisteredTools, TyCtxt}; use rustc_middle::ty::{ResolverGlobalCtxt, ResolverOutputs}; use rustc_query_system::ich::StableHashingContext; +use rustc_session::lint::builtin::PRIVATE_MACRO_USE; use rustc_session::lint::LintBuffer; use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind, SyntaxContext, Transparency}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; @@ -1799,6 +1800,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } if let NameBindingKind::Import { import, binding, ref used } = used_binding.kind { + if let ImportKind::MacroUse { warn_private: true } = import.kind { + let msg = format!("macro `{ident}` is private"); + self.lint_buffer().buffer_lint(PRIVATE_MACRO_USE, import.root_id, ident.span, msg); + } // Avoid marking `extern crate` items that refer to a name from extern prelude, // but not introduce it, as used if they are accessed from lexical scope. if is_lexical_scope { diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 24ad78efa83b1..1dc3f49df38bf 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -185,6 +185,7 @@ #![feature(set_ptr_value)] #![feature(slice_ptr_get)] #![feature(slice_split_at_unchecked)] +#![feature(split_at_checked)] #![feature(str_internals)] #![feature(str_split_inclusive_remainder)] #![feature(str_split_remainder)] diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index c191877c434f2..6d0bb924073ce 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -1842,7 +1842,8 @@ impl [T] { /// /// # Panics /// - /// Panics if `mid > len`. + /// Panics if `mid > len`. For a non-panicking alternative see + /// [`split_at_checked`](slice::split_at_checked). /// /// # Examples /// @@ -1869,14 +1870,15 @@ impl [T] { /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_slice_split_at_not_mut", since = "1.71.0")] + #[rustc_allow_const_fn_unstable(split_at_checked)] #[inline] #[track_caller] #[must_use] pub const fn split_at(&self, mid: usize) -> (&[T], &[T]) { - assert!(mid <= self.len()); - // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which - // fulfills the requirements of `split_at_unchecked`. - unsafe { self.split_at_unchecked(mid) } + match self.split_at_checked(mid) { + Some(pair) => pair, + None => panic!("mid > len"), + } } /// Divides one mutable slice into two at an index. @@ -1887,7 +1889,8 @@ impl [T] { /// /// # Panics /// - /// Panics if `mid > len`. + /// Panics if `mid > len`. For a non-panicking alternative see + /// [`split_at_mut_checked`](slice::split_at_mut_checked). /// /// # Examples /// @@ -1906,10 +1909,10 @@ impl [T] { #[must_use] #[rustc_const_unstable(feature = "const_slice_split_at_mut", issue = "101804")] pub const fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) { - assert!(mid <= self.len()); - // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which - // fulfills the requirements of `from_raw_parts_mut`. - unsafe { self.split_at_mut_unchecked(mid) } + match self.split_at_mut_checked(mid) { + Some(pair) => pair, + None => panic!("mid > len"), + } } /// Divides one slice into two at an index, without doing bounds checking. @@ -2031,6 +2034,98 @@ impl [T] { unsafe { (from_raw_parts_mut(ptr, mid), from_raw_parts_mut(ptr.add(mid), len - mid)) } } + /// Divides one slice into two at an index, returning `None` if the slice is + /// too short. + /// + /// If `mid ≤ len` returns a pair of slices where the first will contain all + /// indices from `[0, mid)` (excluding the index `mid` itself) and the + /// second will contain all indices from `[mid, len)` (excluding the index + /// `len` itself). + /// + /// Otherwise, if `mid > len`, returns `None`. + /// + /// # Examples + /// + /// ``` + /// #![feature(split_at_checked)] + /// + /// let v = [1, -2, 3, -4, 5, -6]; + /// + /// { + /// let (left, right) = v.split_at_checked(0).unwrap(); + /// assert_eq!(left, []); + /// assert_eq!(right, [1, -2, 3, -4, 5, -6]); + /// } + /// + /// { + /// let (left, right) = v.split_at_checked(2).unwrap(); + /// assert_eq!(left, [1, -2]); + /// assert_eq!(right, [3, -4, 5, -6]); + /// } + /// + /// { + /// let (left, right) = v.split_at_checked(6).unwrap(); + /// assert_eq!(left, [1, -2, 3, -4, 5, -6]); + /// assert_eq!(right, []); + /// } + /// + /// assert_eq!(None, v.split_at_checked(7)); + /// ``` + #[unstable(feature = "split_at_checked", reason = "new API", issue = "119128")] + #[rustc_const_unstable(feature = "split_at_checked", issue = "119128")] + #[inline] + #[must_use] + pub const fn split_at_checked(&self, mid: usize) -> Option<(&[T], &[T])> { + if mid <= self.len() { + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `split_at_unchecked`. + Some(unsafe { self.split_at_unchecked(mid) }) + } else { + None + } + } + + /// Divides one mutable slice into two at an index, returning `None` if the + /// slice is too short. + /// + /// If `mid ≤ len` returns a pair of slices where the first will contain all + /// indices from `[0, mid)` (excluding the index `mid` itself) and the + /// second will contain all indices from `[mid, len)` (excluding the index + /// `len` itself). + /// + /// Otherwise, if `mid > len`, returns `None`. + /// + /// # Examples + /// + /// ``` + /// #![feature(split_at_checked)] + /// + /// let mut v = [1, 0, 3, 0, 5, 6]; + /// + /// if let Some((left, right)) = v.split_at_mut_checked(2) { + /// assert_eq!(left, [1, 0]); + /// assert_eq!(right, [3, 0, 5, 6]); + /// left[1] = 2; + /// right[1] = 4; + /// } + /// assert_eq!(v, [1, 2, 3, 4, 5, 6]); + /// + /// assert_eq!(None, v.split_at_mut_checked(7)); + /// ``` + #[unstable(feature = "split_at_checked", reason = "new API", issue = "119128")] + #[rustc_const_unstable(feature = "split_at_checked", issue = "119128")] + #[inline] + #[must_use] + pub const fn split_at_mut_checked(&mut self, mid: usize) -> Option<(&mut [T], &mut [T])> { + if mid <= self.len() { + // SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which + // fulfills the requirements of `split_at_unchecked`. + Some(unsafe { self.split_at_mut_unchecked(mid) }) + } else { + None + } + } + /// Returns an iterator over subslices separated by elements that match /// `pred`. The matched element is not contained in the subslices. /// diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index a22c46edce254..80c5fe0de8da7 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -641,8 +641,9 @@ impl str { /// /// # Panics /// - /// Panics if `mid` is not on a UTF-8 code point boundary, or if it is - /// past the end of the last code point of the string slice. + /// Panics if `mid` is not on a UTF-8 code point boundary, or if it is past + /// the end of the last code point of the string slice. For a non-panicking + /// alternative see [`split_at_checked`](str::split_at_checked). /// /// # Examples /// @@ -658,12 +659,9 @@ impl str { #[must_use] #[stable(feature = "str_split_at", since = "1.4.0")] pub fn split_at(&self, mid: usize) -> (&str, &str) { - // is_char_boundary checks that the index is in [0, .len()] - if self.is_char_boundary(mid) { - // SAFETY: just checked that `mid` is on a char boundary. - unsafe { (self.get_unchecked(0..mid), self.get_unchecked(mid..self.len())) } - } else { - slice_error_fail(self, 0, mid) + match self.split_at_checked(mid) { + None => slice_error_fail(self, 0, mid), + Some(pair) => pair, } } @@ -681,8 +679,9 @@ impl str { /// /// # Panics /// - /// Panics if `mid` is not on a UTF-8 code point boundary, or if it is - /// past the end of the last code point of the string slice. + /// Panics if `mid` is not on a UTF-8 code point boundary, or if it is past + /// the end of the last code point of the string slice. For a non-panicking + /// alternative see [`split_at_mut_checked`](str::split_at_mut_checked). /// /// # Examples /// @@ -702,20 +701,114 @@ impl str { pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str) { // is_char_boundary checks that the index is in [0, .len()] if self.is_char_boundary(mid) { - let len = self.len(); - let ptr = self.as_mut_ptr(); // SAFETY: just checked that `mid` is on a char boundary. - unsafe { - ( - from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr, mid)), - from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr.add(mid), len - mid)), - ) - } + unsafe { self.split_at_mut_unchecked(mid) } } else { slice_error_fail(self, 0, mid) } } + /// Divide one string slice into two at an index. + /// + /// The argument, `mid`, should be a valid byte offset from the start of the + /// string. It must also be on the boundary of a UTF-8 code point. The + /// method returns `None` if that’s not the case. + /// + /// The two slices returned go from the start of the string slice to `mid`, + /// and from `mid` to the end of the string slice. + /// + /// To get mutable string slices instead, see the [`split_at_mut_checked`] + /// method. + /// + /// [`split_at_mut_checked`]: str::split_at_mut_checked + /// + /// # Examples + /// + /// ``` + /// #![feature(split_at_checked)] + /// + /// let s = "Per Martin-Löf"; + /// + /// let (first, last) = s.split_at_checked(3).unwrap(); + /// assert_eq!("Per", first); + /// assert_eq!(" Martin-Löf", last); + /// + /// assert_eq!(None, s.split_at_checked(13)); // Inside “ö” + /// assert_eq!(None, s.split_at_checked(16)); // Beyond the string length + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "split_at_checked", reason = "new API", issue = "119128")] + pub fn split_at_checked(&self, mid: usize) -> Option<(&str, &str)> { + // is_char_boundary checks that the index is in [0, .len()] + if self.is_char_boundary(mid) { + // SAFETY: just checked that `mid` is on a char boundary. + Some(unsafe { (self.get_unchecked(0..mid), self.get_unchecked(mid..self.len())) }) + } else { + None + } + } + + /// Divide one mutable string slice into two at an index. + /// + /// The argument, `mid`, should be a valid byte offset from the start of the + /// string. It must also be on the boundary of a UTF-8 code point. The + /// method returns `None` if that’s not the case. + /// + /// The two slices returned go from the start of the string slice to `mid`, + /// and from `mid` to the end of the string slice. + /// + /// To get immutable string slices instead, see the [`split_at_checked`] method. + /// + /// [`split_at_checked`]: str::split_at_checked + /// + /// # Examples + /// + /// ``` + /// #![feature(split_at_checked)] + /// + /// let mut s = "Per Martin-Löf".to_string(); + /// if let Some((first, last)) = s.split_at_mut_checked(3) { + /// first.make_ascii_uppercase(); + /// assert_eq!("PER", first); + /// assert_eq!(" Martin-Löf", last); + /// } + /// assert_eq!("PER Martin-Löf", s); + /// + /// assert_eq!(None, s.split_at_mut_checked(13)); // Inside “ö” + /// assert_eq!(None, s.split_at_mut_checked(16)); // Beyond the string length + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "split_at_checked", reason = "new API", issue = "119128")] + pub fn split_at_mut_checked(&mut self, mid: usize) -> Option<(&mut str, &mut str)> { + // is_char_boundary checks that the index is in [0, .len()] + if self.is_char_boundary(mid) { + // SAFETY: just checked that `mid` is on a char boundary. + Some(unsafe { self.split_at_mut_unchecked(mid) }) + } else { + None + } + } + + /// Divide one string slice into two at an index. + /// + /// # Safety + /// + /// The caller must ensure that `mid` is a valid byte offset from the start + /// of the string and falls on the boundary of a UTF-8 code point. + unsafe fn split_at_mut_unchecked(&mut self, mid: usize) -> (&mut str, &mut str) { + let len = self.len(); + let ptr = self.as_mut_ptr(); + // SAFETY: caller guarantees `mid` is on a char boundary. + unsafe { + ( + from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr, mid)), + from_utf8_unchecked_mut(slice::from_raw_parts_mut(ptr.add(mid), len - mid)), + ) + } + } + /// Returns an iterator over the [`char`]s of a string slice. /// /// As a string slice consists of valid UTF-8, we can iterate through a diff --git a/library/std/src/os/unix/net/listener.rs b/library/std/src/os/unix/net/listener.rs index 8bf1e2dca6f7b..ecc0bbce5437a 100644 --- a/library/std/src/os/unix/net/listener.rs +++ b/library/std/src/os/unix/net/listener.rs @@ -80,7 +80,12 @@ impl UnixListener { target_os = "horizon" ))] const backlog: libc::c_int = 128; - #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))] + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd", + target_os = "macos" + ))] const backlog: libc::c_int = -1; #[cfg(not(any( target_os = "windows", @@ -88,6 +93,7 @@ impl UnixListener { target_os = "linux", target_os = "freebsd", target_os = "openbsd", + target_os = "macos", target_os = "espidf", target_os = "horizon" )))] diff --git a/library/std/src/os/xous/ffi.rs b/library/std/src/os/xous/ffi.rs index 8be7fbb102f3e..7fe84db515c34 100644 --- a/library/std/src/os/xous/ffi.rs +++ b/library/std/src/os/xous/ffi.rs @@ -88,29 +88,31 @@ fn lend_impl( let a3 = opcode; let a4 = data.as_ptr() as usize; let a5 = data.len(); - let mut a6 = arg1; - let mut a7 = arg2; + let a6 = arg1; + let a7 = arg2; + let mut ret1; + let mut ret2; unsafe { core::arch::asm!( "ecall", inlateout("a0") a0, - inlateout("a1") a1 => _, - inlateout("a2") a2 => _, + inlateout("a1") a1 => ret1, + inlateout("a2") a2 => ret2, inlateout("a3") a3 => _, inlateout("a4") a4 => _, inlateout("a5") a5 => _, - inlateout("a6") a6, - inlateout("a7") a7, + inlateout("a6") a6 => _, + inlateout("a7") a7 => _, ) }; let result = a0; if result == SyscallResult::MemoryReturned as usize { - Ok((a6, a7)) + Ok((ret1, ret2)) } else if result == SyscallResult::Error as usize { - Err(a1.into()) + Err(ret1.into()) } else { Err(Error::InternalError) } @@ -405,7 +407,7 @@ pub(crate) unsafe fn map_memory( pub(crate) unsafe fn unmap_memory(range: *mut [T]) -> Result<(), Error> { let mut a0 = Syscall::UnmapMemory as usize; let mut a1 = range.as_mut_ptr() as usize; - let a2 = range.len(); + let a2 = range.len() * core::mem::size_of::(); let a3 = 0; let a4 = 0; let a5 = 0; @@ -450,7 +452,7 @@ pub(crate) unsafe fn update_memory_flags( ) -> Result<(), Error> { let mut a0 = Syscall::UpdateMemoryFlags as usize; let mut a1 = range.as_mut_ptr() as usize; - let a2 = range.len(); + let a2 = range.len() * core::mem::size_of::(); let a3 = new_flags.bits(); let a4 = 0; // Process ID is currently None let a5 = 0; diff --git a/library/std/src/os/xous/services.rs b/library/std/src/os/xous/services.rs index 5c219f1fbb95e..a75be1b857003 100644 --- a/library/std/src/os/xous/services.rs +++ b/library/std/src/os/xous/services.rs @@ -1,9 +1,15 @@ use crate::os::xous::ffi::Connection; use core::sync::atomic::{AtomicU32, Ordering}; +mod dns; +pub(crate) use dns::*; + mod log; pub(crate) use log::*; +mod net; +pub(crate) use net::*; + mod systime; pub(crate) use systime::*; diff --git a/library/std/src/os/xous/services/dns.rs b/library/std/src/os/xous/services/dns.rs new file mode 100644 index 0000000000000..a7d88f4892cda --- /dev/null +++ b/library/std/src/os/xous/services/dns.rs @@ -0,0 +1,28 @@ +use crate::os::xous::ffi::Connection; +use crate::os::xous::services::connect; +use core::sync::atomic::{AtomicU32, Ordering}; + +#[repr(usize)] +pub(crate) enum DnsLendMut { + RawLookup = 6, +} + +impl Into for DnsLendMut { + fn into(self) -> usize { + self as usize + } +} + +/// Return a `Connection` to the DNS lookup server. This server is used for +/// querying domain name values. +pub(crate) fn dns_server() -> Connection { + static DNS_CONNECTION: AtomicU32 = AtomicU32::new(0); + let cid = DNS_CONNECTION.load(Ordering::Relaxed); + if cid != 0 { + return cid.into(); + } + + let cid = connect("_DNS Resolver Middleware_").unwrap(); + DNS_CONNECTION.store(cid.into(), Ordering::Relaxed); + cid +} diff --git a/library/std/src/os/xous/services/log.rs b/library/std/src/os/xous/services/log.rs index e6bae929eac0e..55a501dc7d00e 100644 --- a/library/std/src/os/xous/services/log.rs +++ b/library/std/src/os/xous/services/log.rs @@ -45,6 +45,17 @@ impl<'a> Into<[usize; 5]> for LogScalar<'a> { } } +pub(crate) enum LogLend { + StandardOutput = 1, + StandardError = 2, +} + +impl Into for LogLend { + fn into(self) -> usize { + self as usize + } +} + /// Return a `Connection` to the log server, which is used for printing messages to /// the console and reporting panics. If the log server has not yet started, this /// will block until the server is running. It is safe to call this multiple times, diff --git a/library/std/src/os/xous/services/net.rs b/library/std/src/os/xous/services/net.rs new file mode 100644 index 0000000000000..26d337dcef168 --- /dev/null +++ b/library/std/src/os/xous/services/net.rs @@ -0,0 +1,95 @@ +use crate::os::xous::ffi::Connection; +use crate::os::xous::services::connect; +use core::sync::atomic::{AtomicU32, Ordering}; + +pub(crate) enum NetBlockingScalar { + StdGetTtlUdp(u16 /* fd */), /* 36 */ + StdSetTtlUdp(u16 /* fd */, u32 /* ttl */), /* 37 */ + StdGetTtlTcp(u16 /* fd */), /* 36 */ + StdSetTtlTcp(u16 /* fd */, u32 /* ttl */), /* 37 */ + StdGetNodelay(u16 /* fd */), /* 38 */ + StdSetNodelay(u16 /* fd */, bool), /* 39 */ + StdTcpClose(u16 /* fd */), /* 34 */ + StdUdpClose(u16 /* fd */), /* 41 */ + StdTcpStreamShutdown(u16 /* fd */, crate::net::Shutdown /* how */), /* 46 */ +} + +pub(crate) enum NetLendMut { + StdTcpConnect, /* 30 */ + StdTcpTx(u16 /* fd */), /* 31 */ + StdTcpPeek(u16 /* fd */, bool /* nonblocking */), /* 32 */ + StdTcpRx(u16 /* fd */, bool /* nonblocking */), /* 33 */ + StdGetAddress(u16 /* fd */), /* 35 */ + StdUdpBind, /* 40 */ + StdUdpRx(u16 /* fd */), /* 42 */ + StdUdpTx(u16 /* fd */), /* 43 */ + StdTcpListen, /* 44 */ + StdTcpAccept(u16 /* fd */), /* 45 */ +} + +impl Into for NetLendMut { + fn into(self) -> usize { + match self { + NetLendMut::StdTcpConnect => 30, + NetLendMut::StdTcpTx(fd) => 31 | ((fd as usize) << 16), + NetLendMut::StdTcpPeek(fd, blocking) => { + 32 | ((fd as usize) << 16) | if blocking { 0x8000 } else { 0 } + } + NetLendMut::StdTcpRx(fd, blocking) => { + 33 | ((fd as usize) << 16) | if blocking { 0x8000 } else { 0 } + } + NetLendMut::StdGetAddress(fd) => 35 | ((fd as usize) << 16), + NetLendMut::StdUdpBind => 40, + NetLendMut::StdUdpRx(fd) => 42 | ((fd as usize) << 16), + NetLendMut::StdUdpTx(fd) => 43 | ((fd as usize) << 16), + NetLendMut::StdTcpListen => 44, + NetLendMut::StdTcpAccept(fd) => 45 | ((fd as usize) << 16), + } + } +} + +impl<'a> Into<[usize; 5]> for NetBlockingScalar { + fn into(self) -> [usize; 5] { + match self { + NetBlockingScalar::StdGetTtlTcp(fd) => [36 | ((fd as usize) << 16), 0, 0, 0, 0], + NetBlockingScalar::StdGetTtlUdp(fd) => [36 | ((fd as usize) << 16), 0, 0, 0, 1], + NetBlockingScalar::StdSetTtlTcp(fd, ttl) => { + [37 | ((fd as usize) << 16), ttl as _, 0, 0, 0] + } + NetBlockingScalar::StdSetTtlUdp(fd, ttl) => { + [37 | ((fd as usize) << 16), ttl as _, 0, 0, 1] + } + NetBlockingScalar::StdGetNodelay(fd) => [38 | ((fd as usize) << 16), 0, 0, 0, 0], + NetBlockingScalar::StdSetNodelay(fd, enabled) => { + [39 | ((fd as usize) << 16), if enabled { 1 } else { 0 }, 0, 0, 1] + } + NetBlockingScalar::StdTcpClose(fd) => [34 | ((fd as usize) << 16), 0, 0, 0, 0], + NetBlockingScalar::StdUdpClose(fd) => [41 | ((fd as usize) << 16), 0, 0, 0, 0], + NetBlockingScalar::StdTcpStreamShutdown(fd, how) => [ + 46 | ((fd as usize) << 16), + match how { + crate::net::Shutdown::Read => 1, + crate::net::Shutdown::Write => 2, + crate::net::Shutdown::Both => 3, + }, + 0, + 0, + 0, + ], + } + } +} + +/// Return a `Connection` to the Network server. This server provides all +/// OS-level networking functions. +pub(crate) fn net_server() -> Connection { + static NET_CONNECTION: AtomicU32 = AtomicU32::new(0); + let cid = NET_CONNECTION.load(Ordering::Relaxed); + if cid != 0 { + return cid.into(); + } + + let cid = connect("_Middleware Network Server_").unwrap(); + NET_CONNECTION.store(cid.into(), Ordering::Relaxed); + cid +} diff --git a/library/std/src/sys/pal/xous/alloc.rs b/library/std/src/sys/pal/xous/alloc.rs index b3a3e691e0d0c..0d540e9552072 100644 --- a/library/std/src/sys/pal/xous/alloc.rs +++ b/library/std/src/sys/pal/xous/alloc.rs @@ -1,7 +1,15 @@ use crate::alloc::{GlobalAlloc, Layout, System}; +#[cfg(not(test))] +#[export_name = "_ZN16__rust_internals3std3sys4xous5alloc8DLMALLOCE"] static mut DLMALLOC: dlmalloc::Dlmalloc = dlmalloc::Dlmalloc::new(); +#[cfg(test)] +extern "Rust" { + #[link_name = "_ZN16__rust_internals3std3sys4xous5alloc8DLMALLOCE"] + static mut DLMALLOC: dlmalloc::Dlmalloc; +} + #[stable(feature = "alloc_system_type", since = "1.28.0")] unsafe impl GlobalAlloc for System { #[inline] diff --git a/library/std/src/sys/pal/xous/locks/condvar.rs b/library/std/src/sys/pal/xous/locks/condvar.rs index 1bb38dfa34154..510235046e195 100644 --- a/library/std/src/sys/pal/xous/locks/condvar.rs +++ b/library/std/src/sys/pal/xous/locks/condvar.rs @@ -1,14 +1,17 @@ use super::mutex::Mutex; use crate::os::xous::ffi::{blocking_scalar, scalar}; -use crate::os::xous::services::ticktimer_server; -use crate::sync::Mutex as StdMutex; +use crate::os::xous::services::{ticktimer_server, TicktimerScalar}; use crate::time::Duration; +use core::sync::atomic::{AtomicUsize, Ordering}; // The implementation is inspired by Andrew D. Birrell's paper // "Implementing Condition Variables with Semaphores" +const NOTIFY_TRIES: usize = 3; + pub struct Condvar { - counter: StdMutex, + counter: AtomicUsize, + timed_out: AtomicUsize, } unsafe impl Send for Condvar {} @@ -18,94 +21,128 @@ impl Condvar { #[inline] #[rustc_const_stable(feature = "const_locks", since = "1.63.0")] pub const fn new() -> Condvar { - Condvar { counter: StdMutex::new(0) } + Condvar { counter: AtomicUsize::new(0), timed_out: AtomicUsize::new(0) } } - pub fn notify_one(&self) { - let mut counter = self.counter.lock().unwrap(); - if *counter <= 0 { + fn notify_some(&self, to_notify: usize) { + // Assumption: The Mutex protecting this condvar is locked throughout the + // entirety of this call, preventing calls to `wait` and `wait_timeout`. + + // Logic check: Ensure that there aren't any missing waiters. Remove any that + // timed-out, ensuring the counter doesn't underflow. + assert!(self.timed_out.load(Ordering::Relaxed) <= self.counter.load(Ordering::Relaxed)); + self.counter.fetch_sub(self.timed_out.swap(0, Ordering::Relaxed), Ordering::Relaxed); + + // Figure out how many threads to notify. Note that it is impossible for `counter` + // to increase during this operation because Mutex is locked. However, it is + // possible for `counter` to decrease due to a condvar timing out, in which + // case the corresponding `timed_out` will increase accordingly. + let Ok(waiter_count) = + self.counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |counter| { + if counter == 0 { + return None; + } else { + Some(counter - counter.min(to_notify)) + } + }) + else { + // No threads are waiting on this condvar return; - } else { - *counter -= 1; - } - let result = blocking_scalar( - ticktimer_server(), - crate::os::xous::services::TicktimerScalar::NotifyCondition(self.index(), 1).into(), - ); - drop(counter); - result.expect("failure to send NotifyCondition command"); - } + }; - pub fn notify_all(&self) { - let mut counter = self.counter.lock().unwrap(); - if *counter <= 0 { + let mut remaining_to_wake = waiter_count.min(to_notify); + if remaining_to_wake == 0 { return; } - let result = blocking_scalar( - ticktimer_server(), - crate::os::xous::services::TicktimerScalar::NotifyCondition(self.index(), *counter) - .into(), - ); - *counter = 0; - drop(counter); + for _wake_tries in 0..NOTIFY_TRIES { + let result = blocking_scalar( + ticktimer_server(), + TicktimerScalar::NotifyCondition(self.index(), remaining_to_wake).into(), + ) + .expect("failure to send NotifyCondition command"); + + // Remove the list of waiters that were notified + remaining_to_wake -= result[0]; + + // Also remove the number of waiters that timed out. Clamp it to 0 in order to + // ensure we don't wait forever in case the waiter woke up between the time + // we counted the remaining waiters and now. + remaining_to_wake = + remaining_to_wake.saturating_sub(self.timed_out.swap(0, Ordering::Relaxed)); + if remaining_to_wake == 0 { + return; + } + crate::thread::yield_now(); + } + } - result.expect("failure to send NotifyCondition command"); + pub fn notify_one(&self) { + self.notify_some(1) + } + + pub fn notify_all(&self) { + self.notify_some(self.counter.load(Ordering::Relaxed)) } fn index(&self) -> usize { - self as *const Condvar as usize + core::ptr::from_ref(self).addr() } - pub unsafe fn wait(&self, mutex: &Mutex) { - let mut counter = self.counter.lock().unwrap(); - *counter += 1; + /// Unlock the given Mutex and wait for the notification. Wait at most + /// `ms` milliseconds, or pass `0` to wait forever. + /// + /// Returns `true` if the condition was received, `false` if it timed out + fn wait_ms(&self, mutex: &Mutex, ms: usize) -> bool { + self.counter.fetch_add(1, Ordering::Relaxed); unsafe { mutex.unlock() }; - drop(counter); + // Threading concern: There is a chance that the `notify` thread wakes up here before + // we have a chance to wait for the condition. This is fine because we've recorded + // the fact that we're waiting by incrementing the counter. let result = blocking_scalar( ticktimer_server(), - crate::os::xous::services::TicktimerScalar::WaitForCondition(self.index(), 0).into(), + TicktimerScalar::WaitForCondition(self.index(), ms).into(), ); + let awoken = result.expect("Ticktimer: failure to send WaitForCondition command")[0] == 0; + + // If we awoke due to a timeout, increment the `timed_out` counter so that the + // main loop of `notify` knows there's a timeout. + // + // This is done with the Mutex still unlocked, because the Mutex might still + // be locked by the `notify` process above. + if !awoken { + self.timed_out.fetch_add(1, Ordering::Relaxed); + } + unsafe { mutex.lock() }; + awoken + } - result.expect("Ticktimer: failure to send WaitForCondition command"); + pub unsafe fn wait(&self, mutex: &Mutex) { + // Wait for 0 ms, which is a special case to "wait forever" + self.wait_ms(mutex, 0); } pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool { - let mut counter = self.counter.lock().unwrap(); - *counter += 1; - unsafe { mutex.unlock() }; - drop(counter); - let mut millis = dur.as_millis() as usize; + // Ensure we don't wait for 0 ms, which would cause us to wait forever if millis == 0 { millis = 1; } - - let result = blocking_scalar( - ticktimer_server(), - crate::os::xous::services::TicktimerScalar::WaitForCondition(self.index(), millis) - .into(), - ); - unsafe { mutex.lock() }; - - let result = result.expect("Ticktimer: failure to send WaitForCondition command")[0] == 0; - - // If we awoke due to a timeout, decrement the wake count, as that would not have - // been done in the `notify()` call. - if !result { - *self.counter.lock().unwrap() -= 1; - } - result + self.wait_ms(mutex, millis) } } impl Drop for Condvar { fn drop(&mut self) { - scalar( - ticktimer_server(), - crate::os::xous::services::TicktimerScalar::FreeCondition(self.index()).into(), - ) - .ok(); + let remaining_count = self.counter.load(Ordering::Relaxed); + let timed_out = self.timed_out.load(Ordering::Relaxed); + assert!( + remaining_count - timed_out == 0, + "counter was {} and timed_out was {} not 0", + remaining_count, + timed_out + ); + scalar(ticktimer_server(), TicktimerScalar::FreeCondition(self.index()).into()).ok(); } } diff --git a/library/std/src/sys/pal/xous/locks/mutex.rs b/library/std/src/sys/pal/xous/locks/mutex.rs index ea51776d54eca..a8c9518ff0bcf 100644 --- a/library/std/src/sys/pal/xous/locks/mutex.rs +++ b/library/std/src/sys/pal/xous/locks/mutex.rs @@ -1,5 +1,5 @@ -use crate::os::xous::ffi::{blocking_scalar, do_yield, scalar}; -use crate::os::xous::services::ticktimer_server; +use crate::os::xous::ffi::{blocking_scalar, do_yield}; +use crate::os::xous::services::{ticktimer_server, TicktimerScalar}; use crate::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed, Ordering::SeqCst}; pub struct Mutex { @@ -29,7 +29,7 @@ impl Mutex { } fn index(&self) -> usize { - self as *const Mutex as usize + core::ptr::from_ref(self).addr() } #[inline] @@ -83,11 +83,8 @@ impl Mutex { } // Unblock one thread that is waiting on this message. - scalar( - ticktimer_server(), - crate::os::xous::services::TicktimerScalar::UnlockMutex(self.index()).into(), - ) - .expect("failure to send UnlockMutex command"); + blocking_scalar(ticktimer_server(), TicktimerScalar::UnlockMutex(self.index()).into()) + .expect("failure to send UnlockMutex command"); } #[inline] @@ -106,11 +103,8 @@ impl Drop for Mutex { // If there was Mutex contention, then we involved the ticktimer. Free // the resources associated with this Mutex as it is deallocated. if self.contended.load(Relaxed) { - scalar( - ticktimer_server(), - crate::os::xous::services::TicktimerScalar::FreeMutex(self.index()).into(), - ) - .ok(); + blocking_scalar(ticktimer_server(), TicktimerScalar::FreeMutex(self.index()).into()) + .ok(); } } } diff --git a/library/std/src/sys/pal/xous/locks/rwlock.rs b/library/std/src/sys/pal/xous/locks/rwlock.rs index 618da758adfa7..ab45b33e1f69b 100644 --- a/library/std/src/sys/pal/xous/locks/rwlock.rs +++ b/library/std/src/sys/pal/xous/locks/rwlock.rs @@ -1,5 +1,5 @@ -use crate::os::xous::ffi::do_yield; -use crate::sync::atomic::{AtomicIsize, Ordering::SeqCst}; +use crate::sync::atomic::{AtomicIsize, Ordering::Acquire}; +use crate::thread::yield_now; pub struct RwLock { /// The "mode" value indicates how many threads are waiting on this @@ -14,6 +14,9 @@ pub struct RwLock { mode: AtomicIsize, } +const RWLOCK_WRITING: isize = -1; +const RWLOCK_FREE: isize = 0; + unsafe impl Send for RwLock {} unsafe impl Sync for RwLock {} @@ -21,52 +24,51 @@ impl RwLock { #[inline] #[rustc_const_stable(feature = "const_locks", since = "1.63.0")] pub const fn new() -> RwLock { - RwLock { mode: AtomicIsize::new(0) } + RwLock { mode: AtomicIsize::new(RWLOCK_FREE) } } #[inline] pub unsafe fn read(&self) { while !unsafe { self.try_read() } { - do_yield(); + yield_now(); } } #[inline] pub unsafe fn try_read(&self) -> bool { - // Non-atomically determine the current value. - let current = self.mode.load(SeqCst); - - // If it's currently locked for writing, then we cannot read. - if current < 0 { - return false; - } - - // Attempt to lock. If the `current` value has changed, then this - // operation will fail and we will not obtain the lock even if we - // could potentially keep it. - let new = current + 1; - self.mode.compare_exchange(current, new, SeqCst, SeqCst).is_ok() + self.mode + .fetch_update( + Acquire, + Acquire, + |v| if v == RWLOCK_WRITING { None } else { Some(v + 1) }, + ) + .is_ok() } #[inline] pub unsafe fn write(&self) { while !unsafe { self.try_write() } { - do_yield(); + yield_now(); } } #[inline] pub unsafe fn try_write(&self) -> bool { - self.mode.compare_exchange(0, -1, SeqCst, SeqCst).is_ok() + self.mode.compare_exchange(RWLOCK_FREE, RWLOCK_WRITING, Acquire, Acquire).is_ok() } #[inline] pub unsafe fn read_unlock(&self) { - self.mode.fetch_sub(1, SeqCst); + let previous = self.mode.fetch_sub(1, Acquire); + assert!(previous != RWLOCK_FREE); + assert!(previous != RWLOCK_WRITING); } #[inline] pub unsafe fn write_unlock(&self) { - assert_eq!(self.mode.compare_exchange(-1, 0, SeqCst, SeqCst), Ok(-1)); + assert_eq!( + self.mode.compare_exchange(RWLOCK_WRITING, RWLOCK_FREE, Acquire, Acquire), + Ok(RWLOCK_WRITING) + ); } } diff --git a/library/std/src/sys/pal/xous/mod.rs b/library/std/src/sys/pal/xous/mod.rs index 230067907c8f0..516d0a68720c6 100644 --- a/library/std/src/sys/pal/xous/mod.rs +++ b/library/std/src/sys/pal/xous/mod.rs @@ -12,10 +12,7 @@ pub mod fs; #[path = "../unsupported/io.rs"] pub mod io; pub mod locks; -#[path = "../unsupported/net.rs"] pub mod net; -#[path = "../unsupported/once.rs"] -pub mod once; pub mod os; #[path = "../unix/path.rs"] pub mod path; diff --git a/library/std/src/sys/pal/xous/net/dns.rs b/library/std/src/sys/pal/xous/net/dns.rs new file mode 100644 index 0000000000000..63056324bfbd9 --- /dev/null +++ b/library/std/src/sys/pal/xous/net/dns.rs @@ -0,0 +1,127 @@ +use crate::io; +use crate::net::{Ipv4Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; +use crate::os::xous::ffi::lend_mut; +use crate::os::xous::services::{dns_server, DnsLendMut}; +use core::convert::{TryFrom, TryInto}; + +pub struct DnsError { + pub code: u8, +} + +#[repr(C, align(4096))] +struct LookupHostQuery([u8; 4096]); + +pub struct LookupHost { + data: LookupHostQuery, + port: u16, + offset: usize, + count: usize, +} + +impl LookupHost { + pub fn port(&self) -> u16 { + self.port + } +} + +impl Iterator for LookupHost { + type Item = SocketAddr; + fn next(&mut self) -> Option { + if self.offset >= self.data.0.len() { + return None; + } + match self.data.0.get(self.offset) { + Some(&4) => { + self.offset += 1; + if self.offset + 4 > self.data.0.len() { + return None; + } + let result = Some(SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new( + self.data.0[self.offset], + self.data.0[self.offset + 1], + self.data.0[self.offset + 2], + self.data.0[self.offset + 3], + ), + self.port, + ))); + self.offset += 4; + result + } + Some(&6) => { + self.offset += 1; + if self.offset + 16 > self.data.0.len() { + return None; + } + let mut new_addr = [0u8; 16]; + for (src, octet) in self.data.0[(self.offset + 1)..(self.offset + 16 + 1)] + .iter() + .zip(new_addr.iter_mut()) + { + *octet = *src; + } + let result = + Some(SocketAddr::V6(SocketAddrV6::new(new_addr.into(), self.port, 0, 0))); + self.offset += 16; + result + } + _ => None, + } + } +} + +pub fn lookup(query: &str, port: u16) -> Result { + let mut result = LookupHost { data: LookupHostQuery([0u8; 4096]), offset: 0, count: 0, port }; + + // Copy the query into the message that gets sent to the DNS server + for (query_byte, result_byte) in query.as_bytes().iter().zip(result.data.0.iter_mut()) { + *result_byte = *query_byte; + } + + lend_mut( + dns_server(), + DnsLendMut::RawLookup.into(), + &mut result.data.0, + 0, + query.as_bytes().len(), + ) + .unwrap(); + if result.data.0[0] != 0 { + return Err(DnsError { code: result.data.0[1] }); + } + assert_eq!(result.offset, 0); + result.count = result.data.0[1] as usize; + + // Advance the offset to the first record + result.offset = 2; + Ok(result) +} + +impl TryFrom<&str> for LookupHost { + type Error = io::Error; + + fn try_from(s: &str) -> io::Result { + macro_rules! try_opt { + ($e:expr, $msg:expr) => { + match $e { + Some(r) => r, + None => return Err(io::const_io_error!(io::ErrorKind::InvalidInput, &$msg)), + } + }; + } + + // split the string by ':' and convert the second part to u16 + let (host, port_str) = try_opt!(s.rsplit_once(':'), "invalid socket address"); + let port: u16 = try_opt!(port_str.parse().ok(), "invalid port value"); + (host, port).try_into() + } +} + +impl TryFrom<(&str, u16)> for LookupHost { + type Error = io::Error; + + fn try_from(v: (&str, u16)) -> io::Result { + lookup(v.0, v.1) + .map_err(|_e| io::const_io_error!(io::ErrorKind::InvalidInput, &"DNS failure")) + } +} diff --git a/library/std/src/sys/pal/xous/net/mod.rs b/library/std/src/sys/pal/xous/net/mod.rs new file mode 100644 index 0000000000000..b5a3da136a6b0 --- /dev/null +++ b/library/std/src/sys/pal/xous/net/mod.rs @@ -0,0 +1,84 @@ +mod dns; + +mod tcpstream; +pub use tcpstream::*; + +mod tcplistener; +pub use tcplistener::*; + +mod udp; +pub use udp::*; + +// this structure needs to be synchronized with what's in net/src/api.rs +#[repr(C)] +#[derive(Debug)] +enum NetError { + // Ok = 0, + Unaddressable = 1, + SocketInUse = 2, + // AccessDenied = 3, + Invalid = 4, + // Finished = 5, + LibraryError = 6, + // AlreadyUsed = 7, + TimedOut = 8, + WouldBlock = 9, +} + +#[repr(C, align(4096))] +struct ConnectRequest { + raw: [u8; 4096], +} + +#[repr(C, align(4096))] +struct SendData { + raw: [u8; 4096], +} + +#[repr(C, align(4096))] +pub struct ReceiveData { + raw: [u8; 4096], +} + +#[repr(C, align(4096))] +pub struct GetAddress { + raw: [u8; 4096], +} + +pub use dns::LookupHost; + +#[allow(nonstandard_style)] +pub mod netc { + pub const AF_INET: u8 = 0; + pub const AF_INET6: u8 = 1; + pub type sa_family_t = u8; + + #[derive(Copy, Clone)] + pub struct in_addr { + pub s_addr: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in { + pub sin_family: sa_family_t, + pub sin_port: u16, + pub sin_addr: in_addr, + } + + #[derive(Copy, Clone)] + pub struct in6_addr { + pub s6_addr: [u8; 16], + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in6 { + pub sin6_family: sa_family_t, + pub sin6_port: u16, + pub sin6_addr: in6_addr, + pub sin6_flowinfo: u32, + pub sin6_scope_id: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr {} +} diff --git a/library/std/src/sys/pal/xous/net/tcplistener.rs b/library/std/src/sys/pal/xous/net/tcplistener.rs new file mode 100644 index 0000000000000..47305013083c8 --- /dev/null +++ b/library/std/src/sys/pal/xous/net/tcplistener.rs @@ -0,0 +1,247 @@ +use super::*; +use crate::fmt; +use crate::io; +use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use crate::os::xous::services; +use crate::sync::Arc; +use core::convert::TryInto; +use core::sync::atomic::{AtomicBool, AtomicU16, AtomicUsize, Ordering}; + +macro_rules! unimpl { + () => { + return Err(io::const_io_error!( + io::ErrorKind::Unsupported, + &"This function is not yet implemented", + )); + }; +} + +#[derive(Clone)] +pub struct TcpListener { + fd: Arc, + local: SocketAddr, + handle_count: Arc, + nonblocking: Arc, +} + +impl TcpListener { + pub fn bind(socketaddr: io::Result<&SocketAddr>) -> io::Result { + let mut addr = *socketaddr?; + + let fd = TcpListener::bind_inner(&mut addr)?; + return Ok(TcpListener { + fd: Arc::new(AtomicU16::new(fd)), + local: addr, + handle_count: Arc::new(AtomicUsize::new(1)), + nonblocking: Arc::new(AtomicBool::new(false)), + }); + } + + /// This returns the raw fd of a Listener, so that it can also be used by the + /// accept routine to replenish the Listener object after its handle has been converted into + /// a TcpStream object. + fn bind_inner(addr: &mut SocketAddr) -> io::Result { + // Construct the request + let mut connect_request = ConnectRequest { raw: [0u8; 4096] }; + + // Serialize the StdUdpBind structure. This is done "manually" because we don't want to + // make an auto-serdes (like bincode or rkyv) crate a dependency of Xous. + let port_bytes = addr.port().to_le_bytes(); + connect_request.raw[0] = port_bytes[0]; + connect_request.raw[1] = port_bytes[1]; + match addr.ip() { + IpAddr::V4(addr) => { + connect_request.raw[2] = 4; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + IpAddr::V6(addr) => { + connect_request.raw[2] = 6; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + } + + let Ok((_, valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdTcpListen.into(), + &mut connect_request.raw, + 0, + 4096, + ) else { + return Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Invalid response")); + }; + + // The first four bytes should be zero upon success, and will be nonzero + // for an error. + let response = connect_request.raw; + if response[0] != 0 || valid == 0 { + let errcode = response[1]; + if errcode == NetError::SocketInUse as u8 { + return Err(io::const_io_error!(io::ErrorKind::ResourceBusy, &"Socket in use")); + } else if errcode == NetError::Invalid as u8 { + return Err(io::const_io_error!( + io::ErrorKind::AddrNotAvailable, + &"Invalid address" + )); + } else if errcode == NetError::LibraryError as u8 { + return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")); + } else { + return Err(io::const_io_error!( + io::ErrorKind::Other, + &"Unable to connect or internal error" + )); + } + } + let fd = response[1] as usize; + if addr.port() == 0 { + // oddly enough, this is a valid port and it means "give me something valid, up to you what that is" + let assigned_port = u16::from_le_bytes(response[2..4].try_into().unwrap()); + addr.set_port(assigned_port); + } + // println!("TcpListening with file handle of {}\r\n", fd); + Ok(fd.try_into().unwrap()) + } + + pub fn socket_addr(&self) -> io::Result { + Ok(self.local) + } + + pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { + let mut receive_request = ReceiveData { raw: [0u8; 4096] }; + + if self.nonblocking.load(Ordering::Relaxed) { + // nonblocking + receive_request.raw[0] = 0; + } else { + // blocking + receive_request.raw[0] = 1; + } + + if let Ok((_offset, _valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdTcpAccept(self.fd.load(Ordering::Relaxed)).into(), + &mut receive_request.raw, + 0, + 0, + ) { + if receive_request.raw[0] != 0 { + // error case + if receive_request.raw[1] == NetError::TimedOut as u8 { + return Err(io::const_io_error!(io::ErrorKind::TimedOut, &"accept timed out",)); + } else if receive_request.raw[1] == NetError::WouldBlock as u8 { + return Err(io::const_io_error!( + io::ErrorKind::WouldBlock, + &"accept would block", + )); + } else if receive_request.raw[1] == NetError::LibraryError as u8 { + return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")); + } else { + return Err(io::const_io_error!(io::ErrorKind::Other, &"library error",)); + } + } else { + // accept successful + let rr = &receive_request.raw; + let stream_fd = u16::from_le_bytes(rr[1..3].try_into().unwrap()); + let port = u16::from_le_bytes(rr[20..22].try_into().unwrap()); + let addr = if rr[3] == 4 { + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(rr[4], rr[5], rr[6], rr[7])), port) + } else if rr[3] == 6 { + SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + u16::from_be_bytes(rr[4..6].try_into().unwrap()), + u16::from_be_bytes(rr[6..8].try_into().unwrap()), + u16::from_be_bytes(rr[8..10].try_into().unwrap()), + u16::from_be_bytes(rr[10..12].try_into().unwrap()), + u16::from_be_bytes(rr[12..14].try_into().unwrap()), + u16::from_be_bytes(rr[14..16].try_into().unwrap()), + u16::from_be_bytes(rr[16..18].try_into().unwrap()), + u16::from_be_bytes(rr[18..20].try_into().unwrap()), + )), + port, + ) + } else { + return Err(io::const_io_error!(io::ErrorKind::Other, &"library error",)); + }; + + // replenish the listener + let mut local_copy = self.local.clone(); // port is non-0 by this time, but the method signature needs a mut + let new_fd = TcpListener::bind_inner(&mut local_copy)?; + self.fd.store(new_fd, Ordering::Relaxed); + + // now return a stream converted from the old stream's fd + Ok((TcpStream::from_listener(stream_fd, self.local.port(), port, addr), addr)) + } + } else { + Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unable to accept")) + } + } + + pub fn duplicate(&self) -> io::Result { + self.handle_count.fetch_add(1, Ordering::Relaxed); + Ok(self.clone()) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + if ttl > 255 { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "TTL must be less than 256")); + } + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdSetTtlTcp(self.fd.load(Ordering::Relaxed), ttl).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn ttl(&self) -> io::Result { + Ok(crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdGetTtlTcp(self.fd.load(Ordering::Relaxed)).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|res| res[0] as _)?) + } + + pub fn set_only_v6(&self, _: bool) -> io::Result<()> { + unimpl!(); + } + + pub fn only_v6(&self) -> io::Result { + unimpl!(); + } + + pub fn take_error(&self) -> io::Result> { + // this call doesn't have a meaning on our platform, but we can at least not panic if it's used. + Ok(None) + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.nonblocking.store(nonblocking, Ordering::Relaxed); + Ok(()) + } +} + +impl fmt::Debug for TcpListener { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TCP listening on {:?}", self.local) + } +} + +impl Drop for TcpListener { + fn drop(&mut self) { + if self.handle_count.fetch_sub(1, Ordering::Relaxed) == 1 { + // only drop if we're the last clone + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + crate::os::xous::services::NetBlockingScalar::StdTcpClose( + self.fd.load(Ordering::Relaxed), + ) + .into(), + ) + .unwrap(); + } + } +} diff --git a/library/std/src/sys/pal/xous/net/tcpstream.rs b/library/std/src/sys/pal/xous/net/tcpstream.rs new file mode 100644 index 0000000000000..7149678118ab6 --- /dev/null +++ b/library/std/src/sys/pal/xous/net/tcpstream.rs @@ -0,0 +1,435 @@ +use super::*; +use crate::fmt; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::net::{IpAddr, Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6}; +use crate::os::xous::services; +use crate::sync::Arc; +use crate::time::Duration; +use core::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; + +macro_rules! unimpl { + () => { + return Err(io::const_io_error!( + io::ErrorKind::Unsupported, + &"This function is not yet implemented", + )); + }; +} + +enum ReadOrPeek { + Read, + Peek, +} + +#[derive(Clone)] +pub struct TcpStream { + fd: u16, + local_port: u16, + remote_port: u16, + peer_addr: SocketAddr, + // milliseconds + read_timeout: Arc, + // milliseconds + write_timeout: Arc, + handle_count: Arc, + nonblocking: Arc, +} + +fn sockaddr_to_buf(duration: Duration, addr: &SocketAddr, buf: &mut [u8]) { + // Construct the request. + let port_bytes = addr.port().to_le_bytes(); + buf[0] = port_bytes[0]; + buf[1] = port_bytes[1]; + for (dest, src) in buf[2..].iter_mut().zip((duration.as_millis() as u64).to_le_bytes()) { + *dest = src; + } + match addr.ip() { + IpAddr::V4(addr) => { + buf[10] = 4; + for (dest, src) in buf[11..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + IpAddr::V6(addr) => { + buf[10] = 6; + for (dest, src) in buf[11..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + } +} + +impl TcpStream { + pub(crate) fn from_listener( + fd: u16, + local_port: u16, + remote_port: u16, + peer_addr: SocketAddr, + ) -> TcpStream { + TcpStream { + fd, + local_port, + remote_port, + peer_addr, + read_timeout: Arc::new(AtomicU32::new(0)), + write_timeout: Arc::new(AtomicU32::new(0)), + handle_count: Arc::new(AtomicUsize::new(1)), + nonblocking: Arc::new(AtomicBool::new(false)), + } + } + + pub fn connect(socketaddr: io::Result<&SocketAddr>) -> io::Result { + Self::connect_timeout(socketaddr?, Duration::ZERO) + } + + pub fn connect_timeout(addr: &SocketAddr, duration: Duration) -> io::Result { + let mut connect_request = ConnectRequest { raw: [0u8; 4096] }; + + // Construct the request. + sockaddr_to_buf(duration, &addr, &mut connect_request.raw); + + let Ok((_, valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdTcpConnect.into(), + &mut connect_request.raw, + 0, + 4096, + ) else { + return Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Invalid response")); + }; + + // The first four bytes should be zero upon success, and will be nonzero + // for an error. + let response = connect_request.raw; + if response[0] != 0 || valid == 0 { + // errcode is a u8 but stuck in a u16 where the upper byte is invalid. Mask & decode accordingly. + let errcode = response[0]; + if errcode == NetError::SocketInUse as u8 { + return Err(io::const_io_error!(io::ErrorKind::ResourceBusy, &"Socket in use",)); + } else if errcode == NetError::Unaddressable as u8 { + return Err(io::const_io_error!( + io::ErrorKind::AddrNotAvailable, + &"Invalid address", + )); + } else { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Unable to connect or internal error", + )); + } + } + let fd = u16::from_le_bytes([response[2], response[3]]); + let local_port = u16::from_le_bytes([response[4], response[5]]); + let remote_port = u16::from_le_bytes([response[6], response[7]]); + // println!( + // "Connected with local port of {}, remote port of {}, file handle of {}", + // local_port, remote_port, fd + // ); + Ok(TcpStream { + fd, + local_port, + remote_port, + peer_addr: *addr, + read_timeout: Arc::new(AtomicU32::new(0)), + write_timeout: Arc::new(AtomicU32::new(0)), + handle_count: Arc::new(AtomicUsize::new(1)), + nonblocking: Arc::new(AtomicBool::new(false)), + }) + } + + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + if let Some(to) = timeout { + if to.is_zero() { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Zero is an invalid timeout", + )); + } + } + self.read_timeout.store( + timeout.map(|t| t.as_millis().min(u32::MAX as u128) as u32).unwrap_or_default(), + Ordering::Relaxed, + ); + Ok(()) + } + + pub fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + if let Some(to) = timeout { + if to.is_zero() { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Zero is an invalid timeout", + )); + } + } + self.write_timeout.store( + timeout.map(|t| t.as_millis().min(u32::MAX as u128) as u32).unwrap_or_default(), + Ordering::Relaxed, + ); + Ok(()) + } + + pub fn read_timeout(&self) -> io::Result> { + match self.read_timeout.load(Ordering::Relaxed) { + 0 => Ok(None), + t => Ok(Some(Duration::from_millis(t as u64))), + } + } + + pub fn write_timeout(&self) -> io::Result> { + match self.write_timeout.load(Ordering::Relaxed) { + 0 => Ok(None), + t => Ok(Some(Duration::from_millis(t as u64))), + } + } + + fn read_or_peek(&self, buf: &mut [u8], op: ReadOrPeek) -> io::Result { + let mut receive_request = ReceiveData { raw: [0u8; 4096] }; + let data_to_read = buf.len().min(receive_request.raw.len()); + + let opcode = match op { + ReadOrPeek::Read => { + services::NetLendMut::StdTcpRx(self.fd, self.nonblocking.load(Ordering::Relaxed)) + } + ReadOrPeek::Peek => { + services::NetLendMut::StdTcpPeek(self.fd, self.nonblocking.load(Ordering::Relaxed)) + } + }; + + let Ok((offset, length)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + opcode.into(), + &mut receive_request.raw, + // Reuse the `offset` as the read timeout + self.read_timeout.load(Ordering::Relaxed) as usize, + data_to_read, + ) else { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Library failure: wrong message type or messaging error" + )); + }; + + if offset != 0 { + for (dest, src) in buf.iter_mut().zip(receive_request.raw[..length].iter()) { + *dest = *src; + } + Ok(length) + } else { + let result = receive_request.raw; + if result[0] != 0 { + if result[1] == 8 { + // timed out + return Err(io::const_io_error!(io::ErrorKind::TimedOut, &"Timeout",)); + } + if result[1] == 9 { + // would block + return Err(io::const_io_error!(io::ErrorKind::WouldBlock, &"Would block",)); + } + } + Err(io::const_io_error!(io::ErrorKind::Other, &"recv_slice failure")) + } + } + + pub fn peek(&self, buf: &mut [u8]) -> io::Result { + self.read_or_peek(buf, ReadOrPeek::Peek) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.read_or_peek(buf, ReadOrPeek::Read) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + crate::io::default_read_vectored(|b| self.read(b), bufs) + } + + pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + crate::io::default_read_buf(|buf| self.read(buf), cursor) + } + + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + let mut send_request = SendData { raw: [0u8; 4096] }; + for (dest, src) in send_request.raw.iter_mut().zip(buf) { + *dest = *src; + } + let buf_len = send_request.raw.len().min(buf.len()); + + let (_offset, _valid) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdTcpTx(self.fd).into(), + &mut send_request.raw, + // Reuse the offset as the timeout + self.write_timeout.load(Ordering::Relaxed) as usize, + buf_len, + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Internal error")))?; + + if send_request.raw[0] != 0 { + if send_request.raw[4] == 8 { + // timed out + return Err(io::const_io_error!( + io::ErrorKind::BrokenPipe, + &"Timeout or connection closed", + )); + } else if send_request.raw[4] == 9 { + // would block + return Err(io::const_io_error!(io::ErrorKind::WouldBlock, &"Would block",)); + } else { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Error when sending", + )); + } + } + Ok(u32::from_le_bytes([ + send_request.raw[4], + send_request.raw[5], + send_request.raw[6], + send_request.raw[7], + ]) as usize) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + crate::io::default_write_vectored(|b| self.write(b), bufs) + } + + pub fn is_write_vectored(&self) -> bool { + false + } + + pub fn peer_addr(&self) -> io::Result { + Ok(self.peer_addr) + } + + pub fn socket_addr(&self) -> io::Result { + let mut get_addr = GetAddress { raw: [0u8; 4096] }; + + let Ok((_offset, _valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdGetAddress(self.fd).into(), + &mut get_addr.raw, + 0, + 0, + ) else { + return Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Internal error")); + }; + let mut i = get_addr.raw.iter(); + match *i.next().unwrap() { + 4 => Ok(SocketAddr::V4(SocketAddrV4::new( + Ipv4Addr::new( + *i.next().unwrap(), + *i.next().unwrap(), + *i.next().unwrap(), + *i.next().unwrap(), + ), + self.local_port, + ))), + 6 => { + let mut new_addr = [0u8; 16]; + for (src, octet) in i.zip(new_addr.iter_mut()) { + *octet = *src; + } + Ok(SocketAddr::V6(SocketAddrV6::new(new_addr.into(), self.local_port, 0, 0))) + } + _ => Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Internal error")), + } + } + + pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdTcpStreamShutdown(self.fd, how).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn duplicate(&self) -> io::Result { + self.handle_count.fetch_add(1, Ordering::Relaxed); + Ok(self.clone()) + } + + pub fn set_linger(&self, _: Option) -> io::Result<()> { + unimpl!(); + } + + pub fn linger(&self) -> io::Result> { + unimpl!(); + } + + pub fn set_nodelay(&self, enabled: bool) -> io::Result<()> { + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdSetNodelay(self.fd, enabled).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn nodelay(&self) -> io::Result { + Ok(crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdGetNodelay(self.fd).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|res| res[0] != 0)?) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + if ttl > 255 { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "TTL must be less than 256")); + } + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdSetTtlTcp(self.fd, ttl).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn ttl(&self) -> io::Result { + Ok(crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdGetTtlTcp(self.fd).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|res| res[0] as _)?) + } + + pub fn take_error(&self) -> io::Result> { + // this call doesn't have a meaning on our platform, but we can at least not panic if it's used. + Ok(None) + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.nonblocking.store(nonblocking, Ordering::SeqCst); + Ok(()) + } +} + +impl fmt::Debug for TcpStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "TCP connection to {:?} port {} to local port {}", + self.peer_addr, self.remote_port, self.local_port + ) + } +} + +impl Drop for TcpStream { + fn drop(&mut self) { + if self.handle_count.fetch_sub(1, Ordering::Relaxed) == 1 { + // only drop if we're the last clone + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdTcpClose(self.fd).into(), + ) + .unwrap(); + } + } +} diff --git a/library/std/src/sys/pal/xous/net/udp.rs b/library/std/src/sys/pal/xous/net/udp.rs new file mode 100644 index 0000000000000..cafa5b3bde829 --- /dev/null +++ b/library/std/src/sys/pal/xous/net/udp.rs @@ -0,0 +1,471 @@ +use super::*; +use crate::cell::Cell; +use crate::fmt; +use crate::io; +use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use crate::os::xous::services; +use crate::sync::Arc; +use crate::time::Duration; +use core::convert::TryInto; +use core::sync::atomic::{AtomicUsize, Ordering}; + +macro_rules! unimpl { + () => { + return Err(io::const_io_error!( + io::ErrorKind::Unsupported, + &"This function is not yet implemented", + )); + }; +} + +#[derive(Clone)] +pub struct UdpSocket { + fd: u16, + local: SocketAddr, + remote: Cell>, + // in milliseconds. The setting applies only to `recv` calls after the timeout is set. + read_timeout: Cell, + // in milliseconds. The setting applies only to `send` calls after the timeout is set. + write_timeout: Cell, + handle_count: Arc, + nonblocking: Cell, +} + +impl UdpSocket { + pub fn bind(socketaddr: io::Result<&SocketAddr>) -> io::Result { + let addr = socketaddr?; + // Construct the request + let mut connect_request = ConnectRequest { raw: [0u8; 4096] }; + + // Serialize the StdUdpBind structure. This is done "manually" because we don't want to + // make an auto-serdes (like bincode or rkyv) crate a dependency of Xous. + let port_bytes = addr.port().to_le_bytes(); + connect_request.raw[0] = port_bytes[0]; + connect_request.raw[1] = port_bytes[1]; + match addr.ip() { + IpAddr::V4(addr) => { + connect_request.raw[2] = 4; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + IpAddr::V6(addr) => { + connect_request.raw[2] = 6; + for (dest, src) in connect_request.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + } + + let response = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdUdpBind.into(), + &mut connect_request.raw, + 0, + 4096, + ); + + if let Ok((_, valid)) = response { + // The first four bytes should be zero upon success, and will be nonzero + // for an error. + let response = connect_request.raw; + if response[0] != 0 || valid == 0 { + let errcode = response[1]; + if errcode == NetError::SocketInUse as u8 { + return Err(io::const_io_error!(io::ErrorKind::ResourceBusy, &"Socket in use")); + } else if errcode == NetError::Invalid as u8 { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Port can't be 0 or invalid address" + )); + } else if errcode == NetError::LibraryError as u8 { + return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")); + } else { + return Err(io::const_io_error!( + io::ErrorKind::Other, + &"Unable to connect or internal error" + )); + } + } + let fd = response[1] as u16; + return Ok(UdpSocket { + fd, + local: *addr, + remote: Cell::new(None), + read_timeout: Cell::new(0), + write_timeout: Cell::new(0), + handle_count: Arc::new(AtomicUsize::new(1)), + nonblocking: Cell::new(false), + }); + } + Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Invalid response")) + } + + pub fn peer_addr(&self) -> io::Result { + match self.remote.get() { + Some(dest) => Ok(dest), + None => Err(io::const_io_error!(io::ErrorKind::NotConnected, &"No peer specified")), + } + } + + pub fn socket_addr(&self) -> io::Result { + Ok(self.local) + } + + fn recv_inner(&self, buf: &mut [u8], do_peek: bool) -> io::Result<(usize, SocketAddr)> { + let mut receive_request = ReceiveData { raw: [0u8; 4096] }; + + if self.nonblocking.get() { + // nonblocking + receive_request.raw[0] = 0; + } else { + // blocking + receive_request.raw[0] = 1; + for (&s, d) in self + .read_timeout + .get() + .to_le_bytes() + .iter() + .zip(receive_request.raw[1..9].iter_mut()) + { + *d = s; + } + } + if let Ok((_offset, _valid)) = crate::os::xous::ffi::lend_mut( + services::net_server(), + services::NetLendMut::StdUdpRx(self.fd).into(), + &mut receive_request.raw, + if do_peek { 1 } else { 0 }, + 0, + ) { + if receive_request.raw[0] != 0 { + // error case + if receive_request.raw[1] == NetError::TimedOut as u8 { + return Err(io::const_io_error!(io::ErrorKind::TimedOut, &"recv timed out",)); + } else if receive_request.raw[1] == NetError::WouldBlock as u8 { + return Err(io::const_io_error!( + io::ErrorKind::WouldBlock, + &"recv would block", + )); + } else if receive_request.raw[1] == NetError::LibraryError as u8 { + return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")); + } else { + return Err(io::const_io_error!(io::ErrorKind::Other, &"library error",)); + } + } else { + let rr = &receive_request.raw; + let rxlen = u16::from_le_bytes(rr[1..3].try_into().unwrap()); + let port = u16::from_le_bytes(rr[20..22].try_into().unwrap()); + let addr = if rr[3] == 4 { + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(rr[4], rr[5], rr[6], rr[7])), port) + } else if rr[3] == 6 { + SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + u16::from_be_bytes(rr[4..6].try_into().unwrap()), + u16::from_be_bytes(rr[6..8].try_into().unwrap()), + u16::from_be_bytes(rr[8..10].try_into().unwrap()), + u16::from_be_bytes(rr[10..12].try_into().unwrap()), + u16::from_be_bytes(rr[12..14].try_into().unwrap()), + u16::from_be_bytes(rr[14..16].try_into().unwrap()), + u16::from_be_bytes(rr[16..18].try_into().unwrap()), + u16::from_be_bytes(rr[18..20].try_into().unwrap()), + )), + port, + ) + } else { + return Err(io::const_io_error!(io::ErrorKind::Other, &"library error",)); + }; + for (&s, d) in rr[22..22 + rxlen as usize].iter().zip(buf.iter_mut()) { + *d = s; + } + Ok((rxlen as usize, addr)) + } + } else { + Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unable to recv")) + } + } + + pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + self.recv_inner(buf, false) + } + + pub fn recv(&self, buf: &mut [u8]) -> io::Result { + self.recv_from(buf).map(|(len, _addr)| len) + } + + pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + self.recv_inner(buf, true) + } + + pub fn peek(&self, buf: &mut [u8]) -> io::Result { + self.peek_from(buf).map(|(len, _addr)| len) + } + + pub fn connect(&self, maybe_addr: io::Result<&SocketAddr>) -> io::Result<()> { + let addr = maybe_addr?; + self.remote.set(Some(*addr)); + Ok(()) + } + + pub fn send(&self, buf: &[u8]) -> io::Result { + if let Some(addr) = self.remote.get() { + self.send_to(buf, &addr) + } else { + Err(io::const_io_error!(io::ErrorKind::NotConnected, &"No remote specified")) + } + } + + pub fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> io::Result { + let mut tx_req = SendData { raw: [0u8; 4096] }; + + // Construct the request. + let port_bytes = addr.port().to_le_bytes(); + tx_req.raw[0] = port_bytes[0]; + tx_req.raw[1] = port_bytes[1]; + match addr.ip() { + IpAddr::V4(addr) => { + tx_req.raw[2] = 4; + for (dest, src) in tx_req.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + IpAddr::V6(addr) => { + tx_req.raw[2] = 6; + for (dest, src) in tx_req.raw[3..].iter_mut().zip(addr.octets()) { + *dest = src; + } + } + } + let len = buf.len() as u16; + let len_bytes = len.to_le_bytes(); + tx_req.raw[19] = len_bytes[0]; + tx_req.raw[20] = len_bytes[1]; + for (&s, d) in buf.iter().zip(tx_req.raw[21..].iter_mut()) { + *d = s; + } + + // let buf = unsafe { + // xous::MemoryRange::new( + // &mut tx_req as *mut SendData as usize, + // core::mem::size_of::(), + // ) + // .unwrap() + // }; + + // write time-outs are implemented on the caller side. Basically, if the Net crate server + // is too busy to take the call immediately: retry, until the timeout is reached. + let now = crate::time::Instant::now(); + let write_timeout = if self.nonblocking.get() { + // nonblocking + core::time::Duration::ZERO + } else { + // blocking + if self.write_timeout.get() == 0 { + // forever + core::time::Duration::from_millis(u64::MAX) + } else { + // or this amount of time + core::time::Duration::from_millis(self.write_timeout.get()) + } + }; + loop { + let response = crate::os::xous::ffi::try_lend_mut( + services::net_server(), + services::NetLendMut::StdUdpTx(self.fd).into(), + &mut tx_req.raw, + 0, + 4096, + ); + match response { + Ok((_, valid)) => { + let response = &tx_req.raw; + if response[0] != 0 || valid == 0 { + let errcode = response[1]; + if errcode == NetError::SocketInUse as u8 { + return Err(io::const_io_error!( + io::ErrorKind::ResourceBusy, + &"Socket in use" + )); + } else if errcode == NetError::Invalid as u8 { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Socket not valid" + )); + } else if errcode == NetError::LibraryError as u8 { + return Err(io::const_io_error!( + io::ErrorKind::Other, + &"Library error" + )); + } else { + return Err(io::const_io_error!( + io::ErrorKind::Other, + &"Unable to connect" + )); + } + } else { + // no error + return Ok(len as usize); + } + } + Err(crate::os::xous::ffi::Error::ServerQueueFull) => { + if now.elapsed() >= write_timeout { + return Err(io::const_io_error!( + io::ErrorKind::WouldBlock, + &"Write timed out" + )); + } else { + // question: do we want to do something a bit more gentle than immediately retrying? + crate::thread::yield_now(); + } + } + _ => return Err(io::const_io_error!(io::ErrorKind::Other, &"Library error")), + } + } + } + + pub fn duplicate(&self) -> io::Result { + self.handle_count.fetch_add(1, Ordering::Relaxed); + Ok(self.clone()) + } + + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + if let Some(d) = timeout { + if d.is_zero() { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Zero duration is invalid" + )); + } + } + self.read_timeout + .set(timeout.map(|t| t.as_millis().min(u64::MAX as u128) as u64).unwrap_or_default()); + Ok(()) + } + + pub fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + if let Some(d) = timeout { + if d.is_zero() { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + &"Zero duration is invalid" + )); + } + } + self.write_timeout + .set(timeout.map(|t| t.as_millis().min(u64::MAX as u128) as u64).unwrap_or_default()); + Ok(()) + } + + pub fn read_timeout(&self) -> io::Result> { + match self.read_timeout.get() { + 0 => Ok(None), + t => Ok(Some(Duration::from_millis(t as u64))), + } + } + + pub fn write_timeout(&self) -> io::Result> { + match self.write_timeout.get() { + 0 => Ok(None), + t => Ok(Some(Duration::from_millis(t as u64))), + } + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + if ttl > 255 { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "TTL must be less than 256")); + } + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdSetTtlUdp(self.fd, ttl).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|_| ()) + } + + pub fn ttl(&self) -> io::Result { + Ok(crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdGetTtlUdp(self.fd).into(), + ) + .or(Err(io::const_io_error!(io::ErrorKind::InvalidInput, &"Unexpected return value"))) + .map(|res| res[0] as _)?) + } + + pub fn take_error(&self) -> io::Result> { + // this call doesn't have a meaning on our platform, but we can at least not panic if it's used. + Ok(None) + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.nonblocking.set(nonblocking); + Ok(()) + } + + // ------------- smoltcp base stack does not have multicast or broadcast support --------------- + pub fn set_broadcast(&self, _: bool) -> io::Result<()> { + unimpl!(); + } + + pub fn broadcast(&self) -> io::Result { + unimpl!(); + } + + pub fn set_multicast_loop_v4(&self, _: bool) -> io::Result<()> { + unimpl!(); + } + + pub fn multicast_loop_v4(&self) -> io::Result { + unimpl!(); + } + + pub fn set_multicast_ttl_v4(&self, _: u32) -> io::Result<()> { + unimpl!(); + } + + pub fn multicast_ttl_v4(&self) -> io::Result { + unimpl!(); + } + + pub fn set_multicast_loop_v6(&self, _: bool) -> io::Result<()> { + unimpl!(); + } + + pub fn multicast_loop_v6(&self) -> io::Result { + unimpl!(); + } + + pub fn join_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> { + unimpl!(); + } + + pub fn join_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> { + unimpl!(); + } + + pub fn leave_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> { + unimpl!(); + } + + pub fn leave_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> { + unimpl!(); + } +} + +impl fmt::Debug for UdpSocket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "UDP listening on {:?} to {:?}", self.local, self.remote.get(),) + } +} + +impl Drop for UdpSocket { + fn drop(&mut self) { + if self.handle_count.fetch_sub(1, Ordering::Relaxed) == 1 { + // only drop if we're the last clone + crate::os::xous::ffi::blocking_scalar( + services::net_server(), + services::NetBlockingScalar::StdUdpClose(self.fd).into(), + ) + .unwrap(); + } + } +} diff --git a/library/std/src/sys/pal/xous/stdio.rs b/library/std/src/sys/pal/xous/stdio.rs index 2ac694641ba94..11608964b52e6 100644 --- a/library/std/src/sys/pal/xous/stdio.rs +++ b/library/std/src/sys/pal/xous/stdio.rs @@ -5,7 +5,7 @@ pub struct Stdout {} pub struct Stderr; use crate::os::xous::ffi::{lend, try_lend, try_scalar, Connection}; -use crate::os::xous::services::{log_server, try_connect, LogScalar}; +use crate::os::xous::services::{log_server, try_connect, LogLend, LogScalar}; impl Stdin { pub const fn new() -> Stdin { @@ -27,7 +27,7 @@ impl Stdout { impl io::Write for Stdout { fn write(&mut self, buf: &[u8]) -> io::Result { - #[repr(align(4096))] + #[repr(C, align(4096))] struct LendBuffer([u8; 4096]); let mut lend_buffer = LendBuffer([0u8; 4096]); let connection = log_server(); @@ -35,7 +35,8 @@ impl io::Write for Stdout { for (dest, src) in lend_buffer.0.iter_mut().zip(chunk) { *dest = *src; } - lend(connection, 1, &lend_buffer.0, 0, chunk.len()).unwrap(); + lend(connection, LogLend::StandardOutput.into(), &lend_buffer.0, 0, chunk.len()) + .unwrap(); } Ok(buf.len()) } @@ -53,7 +54,7 @@ impl Stderr { impl io::Write for Stderr { fn write(&mut self, buf: &[u8]) -> io::Result { - #[repr(align(4096))] + #[repr(C, align(4096))] struct LendBuffer([u8; 4096]); let mut lend_buffer = LendBuffer([0u8; 4096]); let connection = log_server(); @@ -61,7 +62,8 @@ impl io::Write for Stderr { for (dest, src) in lend_buffer.0.iter_mut().zip(chunk) { *dest = *src; } - lend(connection, 1, &lend_buffer.0, 0, chunk.len()).unwrap(); + lend(connection, LogLend::StandardError.into(), &lend_buffer.0, 0, chunk.len()) + .unwrap(); } Ok(buf.len()) } diff --git a/library/std/src/sys/pal/xous/thread.rs b/library/std/src/sys/pal/xous/thread.rs index 78c68de7bf38d..0f452e07a5c58 100644 --- a/library/std/src/sys/pal/xous/thread.rs +++ b/library/std/src/sys/pal/xous/thread.rs @@ -68,14 +68,18 @@ impl Thread { ) .map_err(|code| io::Error::from_raw_os_error(code as i32))?; - extern "C" fn thread_start(main: *mut usize, guard_page_pre: usize, stack_size: usize) { + extern "C" fn thread_start( + main: *mut usize, + guard_page_pre: usize, + stack_size: usize, + ) -> ! { unsafe { - // Finally, let's run some code. + // Run the contents of the new thread. Box::from_raw(main as *mut Box)(); } // Destroy TLS, which will free the TLS page and call the destructor for - // any thread local storage. + // any thread local storage (if any). unsafe { crate::sys::thread_local_key::destroy_tls(); } diff --git a/library/std/src/sys/pal/xous/thread_local_key.rs b/library/std/src/sys/pal/xous/thread_local_key.rs index 3771ea6570084..59a668c3df6ff 100644 --- a/library/std/src/sys/pal/xous/thread_local_key.rs +++ b/library/std/src/sys/pal/xous/thread_local_key.rs @@ -23,10 +23,25 @@ pub type Dtor = unsafe extern "C" fn(*mut u8); const TLS_MEMORY_SIZE: usize = 4096; -/// TLS keys start at `1` to mimic POSIX. +/// TLS keys start at `1`. Index `0` is unused +#[cfg(not(test))] +#[export_name = "_ZN16__rust_internals3std3sys4xous16thread_local_key13TLS_KEY_INDEXE"] static TLS_KEY_INDEX: AtomicUsize = AtomicUsize::new(1); -fn tls_ptr_addr() -> *mut usize { +#[cfg(not(test))] +#[export_name = "_ZN16__rust_internals3std3sys4xous16thread_local_key9DTORSE"] +static DTORS: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +#[cfg(test)] +extern "Rust" { + #[link_name = "_ZN16__rust_internals3std3sys4xous16thread_local_key13TLS_KEY_INDEXE"] + static TLS_KEY_INDEX: AtomicUsize; + + #[link_name = "_ZN16__rust_internals3std3sys4xous16thread_local_key9DTORSE"] + static DTORS: AtomicPtr; +} + +fn tls_ptr_addr() -> *mut *mut u8 { let mut tp: usize; unsafe { asm!( @@ -34,50 +49,50 @@ fn tls_ptr_addr() -> *mut usize { out(reg) tp, ); } - core::ptr::from_exposed_addr_mut::(tp) + core::ptr::from_exposed_addr_mut::<*mut u8>(tp) } /// Create an area of memory that's unique per thread. This area will /// contain all thread local pointers. -fn tls_ptr() -> *mut usize { - let mut tp = tls_ptr_addr(); +fn tls_table() -> &'static mut [*mut u8] { + let tp = tls_ptr_addr(); + if !tp.is_null() { + return unsafe { + core::slice::from_raw_parts_mut(tp, TLS_MEMORY_SIZE / core::mem::size_of::<*mut u8>()) + }; + } // If the TP register is `0`, then this thread hasn't initialized // its TLS yet. Allocate a new page to store this memory. - if tp.is_null() { - tp = unsafe { - map_memory( - None, - None, - TLS_MEMORY_SIZE / core::mem::size_of::(), - MemoryFlags::R | MemoryFlags::W, - ) - } + let tp = unsafe { + map_memory( + None, + None, + TLS_MEMORY_SIZE / core::mem::size_of::<*mut u8>(), + MemoryFlags::R | MemoryFlags::W, + ) .expect("Unable to allocate memory for thread local storage") - .as_mut_ptr(); + }; - unsafe { - // Key #0 is currently unused. - (tp).write_volatile(0); + for val in tp.iter() { + assert!(*val as usize == 0); + } - // Set the thread's `$tp` register - asm!( - "mv tp, {}", - in(reg) tp as usize, - ); - } + unsafe { + // Set the thread's `$tp` register + asm!( + "mv tp, {}", + in(reg) tp.as_mut_ptr() as usize, + ); } tp } -/// Allocate a new TLS key. These keys are shared among all threads. -fn tls_alloc() -> usize { - TLS_KEY_INDEX.fetch_add(1, SeqCst) -} - #[inline] pub unsafe fn create(dtor: Option) -> Key { - let key = tls_alloc(); + // Allocate a new TLS key. These keys are shared among all threads. + #[allow(unused_unsafe)] + let key = unsafe { TLS_KEY_INDEX.fetch_add(1, SeqCst) }; if let Some(f) = dtor { unsafe { register_dtor(key, f) }; } @@ -87,18 +102,20 @@ pub unsafe fn create(dtor: Option) -> Key { #[inline] pub unsafe fn set(key: Key, value: *mut u8) { assert!((key < 1022) && (key >= 1)); - unsafe { tls_ptr().add(key).write_volatile(value as usize) }; + let table = tls_table(); + table[key] = value; } #[inline] pub unsafe fn get(key: Key) -> *mut u8 { assert!((key < 1022) && (key >= 1)); - core::ptr::from_exposed_addr_mut::(unsafe { tls_ptr().add(key).read_volatile() }) + tls_table()[key] } #[inline] pub unsafe fn destroy(_key: Key) { - panic!("can't destroy keys on Xous"); + // Just leak the key. Probably not great on long-running systems that create + // lots of TLS variables, but in practice that's not an issue. } // ------------------------------------------------------------------------- @@ -127,8 +144,6 @@ pub unsafe fn destroy(_key: Key) { // key but also a slot for the destructor queue on windows. An optimization for // another day! -static DTORS: AtomicPtr = AtomicPtr::new(ptr::null_mut()); - struct Node { dtor: Dtor, key: Key, @@ -138,10 +153,12 @@ struct Node { unsafe fn register_dtor(key: Key, dtor: Dtor) { let mut node = ManuallyDrop::new(Box::new(Node { key, dtor, next: ptr::null_mut() })); - let mut head = DTORS.load(SeqCst); + #[allow(unused_unsafe)] + let mut head = unsafe { DTORS.load(SeqCst) }; loop { node.next = head; - match DTORS.compare_exchange(head, &mut **node, SeqCst, SeqCst) { + #[allow(unused_unsafe)] + match unsafe { DTORS.compare_exchange(head, &mut **node, SeqCst, SeqCst) } { Ok(_) => return, // nothing to drop, we successfully added the node to the list Err(cur) => head = cur, } @@ -155,6 +172,7 @@ pub unsafe fn destroy_tls() { if tp.is_null() { return; } + unsafe { run_dtors() }; // Finally, free the TLS array @@ -169,12 +187,19 @@ pub unsafe fn destroy_tls() { unsafe fn run_dtors() { let mut any_run = true; + + // Run the destructor "some" number of times. This is 5x on Windows, + // so we copy it here. This allows TLS variables to create new + // TLS variables upon destruction that will also get destroyed. + // Keep going until we run out of tries or until we have nothing + // left to destroy. for _ in 0..5 { if !any_run { break; } any_run = false; - let mut cur = DTORS.load(SeqCst); + #[allow(unused_unsafe)] + let mut cur = unsafe { DTORS.load(SeqCst) }; while !cur.is_null() { let ptr = unsafe { get((*cur).key) }; diff --git a/library/std/src/sys/pal/xous/thread_parking.rs b/library/std/src/sys/pal/xous/thread_parking.rs index aa39c6d27185f..0bd0462d77d35 100644 --- a/library/std/src/sys/pal/xous/thread_parking.rs +++ b/library/std/src/sys/pal/xous/thread_parking.rs @@ -29,31 +29,40 @@ impl Parker { // Change NOTIFIED to EMPTY and EMPTY to PARKED. let state = self.state.fetch_sub(1, Acquire); if state == NOTIFIED { + // The state has gone from NOTIFIED (1) to EMPTY (0) return; } + // The state has gone from EMPTY (0) to PARKED (-1) + assert!(state == EMPTY); - // The state was set to PARKED. Wait until the `unpark` wakes us up. + // The state is now PARKED (-1). Wait until the `unpark` wakes us up. blocking_scalar( ticktimer_server(), TicktimerScalar::WaitForCondition(self.index(), 0).into(), ) .expect("failed to send WaitForCondition command"); - self.state.swap(EMPTY, Acquire); + let state = self.state.swap(EMPTY, Acquire); + assert!(state == NOTIFIED || state == PARKED); } pub unsafe fn park_timeout(self: Pin<&Self>, timeout: Duration) { // Change NOTIFIED to EMPTY and EMPTY to PARKED. let state = self.state.fetch_sub(1, Acquire); if state == NOTIFIED { + // The state has gone from NOTIFIED (1) to EMPTY (0) return; } + // The state has gone from EMPTY (0) to PARKED (-1) + assert!(state == EMPTY); // A value of zero indicates an indefinite wait. Clamp the number of // milliseconds to the allowed range. let millis = usize::max(timeout.as_millis().try_into().unwrap_or(usize::MAX), 1); - let was_timeout = blocking_scalar( + // The state is now PARKED (-1). Wait until the `unpark` wakes us up, + // or things time out. + let _was_timeout = blocking_scalar( ticktimer_server(), TicktimerScalar::WaitForCondition(self.index(), millis).into(), ) @@ -61,28 +70,37 @@ impl Parker { != 0; let state = self.state.swap(EMPTY, Acquire); - if was_timeout && state == NOTIFIED { - // The state was set to NOTIFIED after we returned from the wait - // but before we reset the state. Therefore, a wakeup is on its - // way, which we need to consume here. - // NOTICE: this is a priority hole. - blocking_scalar( - ticktimer_server(), - TicktimerScalar::WaitForCondition(self.index(), 0).into(), - ) - .expect("failed to send WaitForCondition command"); - } + assert!(state == PARKED || state == NOTIFIED); } pub fn unpark(self: Pin<&Self>) { - let state = self.state.swap(NOTIFIED, Release); - if state == PARKED { - // The thread is parked, wake it up. - blocking_scalar( - ticktimer_server(), - TicktimerScalar::NotifyCondition(self.index(), 1).into(), - ) - .expect("failed to send NotifyCondition command"); + // If the state is already `NOTIFIED`, then another thread has + // indicated it wants to wake up the target thread. + // + // If the state is `EMPTY` then there is nothing to wake up, and + // the target thread will immediately exit from `park()` the + // next time that function is called. + if self.state.swap(NOTIFIED, Release) != PARKED { + return; + } + + // The thread is parked, wake it up. Keep trying until we wake something up. + // This will happen when the `NotifyCondition` call returns the fact that + // 1 condition was notified. + // Alternately, keep going until the state is seen as `EMPTY`, indicating + // the thread woke up and kept going. This can happen when the Park + // times out before we can send the NotifyCondition message. + while blocking_scalar( + ticktimer_server(), + TicktimerScalar::NotifyCondition(self.index(), 1).into(), + ) + .expect("failed to send NotifyCondition command")[0] + == 0 + && self.state.load(Acquire) != EMPTY + { + // The target thread hasn't yet hit the `WaitForCondition` call. + // Yield to let the target thread run some more. + crate::thread::yield_now(); } } } diff --git a/library/std/src/sys_common/once/mod.rs b/library/std/src/sys_common/once/mod.rs index 359697d831317..ec57568c54c4f 100644 --- a/library/std/src/sys_common/once/mod.rs +++ b/library/std/src/sys_common/once/mod.rs @@ -25,6 +25,7 @@ cfg_if::cfg_if! { target_family = "unix", all(target_vendor = "fortanix", target_env = "sgx"), target_os = "solid_asp3", + target_os = "xous", ))] { mod queue; pub use queue::{Once, OnceState}; diff --git a/src/doc/unstable-book/src/compiler-flags/remap-path-scope.md b/src/doc/unstable-book/src/compiler-flags/remap-path-scope.md index 13349ff6b8f41..424f1128e3b5c 100644 --- a/src/doc/unstable-book/src/compiler-flags/remap-path-scope.md +++ b/src/doc/unstable-book/src/compiler-flags/remap-path-scope.md @@ -20,5 +20,5 @@ This flag accepts a comma-separated list of values and may be specified multiple ```sh # This would produce an absolute path to main.rs in build outputs of # "./main.rs". -rustc --remap-path-prefix=$(PWD)=/remapped -Zremap-path-prefix=object main.rs +rustc --remap-path-prefix=$(PWD)=/remapped -Zremap-path-scope=object main.rs ``` diff --git a/src/tools/remote-test-client/src/main.rs b/src/tools/remote-test-client/src/main.rs index bcda930d093c5..590c735596ed9 100644 --- a/src/tools/remote-test-client/src/main.rs +++ b/src/tools/remote-test-client/src/main.rs @@ -300,7 +300,7 @@ fn run(support_lib_count: usize, exe: String, all_args: Vec) { // Ok now it's time to read all the output. We're receiving "frames" // representing stdout/stderr, so we decode all that here. - let mut header = [0; 5]; + let mut header = [0; 9]; let mut stderr_done = false; let mut stdout_done = false; let mut client = t!(client.into_inner()); @@ -308,10 +308,8 @@ fn run(support_lib_count: usize, exe: String, all_args: Vec) { let mut stderr = io::stderr(); while !stdout_done || !stderr_done { t!(client.read_exact(&mut header)); - let amt = ((header[1] as u64) << 24) - | ((header[2] as u64) << 16) - | ((header[3] as u64) << 8) - | ((header[4] as u64) << 0); + let amt = u64::from_be_bytes(header[1..9].try_into().unwrap()); + if header[0] == 0 { if amt == 0 { stdout_done = true; @@ -349,7 +347,8 @@ fn send(path: &Path, dst: &mut dyn Write) { t!(dst.write_all(&[0])); let mut file = t!(File::open(&path)); let amt = t!(file.metadata()).len(); - t!(dst.write_all(&[(amt >> 24) as u8, (amt >> 16) as u8, (amt >> 8) as u8, (amt >> 0) as u8,])); + + t!(dst.write_all(&amt.to_be_bytes())); t!(io::copy(&mut file, dst)); } diff --git a/src/tools/remote-test-server/src/main.rs b/src/tools/remote-test-server/src/main.rs index 3d61a0675590c..68d7aa56c438b 100644 --- a/src/tools/remote-test-server/src/main.rs +++ b/src/tools/remote-test-server/src/main.rs @@ -347,7 +347,7 @@ fn recv(dir: &Path, io: &mut B) -> PathBuf { // the filesystem limits. let len = cmp::min(filename.len() - 1, 50); let dst = dir.join(t!(str::from_utf8(&filename[..len]))); - let amt = read_u32(io) as u64; + let amt = read_u64(io); t!(io::copy(&mut io.take(amt), &mut t!(File::create(&dst)))); set_permissions(&dst); dst @@ -365,7 +365,7 @@ fn my_copy(src: &mut dyn Read, which: u8, dst: &Mutex) { loop { let n = t!(src.read(&mut b)); let mut dst = dst.lock().unwrap(); - t!(dst.write_all(&create_header(which, n as u32))); + t!(dst.write_all(&create_header(which, n as u64))); if n > 0 { t!(dst.write_all(&b[..n])); } else { @@ -377,7 +377,7 @@ fn my_copy(src: &mut dyn Read, which: u8, dst: &Mutex) { fn batch_copy(buf: &[u8], which: u8, dst: &Mutex) { let n = buf.len(); let mut dst = dst.lock().unwrap(); - t!(dst.write_all(&create_header(which, n as u32))); + t!(dst.write_all(&create_header(which, n as u64))); if n > 0 { t!(dst.write_all(buf)); // Marking buf finished @@ -385,13 +385,13 @@ fn batch_copy(buf: &[u8], which: u8, dst: &Mutex) { } } -const fn create_header(which: u8, n: u32) -> [u8; 5] { +const fn create_header(which: u8, n: u64) -> [u8; 9] { let bytes = n.to_be_bytes(); - [which, bytes[0], bytes[1], bytes[2], bytes[3]] + [which, bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]] } -fn read_u32(r: &mut dyn Read) -> u32 { - let mut len = [0; 4]; +fn read_u64(r: &mut dyn Read) -> u64 { + let mut len = [0; 8]; t!(r.read_exact(&mut len)); - u32::from_be_bytes(len) + u64::from_be_bytes(len) } diff --git a/tests/ui/associated-type-bounds/return-type-notation/issue-120208-higher-ranked-const.rs b/tests/ui/associated-type-bounds/return-type-notation/issue-120208-higher-ranked-const.rs new file mode 100644 index 0000000000000..3b350e14fd937 --- /dev/null +++ b/tests/ui/associated-type-bounds/return-type-notation/issue-120208-higher-ranked-const.rs @@ -0,0 +1,17 @@ +// edition: 2021 + +#![feature(return_type_notation)] +//~^ WARN the feature `return_type_notation` is incomplete + +trait HealthCheck { + async fn check() -> bool; +} + +async fn do_health_check_par(hc: HC) +where + HC: HealthCheck + Send + 'static, + //~^ ERROR return type notation is not allowed for functions that have const parameters +{ +} + +fn main() {} diff --git a/tests/ui/associated-type-bounds/return-type-notation/issue-120208-higher-ranked-const.stderr b/tests/ui/associated-type-bounds/return-type-notation/issue-120208-higher-ranked-const.stderr new file mode 100644 index 0000000000000..8a3f037d00321 --- /dev/null +++ b/tests/ui/associated-type-bounds/return-type-notation/issue-120208-higher-ranked-const.stderr @@ -0,0 +1,20 @@ +warning: the feature `return_type_notation` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/issue-120208-higher-ranked-const.rs:3:12 + | +LL | #![feature(return_type_notation)] + | ^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #109417 for more information + = note: `#[warn(incomplete_features)]` on by default + +error: return type notation is not allowed for functions that have const parameters + --> $DIR/issue-120208-higher-ranked-const.rs:12:21 + | +LL | async fn check() -> bool; + | -------------- const parameter declared here +... +LL | HC: HealthCheck + Send + 'static, + | ^^^^^^^^^^^^^ + +error: aborting due to 1 previous error; 1 warning emitted + diff --git a/tests/ui/extern/auxiliary/issue-80074-macro-2.rs b/tests/ui/extern/auxiliary/issue-80074-macro-2.rs new file mode 100644 index 0000000000000..bc87a2b543478 --- /dev/null +++ b/tests/ui/extern/auxiliary/issue-80074-macro-2.rs @@ -0,0 +1,3 @@ +// edition:2018 + +macro_rules! m { () => {}; } diff --git a/tests/ui/extern/auxiliary/issue-80074-macro.rs b/tests/ui/extern/auxiliary/issue-80074-macro.rs index 30e0f19ab8d84..3e912d977159a 100644 --- a/tests/ui/extern/auxiliary/issue-80074-macro.rs +++ b/tests/ui/extern/auxiliary/issue-80074-macro.rs @@ -2,3 +2,5 @@ macro_rules! foo_ { () => {}; } use foo_ as foo; + +macro_rules! bar { () => {}; } diff --git a/tests/ui/extern/issue-80074.rs b/tests/ui/extern/issue-80074.rs index f83027d4abfd2..6e4f176de8201 100644 --- a/tests/ui/extern/issue-80074.rs +++ b/tests/ui/extern/issue-80074.rs @@ -1,10 +1,20 @@ // edition:2018 -// build-pass // aux-crate:issue_80074=issue-80074-macro.rs +// aux-crate:issue_80074_2=issue-80074-macro-2.rs #[macro_use] extern crate issue_80074; +#[macro_use(m)] +extern crate issue_80074_2; +//~^^ ERROR: imported macro not found + fn main() { foo!(); + //~^ WARN: macro `foo` is private + //~| WARN: it will become a hard error in a future release! + bar!(); + //~^ ERROR: cannot find macro `bar` in this scope + m!(); + //~^ ERROR: cannot find macro `m` in this scope } diff --git a/tests/ui/extern/issue-80074.stderr b/tests/ui/extern/issue-80074.stderr new file mode 100644 index 0000000000000..b30b761593e88 --- /dev/null +++ b/tests/ui/extern/issue-80074.stderr @@ -0,0 +1,31 @@ +error[E0469]: imported macro not found + --> $DIR/issue-80074.rs:8:13 + | +LL | #[macro_use(m)] + | ^ + +error: cannot find macro `bar` in this scope + --> $DIR/issue-80074.rs:16:5 + | +LL | bar!(); + | ^^^ + +error: cannot find macro `m` in this scope + --> $DIR/issue-80074.rs:18:5 + | +LL | m!(); + | ^ + +warning: macro `foo` is private + --> $DIR/issue-80074.rs:13:5 + | +LL | foo!(); + | ^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #120192 + = note: `#[warn(private_macro_use)]` on by default + +error: aborting due to 3 previous errors; 1 warning emitted + +For more information about this error, try `rustc --explain E0469`. diff --git a/tests/ui/feature-gates/feature-gate-exclusive-range-pattern.stderr b/tests/ui/feature-gates/feature-gate-exclusive-range-pattern.stderr index d05971fb0529d..5e3d34aa9f3ed 100644 --- a/tests/ui/feature-gates/feature-gate-exclusive-range-pattern.stderr +++ b/tests/ui/feature-gates/feature-gate-exclusive-range-pattern.stderr @@ -7,6 +7,7 @@ LL | 0 .. 3 => {} = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = help: use an inclusive range pattern, like N..=M error: aborting due to 1 previous error diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr b/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr index e2fab14ffc235..cf3bde9705b6b 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr +++ b/tests/ui/half-open-range-patterns/range_pat_interactions1.stderr @@ -21,6 +21,7 @@ LL | if let n @ 2..3|4 = x { = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = help: use an inclusive range pattern, like N..=M error[E0658]: exclusive range pattern syntax is experimental --> $DIR/range_pat_interactions1.rs:14:23 @@ -31,6 +32,7 @@ LL | } else if let 2..3 | 4 = x { = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = help: use an inclusive range pattern, like N..=M error: aborting due to 4 previous errors diff --git a/tests/ui/half-open-range-patterns/range_pat_interactions3.stderr b/tests/ui/half-open-range-patterns/range_pat_interactions3.stderr index fe233496261c1..51cc22e7d5602 100644 --- a/tests/ui/half-open-range-patterns/range_pat_interactions3.stderr +++ b/tests/ui/half-open-range-patterns/range_pat_interactions3.stderr @@ -17,6 +17,7 @@ LL | 1 | -3..0 => first_or.push(x), = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = help: use an inclusive range pattern, like N..=M error[E0658]: exclusive range pattern syntax is experimental --> $DIR/range_pat_interactions3.rs:12:18 @@ -27,6 +28,7 @@ LL | y @ (0..5 | 6) => or_two.push(y), = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = help: use an inclusive range pattern, like N..=M error[E0658]: exclusive range pattern syntax is experimental --> $DIR/range_pat_interactions3.rs:14:17 @@ -37,6 +39,7 @@ LL | y @ 0..const { 5 + 1 } => assert_eq!(y, 5), = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = help: use an inclusive range pattern, like N..=M error[E0658]: exclusive range pattern syntax is experimental --> $DIR/range_pat_interactions3.rs:18:17 @@ -47,6 +50,7 @@ LL | y @ ..-7 => assert_eq!(y, -8), = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = help: use an inclusive range pattern, like N..=M error: aborting due to 5 previous errors diff --git a/tests/ui/half-open-range-patterns/slice_pattern_syntax_problem1.stderr b/tests/ui/half-open-range-patterns/slice_pattern_syntax_problem1.stderr index 5edd877bee03e..be8f3aa5051e3 100644 --- a/tests/ui/half-open-range-patterns/slice_pattern_syntax_problem1.stderr +++ b/tests/ui/half-open-range-patterns/slice_pattern_syntax_problem1.stderr @@ -17,6 +17,7 @@ LL | let [a @ 3.., b @ ..3, c @ 4..6, ..] = xs; = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = help: use an inclusive range pattern, like N..=M error[E0658]: exclusive range pattern syntax is experimental --> $DIR/slice_pattern_syntax_problem1.rs:4:32 @@ -27,6 +28,7 @@ LL | let [a @ 3.., b @ ..3, c @ 4..6, ..] = xs; = note: see issue #37854 for more information = help: add `#![feature(exclusive_range_pattern)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = help: use an inclusive range pattern, like N..=M error: aborting due to 3 previous errors diff --git a/tests/ui/imports/auxiliary/issue-119369-extern.rs b/tests/ui/imports/auxiliary/issue-119369-extern.rs new file mode 100644 index 0000000000000..278cbfa1e3e54 --- /dev/null +++ b/tests/ui/imports/auxiliary/issue-119369-extern.rs @@ -0,0 +1,2 @@ +use std::vec; +use std::hash::Hash; diff --git a/tests/ui/imports/issue-119369.rs b/tests/ui/imports/issue-119369.rs new file mode 100644 index 0000000000000..0b4dc3f4654bd --- /dev/null +++ b/tests/ui/imports/issue-119369.rs @@ -0,0 +1,14 @@ +// check-pass +// aux-build: issue-119369-extern.rs + +// https://github.com/rust-lang/rust/pull/119369#issuecomment-1874905662 + +#[macro_use] +extern crate issue_119369_extern; + +#[derive(Hash)] +struct A; + +fn main() { + let _: Vec = vec![]; +} diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.stderr index e17859eb40f27..d9d7e297f8e97 100644 --- a/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.stderr +++ b/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.stderr @@ -97,6 +97,7 @@ error: call to function `sse2` with `#[target_feature]` is unsafe and requires u LL | sse2(); | ^^^^^^ call to function with `#[target_feature]` | + = note: for more information, see issue #71668 = help: in order for the call to be safe, the context requires the following additional target feature: sse2 = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]` note: an unsafe function restricts its caller, but its body is safe by default diff --git a/tests/ui/unsafe/edition-2024-unsafe_op_in_unsafe_fn.stderr b/tests/ui/unsafe/edition-2024-unsafe_op_in_unsafe_fn.stderr index 1187c2d80f388..2eb1754392e1a 100644 --- a/tests/ui/unsafe/edition-2024-unsafe_op_in_unsafe_fn.stderr +++ b/tests/ui/unsafe/edition-2024-unsafe_op_in_unsafe_fn.stderr @@ -4,6 +4,7 @@ warning: call to unsafe function `unsf` is unsafe and requires unsafe block (err LL | unsf(); | ^^^^^^ call to unsafe function | + = note: for more information, see issue #71668 = note: consult the function's documentation for information on how to avoid undefined behavior note: an unsafe function restricts its caller, but its body is safe by default --> $DIR/edition-2024-unsafe_op_in_unsafe_fn.rs:9:1 diff --git a/tests/ui/unsafe/auxiliary/external_unsafe_macro.rs b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/auxiliary/external_unsafe_macro.rs similarity index 100% rename from tests/ui/unsafe/auxiliary/external_unsafe_macro.rs rename to tests/ui/unsafe/unsafe_op_in_unsafe_fn/auxiliary/external_unsafe_macro.rs diff --git a/tests/ui/unsafe/unsafe_op_in_unsafe_fn/edition_2024_default.rs b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/edition_2024_default.rs new file mode 100644 index 0000000000000..4196b1cae249d --- /dev/null +++ b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/edition_2024_default.rs @@ -0,0 +1,20 @@ +// edition: 2024 +// compile-flags: -Zunstable-options +// check-pass + +// Tests that `unsafe_op_in_unsafe_fn` is warn-by-default in edition 2024 and that the +// `unused_unsafe` lint does not consider the inner unsafe block to be unused. +#![crate_type = "lib"] +#![deny(unused_unsafe)] + +unsafe fn unsf() {} + +unsafe fn foo() { + unsf(); + //~^ WARN + + // no unused_unsafe + unsafe { + unsf(); + } +} diff --git a/tests/ui/unsafe/unsafe_op_in_unsafe_fn/edition_2024_default.stderr b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/edition_2024_default.stderr new file mode 100644 index 0000000000000..e6d1d4e5cc780 --- /dev/null +++ b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/edition_2024_default.stderr @@ -0,0 +1,17 @@ +warning: call to unsafe function `unsf` is unsafe and requires unsafe block (error E0133) + --> $DIR/edition_2024_default.rs:13:5 + | +LL | unsf(); + | ^^^^^^ call to unsafe function + | + = note: for more information, see issue #71668 + = note: consult the function's documentation for information on how to avoid undefined behavior +note: an unsafe function restricts its caller, but its body is safe by default + --> $DIR/edition_2024_default.rs:12:1 + | +LL | unsafe fn foo() { + | ^^^^^^^^^^^^^^^ + = note: `#[warn(unsafe_op_in_unsafe_fn)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/unsafe/unsafe_op_in_unsafe_fn/in_2024_compatibility.rs b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/in_2024_compatibility.rs new file mode 100644 index 0000000000000..ec04bf8d63143 --- /dev/null +++ b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/in_2024_compatibility.rs @@ -0,0 +1,9 @@ +#![deny(rust_2024_compatibility)] +#![crate_type = "lib"] + +unsafe fn unsf() {} + +unsafe fn foo() { + unsf(); + //~^ ERROR call to unsafe function `unsf` is unsafe and requires unsafe block +} diff --git a/tests/ui/unsafe/unsafe_op_in_unsafe_fn/in_2024_compatibility.stderr b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/in_2024_compatibility.stderr new file mode 100644 index 0000000000000..5092c1e689d67 --- /dev/null +++ b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/in_2024_compatibility.stderr @@ -0,0 +1,22 @@ +error: call to unsafe function `unsf` is unsafe and requires unsafe block (error E0133) + --> $DIR/in_2024_compatibility.rs:7:5 + | +LL | unsf(); + | ^^^^^^ call to unsafe function + | + = note: for more information, see issue #71668 + = note: consult the function's documentation for information on how to avoid undefined behavior +note: an unsafe function restricts its caller, but its body is safe by default + --> $DIR/in_2024_compatibility.rs:6:1 + | +LL | unsafe fn foo() { + | ^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/in_2024_compatibility.rs:1:9 + | +LL | #![deny(rust_2024_compatibility)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + = note: `#[deny(unsafe_op_in_unsafe_fn)]` implied by `#[deny(rust_2024_compatibility)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/unsafe/rfc-2585-unsafe_op_in_unsafe_fn.rs b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/rfc-2585-unsafe_op_in_unsafe_fn.rs similarity index 100% rename from tests/ui/unsafe/rfc-2585-unsafe_op_in_unsafe_fn.rs rename to tests/ui/unsafe/unsafe_op_in_unsafe_fn/rfc-2585-unsafe_op_in_unsafe_fn.rs diff --git a/tests/ui/unsafe/rfc-2585-unsafe_op_in_unsafe_fn.stderr b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/rfc-2585-unsafe_op_in_unsafe_fn.stderr similarity index 87% rename from tests/ui/unsafe/rfc-2585-unsafe_op_in_unsafe_fn.stderr rename to tests/ui/unsafe/unsafe_op_in_unsafe_fn/rfc-2585-unsafe_op_in_unsafe_fn.stderr index ea0659b2e104d..4bc604a110ea1 100644 --- a/tests/ui/unsafe/rfc-2585-unsafe_op_in_unsafe_fn.stderr +++ b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/rfc-2585-unsafe_op_in_unsafe_fn.stderr @@ -4,6 +4,7 @@ error: call to unsafe function `unsf` is unsafe and requires unsafe block (error LL | unsf(); | ^^^^^^ call to unsafe function | + = note: for more information, see issue #71668 = note: consult the function's documentation for information on how to avoid undefined behavior note: an unsafe function restricts its caller, but its body is safe by default --> $DIR/rfc-2585-unsafe_op_in_unsafe_fn.rs:8:1 @@ -22,6 +23,7 @@ error: dereference of raw pointer is unsafe and requires unsafe block (error E01 LL | *PTR; | ^^^^ dereference of raw pointer | + = note: for more information, see issue #71668 = note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior error: use of mutable static is unsafe and requires unsafe block (error E0133) @@ -30,6 +32,7 @@ error: use of mutable static is unsafe and requires unsafe block (error E0133) LL | VOID = (); | ^^^^ use of mutable static | + = note: for more information, see issue #71668 = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior error: unnecessary `unsafe` block @@ -50,6 +53,7 @@ error: call to unsafe function `unsf` is unsafe and requires unsafe block (error LL | unsf(); | ^^^^^^ call to unsafe function | + = note: for more information, see issue #71668 = note: consult the function's documentation for information on how to avoid undefined behavior note: an unsafe function restricts its caller, but its body is safe by default --> $DIR/rfc-2585-unsafe_op_in_unsafe_fn.rs:23:1 @@ -69,6 +73,7 @@ error: dereference of raw pointer is unsafe and requires unsafe block (error E01 LL | *PTR; | ^^^^ dereference of raw pointer | + = note: for more information, see issue #71668 = note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior error: use of mutable static is unsafe and requires unsafe block (error E0133) @@ -77,6 +82,7 @@ error: use of mutable static is unsafe and requires unsafe block (error E0133) LL | VOID = (); | ^^^^ use of mutable static | + = note: for more information, see issue #71668 = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior error: unnecessary `unsafe` block diff --git a/tests/ui/unsafe/wrapping-unsafe-block-sugg.fixed b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/wrapping-unsafe-block-sugg.fixed similarity index 56% rename from tests/ui/unsafe/wrapping-unsafe-block-sugg.fixed rename to tests/ui/unsafe/unsafe_op_in_unsafe_fn/wrapping-unsafe-block-sugg.fixed index 20f4fe847daca..8f5cc014a3bab 100644 --- a/tests/ui/unsafe/wrapping-unsafe-block-sugg.fixed +++ b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/wrapping-unsafe-block-sugg.fixed @@ -11,50 +11,60 @@ unsafe fn unsf() {} pub unsafe fn foo() { unsafe { //~^ NOTE an unsafe function restricts its caller, but its body is safe by default unsf(); //~ ERROR call to unsafe function `unsf` is unsafe - //~^ NOTE - //~| NOTE + //~^ NOTE call to unsafe function + //~| NOTE for more information, see issue #71668 + //~| NOTE consult the function's documentation unsf(); //~ ERROR call to unsafe function `unsf` is unsafe - //~^ NOTE - //~| NOTE + //~^ NOTE call to unsafe function + //~| NOTE for more information, see issue #71668 + //~| NOTE consult the function's documentation }} pub unsafe fn bar(x: *const i32) -> i32 { unsafe { //~^ NOTE an unsafe function restricts its caller, but its body is safe by default let y = *x; //~ ERROR dereference of raw pointer is unsafe and requires unsafe block - //~^ NOTE - //~| NOTE + //~^ NOTE dereference of raw pointer + //~| NOTE for more information, see issue #71668 + //~| NOTE raw pointers may be null y + *x //~ ERROR dereference of raw pointer is unsafe and requires unsafe block - //~^ NOTE - //~| NOTE + //~^ NOTE dereference of raw pointer + //~| NOTE for more information, see issue #71668 + //~| NOTE raw pointers may be null }} static mut BAZ: i32 = 0; pub unsafe fn baz() -> i32 { unsafe { //~^ NOTE an unsafe function restricts its caller, but its body is safe by default let y = BAZ; //~ ERROR use of mutable static is unsafe and requires unsafe block - //~^ NOTE - //~| NOTE + //~^ NOTE use of mutable static + //~| NOTE for more information, see issue #71668 + //~| NOTE mutable statics can be mutated by multiple threads y + BAZ //~ ERROR use of mutable static is unsafe and requires unsafe block - //~^ NOTE - //~| NOTE + //~^ NOTE use of mutable static + //~| NOTE for more information, see issue #71668 + //~| NOTE mutable statics can be mutated by multiple threads }} macro_rules! unsafe_macro { () => (unsf()) } //~^ ERROR call to unsafe function `unsf` is unsafe -//~| NOTE -//~| NOTE +//~| NOTE call to unsafe function +//~| NOTE for more information, see issue #71668 +//~| NOTE consult the function's documentation //~| ERROR call to unsafe function `unsf` is unsafe -//~| NOTE -//~| NOTE +//~| NOTE call to unsafe function +//~| NOTE for more information, see issue #71668 +//~| NOTE consult the function's documentation pub unsafe fn unsafe_in_macro() { unsafe { //~^ NOTE an unsafe function restricts its caller, but its body is safe by default unsafe_macro!(); - //~^ NOTE - //~| NOTE + //~^ NOTE in this expansion + //~| NOTE in this expansion + //~| NOTE in this expansion unsafe_macro!(); - //~^ NOTE - //~| NOTE + //~^ NOTE in this expansion + //~| NOTE in this expansion + //~| NOTE in this expansion }} pub unsafe fn unsafe_in_external_macro() { diff --git a/tests/ui/unsafe/wrapping-unsafe-block-sugg.rs b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/wrapping-unsafe-block-sugg.rs similarity index 56% rename from tests/ui/unsafe/wrapping-unsafe-block-sugg.rs rename to tests/ui/unsafe/unsafe_op_in_unsafe_fn/wrapping-unsafe-block-sugg.rs index 13a446d2d2480..90c9e28239693 100644 --- a/tests/ui/unsafe/wrapping-unsafe-block-sugg.rs +++ b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/wrapping-unsafe-block-sugg.rs @@ -11,50 +11,60 @@ unsafe fn unsf() {} pub unsafe fn foo() { //~^ NOTE an unsafe function restricts its caller, but its body is safe by default unsf(); //~ ERROR call to unsafe function `unsf` is unsafe - //~^ NOTE - //~| NOTE + //~^ NOTE call to unsafe function + //~| NOTE for more information, see issue #71668 + //~| NOTE consult the function's documentation unsf(); //~ ERROR call to unsafe function `unsf` is unsafe - //~^ NOTE - //~| NOTE + //~^ NOTE call to unsafe function + //~| NOTE for more information, see issue #71668 + //~| NOTE consult the function's documentation } pub unsafe fn bar(x: *const i32) -> i32 { //~^ NOTE an unsafe function restricts its caller, but its body is safe by default let y = *x; //~ ERROR dereference of raw pointer is unsafe and requires unsafe block - //~^ NOTE - //~| NOTE + //~^ NOTE dereference of raw pointer + //~| NOTE for more information, see issue #71668 + //~| NOTE raw pointers may be null y + *x //~ ERROR dereference of raw pointer is unsafe and requires unsafe block - //~^ NOTE - //~| NOTE + //~^ NOTE dereference of raw pointer + //~| NOTE for more information, see issue #71668 + //~| NOTE raw pointers may be null } static mut BAZ: i32 = 0; pub unsafe fn baz() -> i32 { //~^ NOTE an unsafe function restricts its caller, but its body is safe by default let y = BAZ; //~ ERROR use of mutable static is unsafe and requires unsafe block - //~^ NOTE - //~| NOTE + //~^ NOTE use of mutable static + //~| NOTE for more information, see issue #71668 + //~| NOTE mutable statics can be mutated by multiple threads y + BAZ //~ ERROR use of mutable static is unsafe and requires unsafe block - //~^ NOTE - //~| NOTE + //~^ NOTE use of mutable static + //~| NOTE for more information, see issue #71668 + //~| NOTE mutable statics can be mutated by multiple threads } macro_rules! unsafe_macro { () => (unsf()) } //~^ ERROR call to unsafe function `unsf` is unsafe -//~| NOTE -//~| NOTE +//~| NOTE call to unsafe function +//~| NOTE for more information, see issue #71668 +//~| NOTE consult the function's documentation //~| ERROR call to unsafe function `unsf` is unsafe -//~| NOTE -//~| NOTE +//~| NOTE call to unsafe function +//~| NOTE for more information, see issue #71668 +//~| NOTE consult the function's documentation pub unsafe fn unsafe_in_macro() { //~^ NOTE an unsafe function restricts its caller, but its body is safe by default unsafe_macro!(); - //~^ NOTE - //~| NOTE + //~^ NOTE in this expansion + //~| NOTE in this expansion + //~| NOTE in this expansion unsafe_macro!(); - //~^ NOTE - //~| NOTE + //~^ NOTE in this expansion + //~| NOTE in this expansion + //~| NOTE in this expansion } pub unsafe fn unsafe_in_external_macro() { diff --git a/tests/ui/unsafe/wrapping-unsafe-block-sugg.stderr b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/wrapping-unsafe-block-sugg.stderr similarity index 74% rename from tests/ui/unsafe/wrapping-unsafe-block-sugg.stderr rename to tests/ui/unsafe/unsafe_op_in_unsafe_fn/wrapping-unsafe-block-sugg.stderr index 84b58bc028858..b9f5969474d00 100644 --- a/tests/ui/unsafe/wrapping-unsafe-block-sugg.stderr +++ b/tests/ui/unsafe/unsafe_op_in_unsafe_fn/wrapping-unsafe-block-sugg.stderr @@ -4,6 +4,7 @@ error: call to unsafe function `unsf` is unsafe and requires unsafe block (error LL | unsf(); | ^^^^^^ call to unsafe function | + = note: for more information, see issue #71668 = note: consult the function's documentation for information on how to avoid undefined behavior note: an unsafe function restricts its caller, but its body is safe by default --> $DIR/wrapping-unsafe-block-sugg.rs:11:1 @@ -17,57 +18,62 @@ LL | #![deny(unsafe_op_in_unsafe_fn)] | ^^^^^^^^^^^^^^^^^^^^^^ error: call to unsafe function `unsf` is unsafe and requires unsafe block (error E0133) - --> $DIR/wrapping-unsafe-block-sugg.rs:16:5 + --> $DIR/wrapping-unsafe-block-sugg.rs:17:5 | LL | unsf(); | ^^^^^^ call to unsafe function | + = note: for more information, see issue #71668 = note: consult the function's documentation for information on how to avoid undefined behavior error: dereference of raw pointer is unsafe and requires unsafe block (error E0133) - --> $DIR/wrapping-unsafe-block-sugg.rs:23:13 + --> $DIR/wrapping-unsafe-block-sugg.rs:25:13 | LL | let y = *x; | ^^ dereference of raw pointer | + = note: for more information, see issue #71668 = note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior note: an unsafe function restricts its caller, but its body is safe by default - --> $DIR/wrapping-unsafe-block-sugg.rs:21:1 + --> $DIR/wrapping-unsafe-block-sugg.rs:23:1 | LL | pub unsafe fn bar(x: *const i32) -> i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: dereference of raw pointer is unsafe and requires unsafe block (error E0133) - --> $DIR/wrapping-unsafe-block-sugg.rs:26:9 + --> $DIR/wrapping-unsafe-block-sugg.rs:29:9 | LL | y + *x | ^^ dereference of raw pointer | + = note: for more information, see issue #71668 = note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior error: use of mutable static is unsafe and requires unsafe block (error E0133) - --> $DIR/wrapping-unsafe-block-sugg.rs:34:13 + --> $DIR/wrapping-unsafe-block-sugg.rs:38:13 | LL | let y = BAZ; | ^^^ use of mutable static | + = note: for more information, see issue #71668 = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior note: an unsafe function restricts its caller, but its body is safe by default - --> $DIR/wrapping-unsafe-block-sugg.rs:32:1 + --> $DIR/wrapping-unsafe-block-sugg.rs:36:1 | LL | pub unsafe fn baz() -> i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: use of mutable static is unsafe and requires unsafe block (error E0133) - --> $DIR/wrapping-unsafe-block-sugg.rs:37:9 + --> $DIR/wrapping-unsafe-block-sugg.rs:42:9 | LL | y + BAZ | ^^^ use of mutable static | + = note: for more information, see issue #71668 = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior error: call to unsafe function `unsf` is unsafe and requires unsafe block (error E0133) - --> $DIR/wrapping-unsafe-block-sugg.rs:42:36 + --> $DIR/wrapping-unsafe-block-sugg.rs:48:36 | LL | macro_rules! unsafe_macro { () => (unsf()) } | ^^^^^^ call to unsafe function @@ -75,16 +81,17 @@ LL | macro_rules! unsafe_macro { () => (unsf()) } LL | unsafe_macro!(); | --------------- in this macro invocation | + = note: for more information, see issue #71668 = note: consult the function's documentation for information on how to avoid undefined behavior note: an unsafe function restricts its caller, but its body is safe by default - --> $DIR/wrapping-unsafe-block-sugg.rs:50:1 + --> $DIR/wrapping-unsafe-block-sugg.rs:58:1 | LL | pub unsafe fn unsafe_in_macro() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `unsafe_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: call to unsafe function `unsf` is unsafe and requires unsafe block (error E0133) - --> $DIR/wrapping-unsafe-block-sugg.rs:42:36 + --> $DIR/wrapping-unsafe-block-sugg.rs:48:36 | LL | macro_rules! unsafe_macro { () => (unsf()) } | ^^^^^^ call to unsafe function @@ -92,6 +99,7 @@ LL | macro_rules! unsafe_macro { () => (unsf()) } LL | unsafe_macro!(); | --------------- in this macro invocation | + = note: for more information, see issue #71668 = note: consult the function's documentation for information on how to avoid undefined behavior = note: this error originates in the macro `unsafe_macro` (in Nightly builds, run with -Z macro-backtrace for more info)