diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 7d59beb958104..279c11257a6f9 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -2081,18 +2081,20 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { ); let mut expected_ty = String::from("fn("); - for (i, arg) in fn_abi.args.iter().enumerate() { - if i > 0 { - expected_ty.push_str(", "); + ty::print::with_no_trimmed_paths!({ + for (i, arg) in fn_abi.args.iter().enumerate() { + if i > 0 { + expected_ty.push_str(", "); + } + use std::fmt::Write; + write!(&mut expected_ty, "{}", arg.layout.ty).unwrap(); } - use std::fmt::Write; - write!(&mut expected_ty, "{}", arg.layout.ty).unwrap(); - } - expected_ty.push(')'); - if !fn_abi.ret.layout.ty.is_unit() { - use std::fmt::Write; - write!(&mut expected_ty, " -> {}", fn_abi.ret.layout.ty).unwrap(); - } + expected_ty.push(')'); + if !fn_abi.ret.layout.ty.is_unit() { + use std::fmt::Write; + write!(&mut expected_ty, " -> {}", fn_abi.ret.layout.ty).unwrap(); + } + }); // 4 for cfi-icall (indirect call) let check_kind = 4; diff --git a/compiler/rustc_hir_typeck/src/intrinsicck.rs b/compiler/rustc_hir_typeck/src/intrinsicck.rs index d2432a7e39af6..364b7e35ae920 100644 --- a/compiler/rustc_hir_typeck/src/intrinsicck.rs +++ b/compiler/rustc_hir_typeck/src/intrinsicck.rs @@ -1,11 +1,13 @@ use hir::HirId; use rustc_abi::Primitive::Pointer; use rustc_abi::VariantIdx; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::codes::*; use rustc_errors::struct_span_code_err; use rustc_hir as hir; use rustc_index::Idx; use rustc_middle::ty::layout::{LayoutError, SizeSkeleton}; +use rustc_middle::ty::offload_meta::is_region_ty; use rustc_middle::ty::{self, Ty, TyCtxt, Unnormalized}; use rustc_span::def_id::LocalDefId; use rustc_span::{ErrorGuaranteed, bug}; @@ -134,6 +136,28 @@ fn check_transmute<'tcx>( } } +fn contains_nested_offload_region<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + let mut visited = FxHashSet::default(); + let mut stack = vec![ty]; + let mut is_root = true; + + while let Some(ty) = stack.pop() { + if !is_root && is_region_ty(tcx, ty) { + return true; + } + is_root = false; + + if let ty::Adt(def, args) = *ty.kind() + && visited.insert(def.did()) + { + stack.extend(def.all_fields().map(|field| field.ty(tcx, args).skip_norm_wip())); + } + stack.extend(ty.walk().skip(1).filter_map(|arg| arg.as_type())); + } + + false +} + fn check_offload<'tcx>( tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, @@ -205,7 +229,23 @@ fn check_offload<'tcx>( { let norm_input_ty = normalize(input_ty); let norm_arg_ty = normalize(arg_ty); - if norm_input_ty != norm_arg_ty { + + if contains_nested_offload_region(tcx, norm_input_ty) + || contains_nested_offload_region(tcx, norm_arg_ty) + { + let guar = tcx + .sess + .dcx() + .struct_span_err( + span, + format!( + "offload kernel argument {i} contains a `Region` nested inside another \ + type. Pass the `Region` by value so it can be mapped like a slice" + ), + ) + .emit_err(); + result = Err(guar); + } else if norm_input_ty != norm_arg_ty { let guar = tcx .sess .dcx() diff --git a/compiler/rustc_index_macros/src/lib.rs b/compiler/rustc_index_macros/src/lib.rs index 27fc4680ea289..b0bc40a9d8b1a 100644 --- a/compiler/rustc_index_macros/src/lib.rs +++ b/compiler/rustc_index_macros/src/lib.rs @@ -26,6 +26,7 @@ mod newtype; /// /// Accepted attributes for customization: /// - `#[stable_hash]`: derives `StableHash`. +/// - `#[default]`: derives `std::default::Default` returning `Self::ZERO`. /// - `#[encodable]`: derives `Encodable`/`Decodable`. /// - `#[orderable]`: derives `PartialOrd`/`Ord`, plus step-related methods. /// - `#[debug_format = "Foo({})"]`: derives `Debug` with particular output. diff --git a/compiler/rustc_index_macros/src/newtype.rs b/compiler/rustc_index_macros/src/newtype.rs index aaa025bd7cbdb..52f1d070718e8 100644 --- a/compiler/rustc_index_macros/src/newtype.rs +++ b/compiler/rustc_index_macros/src/newtype.rs @@ -24,6 +24,7 @@ impl Parse for Newtype { let mut encodable = false; let mut ord = false; let mut stable_hash = false; + let mut default = false; let mut gate_rustc_only = quote! {}; let mut gate_rustc_only_cfg = quote! { all() }; @@ -46,6 +47,10 @@ impl Parse for Newtype { stable_hash = true; false } + "default" => { + default = true; + false + } "max" => { let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta else { @@ -185,6 +190,19 @@ impl Parse for Newtype { quote! {} }; + let default_impl = if default { + quote! { + impl std::default::Default for #name { + #[inline] + fn default() -> Self { + Self::ZERO + } + } + } + } else { + quote! {} + }; + let debug_impl = quote! { impl ::std::fmt::Debug for #name { fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { @@ -323,6 +341,8 @@ impl Parse for Newtype { #stable_hash_impl + #default_impl + impl From<#name> for u32 { #[inline] fn from(v: #name) -> u32 { diff --git a/compiler/rustc_middle/src/ty/offload_meta.rs b/compiler/rustc_middle/src/ty/offload_meta.rs index a58e517e05f61..6cf8abc482260 100644 --- a/compiler/rustc_middle/src/ty/offload_meta.rs +++ b/compiler/rustc_middle/src/ty/offload_meta.rs @@ -1,5 +1,6 @@ use bitflags::bitflags; use rustc_abi::{BackendRepr, TyAbiInterface}; +use rustc_span::sym; use rustc_target::callconv::ArgAbi; use crate::ty::{self, PseudoCanonicalInput, Ty, TyCtxt, TypingEnv}; @@ -75,6 +76,25 @@ impl OffloadMetadata { where Ty<'tcx>: TyAbiInterface<'tcx, C>, { + if let Some(elem_ty) = region_element_ty(tcx, ty) { + let ptr = OffloadMetadata::from_ty(tcx, Ty::new_slice(tcx, elem_ty)); + let len = OffloadMetadata::from_ty(tcx, tcx.types.usize); + // `Region` is a `{ ptr, len }` pair, but field order is not guaranteed. + return arg_abi + .layout + .fields + .index_by_increasing_offset() + .filter(|&i| arg_abi.layout.field(cx, i).size.bytes() != 0) + .map(|i| { + if arg_abi.layout.field(cx, i).ty == tcx.types.usize { + (len, tcx.types.usize) + } else { + (ptr, Ty::new_mut_ptr(tcx, elem_ty)) + } + }) + .collect(); + } + match arg_abi.layout.backend_repr { BackendRepr::ScalarPair { a: _, b: _, b_offset: _ } => (0..2) .map(|i| { @@ -87,6 +107,22 @@ impl OffloadMetadata { } } +pub fn is_region_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + matches!( + ty.kind(), + ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::offload_region) + ) +} + +fn region_element_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { + if is_region_ty(tcx, ty) { + let ty::Adt(_, args) = ty.kind() else { unreachable!() }; + Some(args.type_at(1)) + } else { + None + } +} + // FIXME(Sa4dUs): implement a solid logic to determine the payload size fn get_payload_size<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> OffloadSize { match ty.kind() { diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 4745a26ade953..c107f6ce4f64c 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -134,15 +134,10 @@ impl fmt::Debug for ExpnId { rustc_index::newtype_index! { /// A unique ID associated with a macro invocation and expansion. #[debug_format = "expn{}"] + #[default] pub struct LocalExpnId {} } -impl Default for LocalExpnId { - fn default() -> LocalExpnId { - LocalExpnId::ROOT - } -} - // To ensure correctness of incremental compilation, // `LocalExpnId` must not implement `Ord` or `PartialOrd`. // See https://github.com/rust-lang/rust/issues/90317. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 65bea89e1c713..cfee78578fbb5 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1514,6 +1514,7 @@ symbols! { offload, offload_get_num_devices, offload_kernel, + offload_region, offset, offset_of, offset_of_enum, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index ef10906730734..a97effe2eb0fd 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -6788,7 +6788,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { tcx, ObligationCause::dummy(), param_env, - ty::TraitRef::new(tcx, trait_def_id, trait_args), + parent_trait_pred.rebind(ty::TraitRef::new(tcx, trait_def_id, trait_args)), ); self.predicate_may_hold(&obligation) }) diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs index eab69fa224d92..a1c5bcacc4b5f 100644 --- a/compiler/rustc_type_ir/src/lib.rs +++ b/compiler/rustc_type_ir/src/lib.rs @@ -375,6 +375,7 @@ rustc_index::newtype_index! { /// type -- an idealized representative of "types in general" that we /// use for checking generic functions. #[stable_hash] + #[default] #[encodable] #[orderable] #[debug_format = "U{}"] @@ -422,12 +423,6 @@ impl UniverseIndex { } } -impl Default for UniverseIndex { - fn default() -> Self { - Self::ROOT - } -} - rustc_index::newtype_index! { #[stable_hash] #[encodable] diff --git a/library/core/src/array/mod.rs b/library/core/src/array/mod.rs index fcd635a03c652..205c2815be1b4 100644 --- a/library/core/src/array/mod.rs +++ b/library/core/src/array/mod.rs @@ -430,20 +430,12 @@ const impl PartialOrd for [T; N] { } #[inline] - fn __chaining_lt(&self, other: &[T; N]) -> ControlFlow { - <[T] as PartialOrd>::__chaining_lt(self, other) + fn __chaining_less(&self, other: &[T; N]) -> ControlFlow { + <[T] as PartialOrd>::__chaining_less(self, other) } #[inline] - fn __chaining_le(&self, other: &[T; N]) -> ControlFlow { - <[T] as PartialOrd>::__chaining_le(self, other) - } - #[inline] - fn __chaining_ge(&self, other: &[T; N]) -> ControlFlow { - <[T] as PartialOrd>::__chaining_ge(self, other) - } - #[inline] - fn __chaining_gt(&self, other: &[T; N]) -> ControlFlow { - <[T] as PartialOrd>::__chaining_gt(self, other) + fn __chaining_greater(&self, other: &[T; N]) -> ControlFlow { + <[T] as PartialOrd>::__chaining_greater(self, other) } } diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs index 1b57bed2738f7..83a2925678e19 100644 --- a/library/core/src/cmp.rs +++ b/library/core/src/cmp.rs @@ -1557,8 +1557,10 @@ pub const trait PartialOrd: self.partial_cmp(other).is_some_and(Ordering::is_ge) } - /// If `self == other`, returns `ControlFlow::Continue(())`. - /// Otherwise, returns `ControlFlow::Break(self < other)`. + /// If `self == other`, returns `Continue(())`. + /// Otherwise, returns `Break(self < other)`, or equivalently `Break(self <= other)`. + /// The only difference when chaining `<` and `<=` is how an equality result is + /// interpreted at the end of the chain. /// /// This is useful for chaining together calls when implementing a lexical /// `PartialOrd::lt`, as it allows types (like primitives) which can cheaply @@ -1568,33 +1570,17 @@ pub const trait PartialOrd: // Added to improve the behaviour of tuples; not necessarily stabilization-track. #[unstable(feature = "partial_ord_chaining_methods", issue = "none")] #[doc(hidden)] - fn __chaining_lt(&self, other: &Rhs) -> ControlFlow { + fn __chaining_less(&self, other: &Rhs) -> ControlFlow { default_chaining_impl(self, other, Ordering::is_lt) } - /// Same as `__chaining_lt`, but for `<=` instead of `<`. + /// Same as `__chaining_less`, but for `>`/`>=` instead of `<`/`<=`. #[inline] #[unstable(feature = "partial_ord_chaining_methods", issue = "none")] #[doc(hidden)] - fn __chaining_le(&self, other: &Rhs) -> ControlFlow { - default_chaining_impl(self, other, Ordering::is_le) - } - - /// Same as `__chaining_lt`, but for `>` instead of `<`. - #[inline] - #[unstable(feature = "partial_ord_chaining_methods", issue = "none")] - #[doc(hidden)] - fn __chaining_gt(&self, other: &Rhs) -> ControlFlow { + fn __chaining_greater(&self, other: &Rhs) -> ControlFlow { default_chaining_impl(self, other, Ordering::is_gt) } - - /// Same as `__chaining_lt`, but for `>=` instead of `<`. - #[inline] - #[unstable(feature = "partial_ord_chaining_methods", issue = "none")] - #[doc(hidden)] - fn __chaining_ge(&self, other: &Rhs) -> ControlFlow { - default_chaining_impl(self, other, Ordering::is_ge) - } } #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] @@ -2225,25 +2211,17 @@ mod impls { // the `Break` case and the comparison will correctly return `false`. #[inline] - fn __chaining_lt(&self, other: &Self) -> ControlFlow { + fn __chaining_less(&self, other: &Self) -> ControlFlow { let (lhs, rhs) = (*self, *other); + // We arbitrarily pick `<` here. Using `<=` gives the same result. if lhs == rhs { Continue(()) } else { Break(lhs < rhs) } } #[inline] - fn __chaining_le(&self, other: &Self) -> ControlFlow { - let (lhs, rhs) = (*self, *other); - if lhs == rhs { Continue(()) } else { Break(lhs <= rhs) } - } - #[inline] - fn __chaining_gt(&self, other: &Self) -> ControlFlow { + fn __chaining_greater(&self, other: &Self) -> ControlFlow { let (lhs, rhs) = (*self, *other); + // We arbitrarily pick `>` here. Using `>=` gives the same result. if lhs == rhs { Continue(()) } else { Break(lhs > rhs) } } - #[inline] - fn __chaining_ge(&self, other: &Self) -> ControlFlow { - let (lhs, rhs) = (*self, *other); - if lhs == rhs { Continue(()) } else { Break(lhs >= rhs) } - } }; } @@ -2484,20 +2462,12 @@ mod impls { PartialOrd::ge(*self, *other) } #[inline] - fn __chaining_lt(&self, other: &&B) -> ControlFlow { - PartialOrd::__chaining_lt(*self, *other) - } - #[inline] - fn __chaining_le(&self, other: &&B) -> ControlFlow { - PartialOrd::__chaining_le(*self, *other) + fn __chaining_less(&self, other: &&B) -> ControlFlow { + PartialOrd::__chaining_less(*self, *other) } #[inline] - fn __chaining_gt(&self, other: &&B) -> ControlFlow { - PartialOrd::__chaining_gt(*self, *other) - } - #[inline] - fn __chaining_ge(&self, other: &&B) -> ControlFlow { - PartialOrd::__chaining_ge(*self, *other) + fn __chaining_greater(&self, other: &&B) -> ControlFlow { + PartialOrd::__chaining_greater(*self, *other) } } #[stable(feature = "rust1", since = "1.0.0")] @@ -2559,20 +2529,12 @@ mod impls { PartialOrd::ge(*self, *other) } #[inline] - fn __chaining_lt(&self, other: &&mut B) -> ControlFlow { - PartialOrd::__chaining_lt(*self, *other) - } - #[inline] - fn __chaining_le(&self, other: &&mut B) -> ControlFlow { - PartialOrd::__chaining_le(*self, *other) - } - #[inline] - fn __chaining_gt(&self, other: &&mut B) -> ControlFlow { - PartialOrd::__chaining_gt(*self, *other) + fn __chaining_less(&self, other: &&mut B) -> ControlFlow { + PartialOrd::__chaining_less(*self, *other) } #[inline] - fn __chaining_ge(&self, other: &&mut B) -> ControlFlow { - PartialOrd::__chaining_ge(*self, *other) + fn __chaining_greater(&self, other: &&mut B) -> ControlFlow { + PartialOrd::__chaining_greater(*self, *other) } } #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/core/src/offload/mod.rs b/library/core/src/offload/mod.rs index 3d85621361209..aefe3a4eb164d 100644 --- a/library/core/src/offload/mod.rs +++ b/library/core/src/offload/mod.rs @@ -1,4 +1,6 @@ // offload module +use core::ptr::NonNull; + #[unstable(feature = "gpu_offload", issue = "131513")] pub use crate::macros::builtin::offload_kernel; #[unstable(feature = "gpu_offload", issue = "131513")] @@ -37,58 +39,52 @@ pub use crate::offload; /// ``` #[macro_export] #[unstable(feature = "gpu_offload", issue = "131513")] -#[allow_internal_unstable(core_intrinsics)] +#[allow_internal_unstable(core_intrinsics, gpu_offload)] macro_rules! offload { - ( $($field:ident = $val:expr),* $(,)? ) => { - $crate::offload!(@munch - [ $($field = $val),* ]; - kernel = NONE; - workgroup_dim = ([1, 1, 1]); - thread_dim = ([1, 1, 1]); - dyn_cache = (0); - device = NONE; - args = NONE - ) - }; - - (@munch [kernel = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = NONE; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = (SOME $val); workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = $device; args = $a) + (@munch [kernel = $val:expr $(, $($rest:tt)*)?]; kernel = NONE; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + $crate::offload!(@munch [ $($($rest)*)? ]; kernel = (SOME $val); workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = $device; args = $a) }; - (@munch [kernel = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = (SOME $old:expr); workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + (@munch [kernel = $val:expr $(, $($rest:tt)*)?]; kernel = (SOME $old:expr); workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { compile_error!("duplicate field `kernel`") }; - (@munch [workgroup_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = ([1, 1, 1]); thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = (SOME $val); thread_dim = $t; dyn_cache = $d; device = $device; args = $a) + (@munch [workgroup_dim = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = ([1, 1, 1]); thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + $crate::offload!(@munch [ $($($rest)*)? ]; kernel = $k; workgroup_dim = (SOME $val); thread_dim = $t; dyn_cache = $d; device = $device; args = $a) }; - (@munch [workgroup_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = (SOME $old:expr); thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + (@munch [workgroup_dim = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = (SOME $old:expr); thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { compile_error!("duplicate field `workgroup_dim`") }; - (@munch [thread_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = ([1, 1, 1]); dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = (SOME $val); dyn_cache = $d; device = $device; args = $a) + (@munch [thread_dim = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = ([1, 1, 1]); dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + $crate::offload!(@munch [ $($($rest)*)? ]; kernel = $k; workgroup_dim = $w; thread_dim = (SOME $val); dyn_cache = $d; device = $device; args = $a) }; - (@munch [thread_dim = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = (SOME $old:expr); dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + (@munch [thread_dim = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = (SOME $old:expr); dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { compile_error!("duplicate field `thread_dim`") }; - (@munch [dyn_cache = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = (0); device = $device:tt; args = $a:tt) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = (SOME $val); device = $device; args = $a) + (@munch [dyn_cache = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = (0); device = $device:tt; args = $a:tt) => { + $crate::offload!(@munch [ $($($rest)*)? ]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = (SOME $val); device = $device; args = $a) }; - (@munch [dyn_cache = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = (SOME $old:expr); device = $device:tt; args = $a:tt) => { + (@munch [dyn_cache = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = (SOME $old:expr); device = $device:tt; args = $a:tt) => { compile_error!("duplicate field `dyn_cache`") }; - (@munch [device = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = NONE; args = $a:tt) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = (SOME $val); args = $a) + (@munch [device = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = NONE; args = $a:tt) => { + $crate::offload!(@munch [ $($($rest)*)? ]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = (SOME $val); args = $a) }; - (@munch [device = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = (SOME $old:expr); args = $a:tt) => { + (@munch [device = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = (SOME $old:expr); args = $a:tt) => { compile_error!("duplicate field `device`") }; - (@munch [args = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = NONE) => { - $crate::offload!(@munch [$($rest_f = $rest_v),*]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = $device; args = (SOME $val)) + (@munch [args = ($($arg:expr),* $(,)?) $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = NONE) => { + $crate::offload!(@munch [ $($($rest)*)? ]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = $device; args = (SOME_TUPLE ($($arg),*))) + }; + (@munch [args = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = NONE) => { + $crate::offload!(@munch [ $($($rest)*)? ]; kernel = $k; workgroup_dim = $w; thread_dim = $t; dyn_cache = $d; device = $device; args = (SOME $val)) }; - (@munch [args = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = (SOME $old:expr)) => { + (@munch [args = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = (SOME $old:expr)) => { + compile_error!("duplicate field `args`") + }; + (@munch [args = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = (SOME_TUPLE $old:tt)) => { compile_error!("duplicate field `args`") }; - (@munch [$invalid:ident = $val:expr $(, $rest_f:ident = $rest_v:expr)*]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { + (@munch [$invalid:ident = $val:expr $(, $($rest:tt)*)?]; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = $a:tt) => { compile_error!(concat!("unknown field `", stringify!($invalid), "`")) }; @@ -98,14 +94,32 @@ macro_rules! offload { (@munch []; kernel = $k:tt; workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = NONE) => { compile_error!("missing `args`") }; - (@munch []; kernel = (SOME $kernel:expr); workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = (SOME $args:expr)) => { + (@munch []; kernel = (SOME $kernel:expr); workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = (SOME_TUPLE ($($arg:expr),* $(,)?))) => {{ + #[allow(unused_imports)] + use $crate::offload::{DefaultLaunchCheck as _, RegionLaunchCheck as _}; + + let __offload_grid = $crate::offload!(@value $w); + let __offload_block = $crate::offload!(@value $t); + $crate::intrinsics::offload::<_, _, ()>( $kernel, - $crate::offload!(@value $w), - $crate::offload!(@value $t), + __offload_grid, + __offload_block, $crate::offload!(@value $d), $crate::offload!(@device $device), - $args, + ($({ + let __offload_arg = $arg; + (&__offload_arg).__offload_check_launch(__offload_grid, __offload_block); + __offload_arg + },)*), + ) + }}; + // Accepting an arbitrary expression here would let callers bypass the per-argument launch + // performed for `args = (..)` by binding the tuple to avariable first. + // FIXME(offload): run those checks on arbitrary expressions + (@munch []; kernel = (SOME $kernel:expr); workgroup_dim = $w:tt; thread_dim = $t:tt; dyn_cache = $d:tt; device = $device:tt; args = (SOME $args:expr)) => { + compile_error!( + "`args` must be a tuple literal" ) }; @@ -124,4 +138,201 @@ macro_rules! offload { ); device } }; + + ( $($tt:tt)* ) => { + $crate::offload!(@munch + [ $($tt)* ]; + kernel = NONE; + workgroup_dim = ([1, 1, 1]); + thread_dim = ([1, 1, 1]); + dyn_cache = (0); + device = NONE; + args = NONE + ) + }; +} + +// Region & Partitioning Strategy + +/// Error returned by [`PartitioningStrategy::check_launch`] when a launch +/// configuration is not compatible with a partitioning strategy. +#[derive(Debug)] +#[unstable(feature = "gpu_offload", issue = "131513")] +pub struct LaunchError { + message: &'static str, +} + +impl LaunchError { + /// Creates a new [`LaunchError`] with the given message. + #[unstable(feature = "gpu_offload", issue = "131513")] + pub const fn new(message: &'static str) -> Self { + Self { message } + } +} + +#[unstable(feature = "gpu_offload", issue = "131513")] +impl core::fmt::Display for LaunchError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.message) + } +} + +/// Defines how execution units access memory regions. +/// +/// # Safety +/// +/// Implementations must guarantee that generated views are disjoint. +/// +/// Implementations must also implement [`check_launch`] correctly since it is soundness-critical. +/// +/// [`check_launch`]: PartitioningStrategy::check_launch +#[unstable(feature = "gpu_offload", issue = "131513")] +pub unsafe trait PartitioningStrategy { + /// Read-only view type for the partitioned memory region. + type View<'a, T: 'a>; + + /// Mutable view type for the partitioned memory region. + type ViewMut<'a, T: 'a>; + + /// Returns the execution index of the current unit. + fn index() -> usize; + + /// Returns a read-only view of the region for the current execution context. + /// + /// # Safety + /// + /// `ptr` must point to `len` valid, initialized elements of type `T`. + /// The memory must stay valid for lifetime `'a`. + unsafe fn get<'a, T>(ptr: NonNull, len: usize) -> Option>; + + /// Returns a mutable view of the region for the current execution context. + /// + /// # Safety + /// + /// `ptr` must point to `len` valid, initialized elements of type `T`. + /// The memory must stay valid for lifetime `'a`. + /// The returned view must be disjoint from all other active views. + unsafe fn get_mut<'a, T>(ptr: NonNull, len: usize) -> Option>; + + /// Checks that a kernel using this strategy can be launched with `len` + /// elements on a `grid` by `block` launch configuration. + /// + /// This is called automatically before the kernel is launched. + /// + /// # Soundness + /// + /// This check is soundness-critical. It may only return `Ok` unconditionally + /// if and only if a launch is valid for arbitrary `len`, `grid` and `block` values. + /// If there is any configuration for which this strategy would produce incorrect + /// results or cause undefined behavior, it must return a [`LaunchError`] instead. + fn check_launch(len: usize, grid: [u32; 3], block: [u32; 3]) -> Result<(), LaunchError>; +} + +/// A memory region bound to a partitioning strategy. +#[derive(Debug)] +#[unstable(feature = "gpu_offload", issue = "131513")] +#[rustc_diagnostic_item = "offload_region"] +pub struct Region<'a, T, S: PartitioningStrategy> { + ptr: NonNull, + len: usize, + _marker: core::marker::PhantomData<(&'a mut [T], S)>, +} + +/// Raw representation used to build a [`Region`] from common aggregate types. +#[derive(Debug)] +#[unstable(feature = "gpu_offload", issue = "131513")] +pub struct RawRegion<'a, T> { + ptr: NonNull, + len: usize, + _marker: core::marker::PhantomData<&'a mut [T]>, +} + +impl<'a, T> From<&'a mut [T]> for RawRegion<'a, T> { + fn from(data: &'a mut [T]) -> Self { + // SAFETY: `data.as_mut_ptr()` is non-null, because it is derived from a reference. + let ptr = unsafe { NonNull::new_unchecked(data.as_mut_ptr()) }; + Self { ptr, len: data.len(), _marker: core::marker::PhantomData } + } +} + +impl<'a, T, const N: usize> From<&'a mut [T; N]> for RawRegion<'a, T> { + fn from(data: &'a mut [T; N]) -> Self { + // SAFETY: `data.as_mut_ptr()` is non-null, because it is derived from a reference. + let ptr = unsafe { NonNull::new_unchecked(data.as_mut_ptr()) }; + Self { ptr, len: N, _marker: core::marker::PhantomData } + } +} + +#[unstable(feature = "gpu_offload", issue = "131513")] +impl<'a, T, S: PartitioningStrategy> Region<'a, T, S> { + /// Creates a new partitioned region from data convertible into a [`RawRegion`]. + pub fn new(data: D) -> Self + where + D: Into>, + { + let raw = data.into(); + Self { ptr: raw.ptr, len: raw.len, _marker: core::marker::PhantomData } + } + + /// Returns a read-only view for the current execution context. + pub fn get(&self) -> Option> { + // SAFETY: `self.ptr` points to `self.len` valid elements for lifetime `'a`. + unsafe { S::get(self.ptr, self.len) } + } + + /// Returns a mutable view for the current execution context. + pub fn get_mut(&mut self) -> Option> { + // SAFETY: `self.ptr` points to `self.len` valid elements for lifetime `'a`. + // The strategy guarantees that the returned view is disjoint. + unsafe { S::get_mut(self.ptr, self.len) } + } + + /// Checks that this region can be launched with the given configuration. + /// + /// Called automatically by the [`offload!`] macro before launching a kernel. + pub fn check_launch(&self, grid: [u32; 3], block: [u32; 3]) -> Result<(), LaunchError> { + S::check_launch(self.len, grid, block) + } + + /// Reborrows the region, producing a new region that aliases the same memory with + /// the lifetime of the borrow. + pub fn reborrow(&mut self) -> Region<'_, T, S> { + Region { ptr: self.ptr, len: self.len, _marker: core::marker::PhantomData } + } +} + +// Launch validation helpers. + +/// Implementation detail of [`offload!`]: validates a [`Region`] argument against +/// the launch configuration of an offload call. +#[doc(hidden)] +#[unstable(feature = "gpu_offload", issue = "131513")] +pub trait RegionLaunchCheck { + /// Panics if the region's partitioning strategy rejects the launch. + fn __offload_check_launch(&self, grid: [u32; 3], block: [u32; 3]); +} + +#[unstable(feature = "gpu_offload", issue = "131513")] +impl<'a, T, S: PartitioningStrategy> RegionLaunchCheck for Region<'a, T, S> { + fn __offload_check_launch(&self, grid: [u32; 3], block: [u32; 3]) { + if let Err(err) = self.check_launch(grid, block) { + panic!( + "offload launch is not supported by the region's partitioning strategy: {}", + err, + ); + } + } +} + +/// no-op launch check for kernel arguments that are not [`Region`]s. +#[doc(hidden)] +#[unstable(feature = "gpu_offload", issue = "131513")] +pub trait DefaultLaunchCheck { + /// Does nothing. + fn __offload_check_launch(&self, grid: [u32; 3], block: [u32; 3]); +} + +#[unstable(feature = "gpu_offload", issue = "131513")] +impl DefaultLaunchCheck for &T { + fn __offload_check_launch(&self, _grid: [u32; 3], _block: [u32; 3]) {} } diff --git a/library/core/src/slice/cmp.rs b/library/core/src/slice/cmp.rs index fcdde758fa7df..3abf75cbf5e2b 100644 --- a/library/core/src/slice/cmp.rs +++ b/library/core/src/slice/cmp.rs @@ -70,35 +70,27 @@ const impl PartialOrd for [T] { // see , or we generate // niche discriminant checks in a way that doesn't trigger it. - as_underlying(self.__chaining_lt(other)) == 1 + as_underlying(self.__chaining_less(other)) == 1 } #[inline] fn le(&self, other: &Self) -> bool { - as_underlying(self.__chaining_le(other)) != 0 + as_underlying(self.__chaining_less(other)) != 0 } #[inline] fn gt(&self, other: &Self) -> bool { - as_underlying(self.__chaining_gt(other)) == 1 + as_underlying(self.__chaining_greater(other)) == 1 } #[inline] fn ge(&self, other: &Self) -> bool { - as_underlying(self.__chaining_ge(other)) != 0 + as_underlying(self.__chaining_greater(other)) != 0 } #[inline] - fn __chaining_lt(&self, other: &Self) -> ControlFlow { - SliceChain::chaining_lt(self, other) + fn __chaining_less(&self, other: &Self) -> ControlFlow { + SliceChain::chaining_less(self, other) } #[inline] - fn __chaining_le(&self, other: &Self) -> ControlFlow { - SliceChain::chaining_le(self, other) - } - #[inline] - fn __chaining_gt(&self, other: &Self) -> ControlFlow { - SliceChain::chaining_gt(self, other) - } - #[inline] - fn __chaining_ge(&self, other: &Self) -> ControlFlow { - SliceChain::chaining_ge(self, other) + fn __chaining_greater(&self, other: &Self) -> ControlFlow { + SliceChain::chaining_greater(self, other) } } @@ -167,10 +159,8 @@ const trait SlicePartialOrd: Sized { #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] // intermediate trait for specialization of slice's PartialOrd chaining methods const trait SliceChain: Sized { - fn chaining_lt(left: &[Self], right: &[Self]) -> ControlFlow; - fn chaining_le(left: &[Self], right: &[Self]) -> ControlFlow; - fn chaining_gt(left: &[Self], right: &[Self]) -> ControlFlow; - fn chaining_ge(left: &[Self], right: &[Self]) -> ControlFlow; + fn chaining_less(left: &[Self], right: &[Self]) -> ControlFlow; + fn chaining_greater(left: &[Self], right: &[Self]) -> ControlFlow; } type AlwaysBreak = ControlFlow; @@ -192,17 +182,11 @@ const impl SlicePartialOrd for A { #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] const impl SliceChain for A { - default fn chaining_lt(left: &[Self], right: &[Self]) -> ControlFlow { - chaining_impl(left, right, PartialOrd::__chaining_lt, usize::__chaining_lt) + default fn chaining_less(left: &[Self], right: &[Self]) -> ControlFlow { + chaining_impl(left, right, PartialOrd::__chaining_less, usize::__chaining_less) } - default fn chaining_le(left: &[Self], right: &[Self]) -> ControlFlow { - chaining_impl(left, right, PartialOrd::__chaining_le, usize::__chaining_le) - } - default fn chaining_gt(left: &[Self], right: &[Self]) -> ControlFlow { - chaining_impl(left, right, PartialOrd::__chaining_gt, usize::__chaining_gt) - } - default fn chaining_ge(left: &[Self], right: &[Self]) -> ControlFlow { - chaining_impl(left, right, PartialOrd::__chaining_ge, usize::__chaining_ge) + default fn chaining_greater(left: &[Self], right: &[Self]) -> ControlFlow { + chaining_impl(left, right, PartialOrd::__chaining_greater, usize::__chaining_greater) } } @@ -343,33 +327,19 @@ const impl SliceOrd for A { #[rustc_const_unstable(feature = "const_cmp", issue = "143800")] const impl SliceChain for A { #[inline] - fn chaining_lt(left: &[Self], right: &[Self]) -> ControlFlow { + fn chaining_less(left: &[Self], right: &[Self]) -> ControlFlow { match SliceOrd::compare(left, right) { Ordering::Equal => ControlFlow::Continue(()), ne => ControlFlow::Break(ne.is_lt()), } } #[inline] - fn chaining_le(left: &[Self], right: &[Self]) -> ControlFlow { - match SliceOrd::compare(left, right) { - Ordering::Equal => ControlFlow::Continue(()), - ne => ControlFlow::Break(ne.is_le()), - } - } - #[inline] - fn chaining_gt(left: &[Self], right: &[Self]) -> ControlFlow { + fn chaining_greater(left: &[Self], right: &[Self]) -> ControlFlow { match SliceOrd::compare(left, right) { Ordering::Equal => ControlFlow::Continue(()), ne => ControlFlow::Break(ne.is_gt()), } } - #[inline] - fn chaining_ge(left: &[Self], right: &[Self]) -> ControlFlow { - match SliceOrd::compare(left, right) { - Ordering::Equal => ControlFlow::Continue(()), - ne => ControlFlow::Break(ne.is_ge()), - } - } } pub(super) trait SliceContains: Sized { diff --git a/library/core/src/tuple.rs b/library/core/src/tuple.rs index e394d3d048c9e..cb61ffadee908 100644 --- a/library/core/src/tuple.rs +++ b/library/core/src/tuple.rs @@ -71,35 +71,27 @@ macro_rules! tuple_impls { } #[inline] fn lt(&self, other: &($($T,)+)) -> bool { - lexical_ord!(lt, __chaining_lt, $( ${ignore($T)} self.${index()}, other.${index()} ),+) + lexical_ord!(lt, __chaining_less, $( ${ignore($T)} self.${index()}, other.${index()} ),+) } #[inline] fn le(&self, other: &($($T,)+)) -> bool { - lexical_ord!(le, __chaining_le, $( ${ignore($T)} self.${index()}, other.${index()} ),+) + lexical_ord!(le, __chaining_less, $( ${ignore($T)} self.${index()}, other.${index()} ),+) } #[inline] fn ge(&self, other: &($($T,)+)) -> bool { - lexical_ord!(ge, __chaining_ge, $( ${ignore($T)} self.${index()}, other.${index()} ),+) + lexical_ord!(ge, __chaining_greater, $( ${ignore($T)} self.${index()}, other.${index()} ),+) } #[inline] fn gt(&self, other: &($($T,)+)) -> bool { - lexical_ord!(gt, __chaining_gt, $( ${ignore($T)} self.${index()}, other.${index()} ),+) + lexical_ord!(gt, __chaining_greater, $( ${ignore($T)} self.${index()}, other.${index()} ),+) } #[inline] - fn __chaining_lt(&self, other: &($($T,)+)) -> ControlFlow { - lexical_chain!(__chaining_lt, $( ${ignore($T)} self.${index()}, other.${index()} ),+) + fn __chaining_less(&self, other: &($($T,)+)) -> ControlFlow { + lexical_chain!(__chaining_less, $( ${ignore($T)} self.${index()}, other.${index()} ),+) } #[inline] - fn __chaining_le(&self, other: &($($T,)+)) -> ControlFlow { - lexical_chain!(__chaining_le, $( ${ignore($T)} self.${index()}, other.${index()} ),+) - } - #[inline] - fn __chaining_gt(&self, other: &($($T,)+)) -> ControlFlow { - lexical_chain!(__chaining_gt, $( ${ignore($T)} self.${index()}, other.${index()} ),+) - } - #[inline] - fn __chaining_ge(&self, other: &($($T,)+)) -> ControlFlow { - lexical_chain!(__chaining_ge, $( ${ignore($T)} self.${index()}, other.${index()} ),+) + fn __chaining_greater(&self, other: &($($T,)+)) -> ControlFlow { + lexical_chain!(__chaining_greater, $( ${ignore($T)} self.${index()}, other.${index()} ),+) } } } diff --git a/src/doc/rustc-dev-guide/src/offload/internals.md b/src/doc/rustc-dev-guide/src/offload/internals.md index da6333f984aa6..8fad6109c2f60 100644 --- a/src/doc/rustc-dev-guide/src/offload/internals.md +++ b/src/doc/rustc-dev-guide/src/offload/internals.md @@ -53,3 +53,28 @@ Trying to teach the whole rustc middle and backend to be aware that any symbol n and it is questionable why we should make the whole compiler more complex, if the alternative is a ~5 line cargo wrapper. We still control the full compilation pipeline and have both host and device code available, therefore there shouldn't be a runtime performance difference between the two approaches. + +## Safety and region design + +The `Region` type (`core::offload::Region`) is a host-side handle to a contiguous memory region +that should be partitioned across the execution units (threads) of an offloaded kernel. Creating a +`Region` takes a mutable borrow of the underlying slice, so Rust's borrow checker prevents the host +from accessing the memory while the region is alive. `Region` is deliberately neither `Copy` nor +`Clone`, which avoids aliasing the same memory through multiple handles. A region can be reborrowed +with `Region::reborrow` when a shorter lifetime is needed. + +The way a region is split across execution units is described by the `PartitioningStrategy` trait. +Given the region pointer and length, the strategy's `get` and `get_mut` methods return a read-only +or mutable view for the current execution unit, or `None` where the current unit has no work to do. +`PartitioningStrategy` is an `unsafe` trait: an implementation must guarantee that all views it +hands out are disjoint, otherwise two units could write to the same location. + +A strategy exposes `check_launch(len, grid, block)`, which decides whether a given number of elements +can be launched with a particular grid/block configuration. This check is soundness-critical: it may +only return `Ok` unconditionally if and only if a launch is valid for arbitrary `len`, `grid`, and +`block` values. If any configuration would produce incorrect results or cause undefined behavior, it +must return a `LaunchError`. The `offload!` macro calls `check_launch` for every `Region` argument +before launching, and panics if the check fails. + +Only `Region`s passed to a kernel by value can be mapped like slices. A `Region` nested inside another +type is rejected during type checking (see `contains_nested_offload_region` in `rustc_hir_typeck`). diff --git a/tests/codegen-llvm/gpu_offload/auxiliary/offload_strategies.rs b/tests/codegen-llvm/gpu_offload/auxiliary/offload_strategies.rs new file mode 100644 index 0000000000000..0669d92b604d1 --- /dev/null +++ b/tests/codegen-llvm/gpu_offload/auxiliary/offload_strategies.rs @@ -0,0 +1,58 @@ +//@ edition: 2024 + +#![feature(gpu_offload)] + +use core::offload::{LaunchError, PartitioningStrategy}; +use core::ptr::NonNull; + +#[derive(Debug, Clone, Copy)] +pub struct Dummy; + +unsafe impl PartitioningStrategy for Dummy { + type View<'a, T: 'a> = &'a T; + type ViewMut<'a, T: 'a> = &'a mut T; + + fn index() -> usize { + 0 + } + + unsafe fn get<'a, T>(_ptr: NonNull, _len: usize) -> Option> { + None + } + + unsafe fn get_mut<'a, T>(_ptr: NonNull, _len: usize) -> Option> { + None + } + + fn check_launch(_len: usize, _grid: [u32; 3], _block: [u32; 3]) -> Result<(), LaunchError> { + Ok(()) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Linear1D; + +unsafe impl PartitioningStrategy for Linear1D { + type View<'a, T: 'a> = &'a T; + type ViewMut<'a, T: 'a> = &'a mut T; + + fn index() -> usize { + 0 + } + + unsafe fn get<'a, T>(_ptr: NonNull, _len: usize) -> Option> { + None + } + + unsafe fn get_mut<'a, T>(_ptr: NonNull, _len: usize) -> Option> { + None + } + + fn check_launch(_len: usize, grid: [u32; 3], block: [u32; 3]) -> Result<(), LaunchError> { + if grid[1] == 1 && grid[2] == 1 && block[1] == 1 && block[2] == 1 { + Ok(()) + } else { + Err(LaunchError::new("")) + } + } +} diff --git a/tests/codegen-llvm/gpu_offload/region_host.rs b/tests/codegen-llvm/gpu_offload/region_host.rs new file mode 100644 index 0000000000000..badbd81a77dac --- /dev/null +++ b/tests/codegen-llvm/gpu_offload/region_host.rs @@ -0,0 +1,46 @@ +//@ compile-flags: -Zoffload=Test -Zunstable-options -C opt-level=1 -Clto=fat +//@ no-prefer-dynamic +//@ needs-offload +//@ edition: 2024 +//@ aux-crate: offload_strategies=offload_strategies.rs + +// This test verifies that a `Region` kernel argument is mapped like a slice. +#![feature(abi_gpu_kernel)] +#![feature(core_intrinsics)] +#![feature(gpu_offload)] +#![feature(rustc_attrs)] +#![no_main] + +extern crate core; + +use core::offload::Region; + +use offload_strategies::Dummy; + +// CHECK: @anon.[[ID:.*]].0 = private unnamed_addr constant [23 x i8] c";unknown;unknown;0;0;;\00", align 1 + +// CHECK-DAG: @.offload_sizes.[[K:[^ ]*foo]] = private unnamed_addr constant [2 x i64] [i64 0, i64 8] +// CHECK-DAG: @.offload_maptypes.[[K]].begin = private unnamed_addr constant [2 x i64] [i64 1, i64 768] +// CHECK-DAG: @.offload_maptypes.[[K]].kernel = private unnamed_addr constant [2 x i64] [i64 32, i64 800] +// CHECK-DAG: @.offload_maptypes.[[K]].end = private unnamed_addr constant [2 x i64] [i64 2, i64 0] + +// CHECK: define{{( dso_local)?}} void @main() +// CHECK: %.offload_sizes = alloca [2 x i64], align 8 +// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr {{.*}} %.offload_sizes, ptr {{.*}} @.offload_sizes.[[K]], i64 16, i1 false) +// CHECK: store i64 16, ptr %.offload_sizes, align 8 +// CHECK: call void @__tgt_target_data_begin_mapper(ptr nonnull @anon.[[ID]].1, i64 -1, i32 2, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull %.offload_sizes, ptr nonnull @.offload_maptypes.[[K]].begin, ptr null, ptr null) +// CHECK: call i32 @__tgt_target_kernel(ptr nonnull @anon.[[ID]].1, i64 -1, i32 1, i32 1, ptr nonnull @.[[K]].region_id, ptr nonnull %kernel_args) +// CHECK-NEXT: call void @__tgt_target_data_end_mapper(ptr nonnull @anon.[[ID]].1, i64 -1, i32 2, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull %.offload_sizes, ptr nonnull @.offload_maptypes.[[K]].end, ptr null, ptr null) + +#[unsafe(no_mangle)] +fn main() { + let mut x = [0.0f32; 4]; + core::offload::offload! { + kernel = foo, + args = (Region::::new(&mut x as &mut [f32]),), + }; +} + +fn foo(region: Region<'_, f32, Dummy>) { + unreachable!(); +} diff --git a/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-diag-mode.rs b/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-diag-mode.rs index 4d58872db7c45..c5f70afbb611c 100644 --- a/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-diag-mode.rs +++ b/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-diag-mode.rs @@ -7,6 +7,9 @@ #![crate_type = "lib"] +#[repr(transparent)] +pub struct Type1(pub i32); + pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { // CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} // CHECK: start: @@ -21,3 +24,19 @@ pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { // CHECK-NEXT: unreachable f(arg) } + +pub fn bar(f: fn(Type1) -> Type1, arg: Type1) -> Type1 { + // CHECK-LABEL: define{{.*}}bar{{.*}}!type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + // CHECK: start: + // CHECK: [[TT:%.+]] = call i1 @llvm.type.test(ptr {{%f|%0}}, metadata !"{{[[:print:]]+}}") + // CHECK-NEXT: br i1 [[TT]], label %type_test.pass, label %type_test.fail + // CHECK: type_test.pass: + // CHECK-NEXT: {{%.+}} = call i32 %f(i32{{.*}} %arg) + // CHECK: type_test.fail: + // CHECK-NEXT: {{%.+}} = ptrtoint ptr {{%f|%0}} to i64 + // cfi-diag-NEXT: call void @__ubsan_handle_cfi_check_fail_abort( + // cfi-diag-minimal-runtime-NEXT: call void @__ubsan_handle_cfi_check_fail_minimal_abort( + + // CHECK-NEXT: unreachable + f(arg) +} diff --git a/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-recover-mode.rs b/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-recover-mode.rs index 3ea97e44c21e7..ac916b6babbef 100644 --- a/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-recover-mode.rs +++ b/tests/codegen-llvm/sanitizer/cfi/emit-type-checks-recover-mode.rs @@ -7,6 +7,9 @@ #![crate_type = "lib"] +#[repr(transparent)] +pub struct Type1(pub i32); + pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { // CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} // CHECK: start: @@ -21,3 +24,18 @@ pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 { // CHECK-NEXT: br label %type_test.pass f(arg) } + +pub fn bar(f: fn(Type1) -> Type1, arg: Type1) -> Type1 { + // CHECK-LABEL: define{{.*}}bar{{.*}}!type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + // CHECK: start: + // CHECK: [[TT:%.+]] = call i1 @llvm.type.test(ptr {{%f|%0}}, metadata !"{{[[:print:]]+}}") + // CHECK-NEXT: br i1 [[TT]], label %type_test.pass, label %type_test.fail + // CHECK: type_test.pass: + // CHECK-NEXT: {{%.+}} = call i32 %f(i32{{.*}} %arg) + // CHECK: type_test.fail: + // CHECK-NEXT: {{%.+}} = ptrtoint ptr {{%f|%0}} to i64 + // cfi-recover-NEXT: call void @__ubsan_handle_cfi_check_fail( + // cfi-recover-minimal-runtime-NEXT: call void @__ubsan_handle_cfi_check_fail_minimal( + // CHECK-NEXT: br label %type_test.pass + f(arg) +} diff --git a/tests/mir-opt/issues/issue_163598.chain_generic.runtime-optimized.after.panic-abort.mir b/tests/mir-opt/issues/issue_163598.chain_generic.runtime-optimized.after.panic-abort.mir index 28259cd0bc312..07b01cb980158 100644 --- a/tests/mir-opt/issues/issue_163598.chain_generic.runtime-optimized.after.panic-abort.mir +++ b/tests/mir-opt/issues/issue_163598.chain_generic.runtime-optimized.after.panic-abort.mir @@ -4,7 +4,7 @@ fn chain_generic(_1: &(T, U), _2: &(T, U)) -> ControlFlow { debug l => _1; debug r => _2; let mut _0: std::ops::ControlFlow; - scope 1 (inlined core::tuple::::__chaining_le) { + scope 1 (inlined core::tuple::::__chaining_less) { let _3: &T; let _4: &T; let mut _5: isize; @@ -24,7 +24,7 @@ fn chain_generic(_1: &(T, U), _2: &(T, U)) -> ControlFlow { _3 = &((*_1).0: T); StorageLive(_4); _4 = &((*_2).0: T); - _0 = ::__chaining_le(move _3, move _4) -> [return: bb1, unwind unreachable]; + _0 = ::__chaining_less(move _3, move _4) -> [return: bb1, unwind unreachable]; } bb1: { @@ -45,7 +45,7 @@ fn chain_generic(_1: &(T, U), _2: &(T, U)) -> ControlFlow { _6 = &((*_1).1: U); StorageLive(_7); _7 = &((*_2).1: U); - _0 = ::__chaining_le(move _6, move _7) -> [return: bb4, unwind unreachable]; + _0 = ::__chaining_less(move _6, move _7) -> [return: bb4, unwind unreachable]; } bb4: { diff --git a/tests/mir-opt/issues/issue_163598.chain_generic.runtime-optimized.after.panic-unwind.mir b/tests/mir-opt/issues/issue_163598.chain_generic.runtime-optimized.after.panic-unwind.mir index c9216daf84bd7..58290f030ec64 100644 --- a/tests/mir-opt/issues/issue_163598.chain_generic.runtime-optimized.after.panic-unwind.mir +++ b/tests/mir-opt/issues/issue_163598.chain_generic.runtime-optimized.after.panic-unwind.mir @@ -4,7 +4,7 @@ fn chain_generic(_1: &(T, U), _2: &(T, U)) -> ControlFlow { debug l => _1; debug r => _2; let mut _0: std::ops::ControlFlow; - scope 1 (inlined core::tuple::::__chaining_le) { + scope 1 (inlined core::tuple::::__chaining_less) { let _3: &T; let _4: &T; let mut _5: isize; @@ -24,7 +24,7 @@ fn chain_generic(_1: &(T, U), _2: &(T, U)) -> ControlFlow { _3 = &((*_1).0: T); StorageLive(_4); _4 = &((*_2).0: T); - _0 = ::__chaining_le(move _3, move _4) -> [return: bb1, unwind continue]; + _0 = ::__chaining_less(move _3, move _4) -> [return: bb1, unwind continue]; } bb1: { @@ -45,7 +45,7 @@ fn chain_generic(_1: &(T, U), _2: &(T, U)) -> ControlFlow { _6 = &((*_1).1: U); StorageLive(_7); _7 = &((*_2).1: U); - _0 = ::__chaining_le(move _6, move _7) -> [return: bb4, unwind continue]; + _0 = ::__chaining_less(move _6, move _7) -> [return: bb4, unwind continue]; } bb4: { diff --git a/tests/mir-opt/issues/issue_163598.rs b/tests/mir-opt/issues/issue_163598.rs index 0da8cb05fba48..1257d495d7e73 100644 --- a/tests/mir-opt/issues/issue_163598.rs +++ b/tests/mir-opt/issues/issue_163598.rs @@ -1,6 +1,7 @@ // EMIT_MIR_FOR_EACH_PANIC_STRATEGY //@ skip-filecheck //@ compile-flags: -Z mir-opt-level=3 +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY // Regression test for #163598. If the output has changed significantly (for example, no longer // being mir-inlined), this may be acceptable if the final optimized assembly for `<=` @@ -13,5 +14,5 @@ fn chain_generic( l: &(T, U), r: &(T, U), ) -> core::ops::ControlFlow { - PartialOrd::__chaining_le(l, r) + PartialOrd::__chaining_less(l, r) } diff --git a/tests/mir-opt/pre-codegen/tuple_ord.demo_ge_partial.runtime-optimized.after.mir b/tests/mir-opt/pre-codegen/tuple_ord.demo_ge_partial.runtime-optimized.after.mir index 079a57c916902..b8633ef366af7 100644 --- a/tests/mir-opt/pre-codegen/tuple_ord.demo_ge_partial.runtime-optimized.after.mir +++ b/tests/mir-opt/pre-codegen/tuple_ord.demo_ge_partial.runtime-optimized.after.mir @@ -9,7 +9,7 @@ fn demo_ge_partial(_1: &(f32, f32), _2: &(f32, f32)) -> bool { let mut _7: std::ops::ControlFlow; scope 3 { } - scope 4 (inlined std::cmp::impls::::__chaining_ge) { + scope 4 (inlined std::cmp::impls::::__chaining_greater) { let mut _3: f32; let mut _4: f32; let mut _5: bool; @@ -37,7 +37,7 @@ fn demo_ge_partial(_1: &(f32, f32), _2: &(f32, f32)) -> bool { bb1: { StorageLive(_6); - _6 = Ge(copy _3, copy _4); + _6 = Gt(copy _3, copy _4); _7 = ControlFlow::::Break(move _6); StorageDead(_6); StorageDead(_5); diff --git a/tests/mir-opt/pre-codegen/tuple_ord.demo_le_total.runtime-optimized.after.mir b/tests/mir-opt/pre-codegen/tuple_ord.demo_le_total.runtime-optimized.after.mir index e4dd5225fe029..a16b6bff2df18 100644 --- a/tests/mir-opt/pre-codegen/tuple_ord.demo_le_total.runtime-optimized.after.mir +++ b/tests/mir-opt/pre-codegen/tuple_ord.demo_le_total.runtime-optimized.after.mir @@ -9,7 +9,7 @@ fn demo_le_total(_1: &(u16, i16), _2: &(u16, i16)) -> bool { let mut _7: std::ops::ControlFlow; scope 3 { } - scope 4 (inlined std::cmp::impls::::__chaining_le) { + scope 4 (inlined std::cmp::impls::::__chaining_less) { let mut _3: u16; let mut _4: u16; let mut _5: bool; @@ -37,7 +37,7 @@ fn demo_le_total(_1: &(u16, i16), _2: &(u16, i16)) -> bool { bb1: { StorageLive(_6); - _6 = Le(copy _3, copy _4); + _6 = Lt(copy _3, copy _4); _7 = ControlFlow::::Break(move _6); StorageDead(_6); StorageDead(_5); diff --git a/tests/ui/derives/imperfect-derive-higher-ranked.rs b/tests/ui/derives/imperfect-derive-higher-ranked.rs new file mode 100644 index 0000000000000..e876af6661822 --- /dev/null +++ b/tests/ui/derives/imperfect-derive-higher-ranked.rs @@ -0,0 +1,11 @@ +// regression test for #164376 + +#[derive(Clone)] +struct S<'a, T>(&'a T); + +fn require() where for<'a> S<'a, T>: Clone {} + +fn main() { + require::<&'static mut u8>(); + //~^ ERROR the trait bound `&'static mut u8: Clone` is not satisfied +} diff --git a/tests/ui/derives/imperfect-derive-higher-ranked.stderr b/tests/ui/derives/imperfect-derive-higher-ranked.stderr new file mode 100644 index 0000000000000..fa619de722b45 --- /dev/null +++ b/tests/ui/derives/imperfect-derive-higher-ranked.stderr @@ -0,0 +1,29 @@ +error[E0277]: the trait bound `&'static mut u8: Clone` is not satisfied + --> $DIR/imperfect-derive-higher-ranked.rs:9:15 + | +LL | require::<&'static mut u8>(); + | ^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `&'static mut u8` + | +help: the trait `Clone` is implemented for `u8` + --> $SRC_DIR/core/src/clone.rs:LL:COL + ::: $SRC_DIR/core/src/clone.rs:LL:COL + | + = note: in this macro invocation +note: required for `S<'a, &'static mut u8>` to implement `for<'a> Clone` + --> $DIR/imperfect-derive-higher-ranked.rs:4:8 + | +LL | #[derive(Clone)] + | ----- in this derive macro expansion +LL | struct S<'a, T>(&'a T); + | ^ - type parameter would need to implement `for<'a> Clone` + = help: consider manually implementing `for<'a> Clone` to avoid undesired bounds caused by "imperfect derives" + = note: to learn more, visit +note: required by a bound in `require` + --> $DIR/imperfect-derive-higher-ranked.rs:6:41 + | +LL | fn require() where for<'a> S<'a, T>: Clone {} + | ^^^^^ required by this bound in `require` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/eii/async-eii-declaration-ice-154533.current.stderr b/tests/ui/eii/async-eii-declaration-ice-154533.current.stderr new file mode 100644 index 0000000000000..e64f26e274594 --- /dev/null +++ b/tests/ui/eii/async-eii-declaration-ice-154533.current.stderr @@ -0,0 +1,30 @@ +error: functions in `extern` blocks cannot have `async` qualifier + --> $DIR/async-eii-declaration-ice-154533.rs:14:1 + | +LL | #[eii] + | ------ in this `extern` block +LL | async fn hello_impl() {} + | ^^^^^ help: remove the `async` qualifier + +error[E0806]: function `hello_impl` has a type that is incompatible with the declaration of `#[hello_impl]` + --> $DIR/async-eii-declaration-ice-154533.rs:14:22 + | +LL | async fn hello_impl() {} + | ^ expected `()`, found future + | +note: expected this because of this attribute + --> $DIR/async-eii-declaration-ice-154533.rs:13:1 + | +LL | #[eii] + | ^^^^^^ +note: type in declaration + --> $DIR/async-eii-declaration-ice-154533.rs:14:22 + | +LL | async fn hello_impl() {} + | ^ + = note: expected signature `fn() -> ()` + found signature `fn() -> impl Future` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0806`. diff --git a/tests/ui/eii/async-eii-declaration-ice-154533.next.stderr b/tests/ui/eii/async-eii-declaration-ice-154533.next.stderr new file mode 100644 index 0000000000000..e64f26e274594 --- /dev/null +++ b/tests/ui/eii/async-eii-declaration-ice-154533.next.stderr @@ -0,0 +1,30 @@ +error: functions in `extern` blocks cannot have `async` qualifier + --> $DIR/async-eii-declaration-ice-154533.rs:14:1 + | +LL | #[eii] + | ------ in this `extern` block +LL | async fn hello_impl() {} + | ^^^^^ help: remove the `async` qualifier + +error[E0806]: function `hello_impl` has a type that is incompatible with the declaration of `#[hello_impl]` + --> $DIR/async-eii-declaration-ice-154533.rs:14:22 + | +LL | async fn hello_impl() {} + | ^ expected `()`, found future + | +note: expected this because of this attribute + --> $DIR/async-eii-declaration-ice-154533.rs:13:1 + | +LL | #[eii] + | ^^^^^^ +note: type in declaration + --> $DIR/async-eii-declaration-ice-154533.rs:14:22 + | +LL | async fn hello_impl() {} + | ^ + = note: expected signature `fn() -> ()` + found signature `fn() -> impl Future` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0806`. diff --git a/tests/ui/eii/async-eii-declaration-ice-154533.rs b/tests/ui/eii/async-eii-declaration-ice-154533.rs new file mode 100644 index 0000000000000..c69caac837795 --- /dev/null +++ b/tests/ui/eii/async-eii-declaration-ice-154533.rs @@ -0,0 +1,18 @@ +//! Regression test for . +//! +//! An `async fn` declared with `#[eii]` used to ICE with +//! "trait bounds on EII not yet supported" under the new solver. + +//@ edition: 2021 +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver + +#![feature(extern_item_impls)] + +#[eii] +async fn hello_impl() {} +//~^ ERROR functions in `extern` blocks cannot have `async` qualifier +//~| ERROR function `hello_impl` has a type that is incompatible with the declaration of `#[hello_impl]` + +fn main() {} diff --git a/tests/ui/offload/auxiliary/offload_strategies.rs b/tests/ui/offload/auxiliary/offload_strategies.rs new file mode 100644 index 0000000000000..0669d92b604d1 --- /dev/null +++ b/tests/ui/offload/auxiliary/offload_strategies.rs @@ -0,0 +1,58 @@ +//@ edition: 2024 + +#![feature(gpu_offload)] + +use core::offload::{LaunchError, PartitioningStrategy}; +use core::ptr::NonNull; + +#[derive(Debug, Clone, Copy)] +pub struct Dummy; + +unsafe impl PartitioningStrategy for Dummy { + type View<'a, T: 'a> = &'a T; + type ViewMut<'a, T: 'a> = &'a mut T; + + fn index() -> usize { + 0 + } + + unsafe fn get<'a, T>(_ptr: NonNull, _len: usize) -> Option> { + None + } + + unsafe fn get_mut<'a, T>(_ptr: NonNull, _len: usize) -> Option> { + None + } + + fn check_launch(_len: usize, _grid: [u32; 3], _block: [u32; 3]) -> Result<(), LaunchError> { + Ok(()) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Linear1D; + +unsafe impl PartitioningStrategy for Linear1D { + type View<'a, T: 'a> = &'a T; + type ViewMut<'a, T: 'a> = &'a mut T; + + fn index() -> usize { + 0 + } + + unsafe fn get<'a, T>(_ptr: NonNull, _len: usize) -> Option> { + None + } + + unsafe fn get_mut<'a, T>(_ptr: NonNull, _len: usize) -> Option> { + None + } + + fn check_launch(_len: usize, grid: [u32; 3], block: [u32; 3]) -> Result<(), LaunchError> { + if grid[1] == 1 && grid[2] == 1 && block[1] == 1 && block[2] == 1 { + Ok(()) + } else { + Err(LaunchError::new("")) + } + } +} diff --git a/tests/ui/offload/non_tuple_args.rs b/tests/ui/offload/non_tuple_args.rs index 2c63b6d6abe81..44da33a808cb7 100644 --- a/tests/ui/offload/non_tuple_args.rs +++ b/tests/ui/offload/non_tuple_args.rs @@ -3,9 +3,15 @@ #![feature(gpu_offload)] fn main() { - // args_ty is not a tuple + // `args` is not a tuple literal. core::offload::offload! { kernel = kernel_0, args = 42 } - //~^ ERROR `{integer}` is not a tuple + //~^ ERROR `args` must be a tuple literal + + // FIXME(offload): Binding a tuple to a variable would currently bypass the + // per-argument launch checks, so the macro rejects it for now. + let args = (); + core::offload::offload! { kernel = kernel_0, args = args } + //~^ ERROR `args` must be a tuple literal } fn kernel_0() {} diff --git a/tests/ui/offload/non_tuple_args.stderr b/tests/ui/offload/non_tuple_args.stderr index 26687d4b4bbb7..d930db4646490 100644 --- a/tests/ui/offload/non_tuple_args.stderr +++ b/tests/ui/offload/non_tuple_args.stderr @@ -1,13 +1,18 @@ -error[E0277]: `{integer}` is not a tuple +error: `args` must be a tuple literal --> $DIR/non_tuple_args.rs:7:5 | LL | core::offload::offload! { kernel = kernel_0, args = 42 } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::marker::Tuple` is not implemented for `{integer}` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -note: required by a bound in `offload` - --> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL = note: this error originates in the macro `$crate::offload` which comes from the expansion of the macro `core::offload::offload` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 1 previous error +error: `args` must be a tuple literal + --> $DIR/non_tuple_args.rs:13:5 + | +LL | core::offload::offload! { kernel = kernel_0, args = args } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `$crate::offload` which comes from the expansion of the macro `core::offload::offload` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 2 previous errors -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/offload/region_borrow.rs b/tests/ui/offload/region_borrow.rs new file mode 100644 index 0000000000000..082d59b42cc99 --- /dev/null +++ b/tests/ui/offload/region_borrow.rs @@ -0,0 +1,21 @@ +//@ edition: 2024 +//@ aux-crate: offload_strategies=offload_strategies.rs + +// This test checks that the borrow checker errors when writing into the data a +// `Region` was created from while the `Region` is still alive. + +#![feature(gpu_offload)] +#![allow(unused_assignments)] + +use core::offload::Region; +use offload_strategies::Dummy; + +fn main() { + let mut x = [0.0f32; 4]; + let region = Region::::new(&mut x[..]); + + x[0] = 1.0; + //~^ ERROR cannot assign to `x[_]` because it is borrowed + + let _view = region.get(); +} diff --git a/tests/ui/offload/region_borrow.stderr b/tests/ui/offload/region_borrow.stderr new file mode 100644 index 0000000000000..6a90cec15ebe5 --- /dev/null +++ b/tests/ui/offload/region_borrow.stderr @@ -0,0 +1,15 @@ +error[E0506]: cannot assign to `x[_]` because it is borrowed + --> $DIR/region_borrow.rs:17:5 + | +LL | let region = Region::::new(&mut x[..]); + | - `x[_]` is borrowed here +LL | +LL | x[0] = 1.0; + | ^^^^^^^^^^ `x[_]` is assigned to here but it was already borrowed +... +LL | let _view = region.get(); + | ------ borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0506`. diff --git a/tests/ui/offload/region_by_ref.rs b/tests/ui/offload/region_by_ref.rs new file mode 100644 index 0000000000000..37ac1aa9c1b0a --- /dev/null +++ b/tests/ui/offload/region_by_ref.rs @@ -0,0 +1,75 @@ +//@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat +//@ edition: 2024 +//@ aux-crate: offload_strategies=offload_strategies.rs + +// This test ensures an error is emitted when a `Region` is nested inside another +// type instead of being passed by value. + +#![feature(core_intrinsics)] +#![feature(gpu_offload)] + +use core::offload::Region; +use offload_strategies::Dummy; + +fn kernel_shared(_region: &Region<'_, f32, Dummy>) {} + +fn kernel_mut(_region: &mut Region<'_, f32, Dummy>) {} + +fn kernel_nested(_arg: (u32, Region<'_, f32, Dummy>)) {} + +struct Wrapper<'a> { + region: Region<'a, f32, Dummy>, + scalar: f32, +} + +fn kernel_wrapper(_arg: Wrapper<'_>) {} + +fn main() { + let mut x = [0.0f32; 4]; + let region = Region::::new(&mut x[..]); + core::intrinsics::offload::<_, _, ()>( + //~^ ERROR offload kernel argument 0 contains a `Region` nested inside another type + kernel_shared, + [1, 1, 1], + [1, 1, 1], + 0, + -1, + (®ion,), + ); + + let mut y = [0.0f32; 4]; + let mut region = Region::::new(&mut y[..]); + core::intrinsics::offload::<_, _, ()>( + //~^ ERROR offload kernel argument 0 contains a `Region` nested inside another type + kernel_mut, + [1, 1, 1], + [1, 1, 1], + 0, + -1, + (&mut region,), + ); + + let mut z = [0.0f32; 4]; + let region = Region::::new(&mut z[..]); + core::intrinsics::offload::<_, _, ()>( + //~^ ERROR offload kernel argument 0 contains a `Region` nested inside another type + kernel_nested, + [1, 1, 1], + [1, 1, 1], + 0, + -1, + ((0u32, region),), + ); + + let mut w = [0.0f32; 4]; + let region = Region::::new(&mut w[..]); + core::intrinsics::offload::<_, _, ()>( + //~^ ERROR offload kernel argument 0 contains a `Region` nested inside another type + kernel_wrapper, + [1, 1, 1], + [1, 1, 1], + 0, + -1, + (Wrapper { region, scalar: 0.0 },), + ); +} diff --git a/tests/ui/offload/region_by_ref.stderr b/tests/ui/offload/region_by_ref.stderr new file mode 100644 index 0000000000000..b3aed2909a480 --- /dev/null +++ b/tests/ui/offload/region_by_ref.stderr @@ -0,0 +1,26 @@ +error: offload kernel argument 0 contains a `Region` nested inside another type. Pass the `Region` by value so it can be mapped like a slice + --> $DIR/region_by_ref.rs:30:5 + | +LL | core::intrinsics::offload::<_, _, ()>( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: offload kernel argument 0 contains a `Region` nested inside another type. Pass the `Region` by value so it can be mapped like a slice + --> $DIR/region_by_ref.rs:42:5 + | +LL | core::intrinsics::offload::<_, _, ()>( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: offload kernel argument 0 contains a `Region` nested inside another type. Pass the `Region` by value so it can be mapped like a slice + --> $DIR/region_by_ref.rs:54:5 + | +LL | core::intrinsics::offload::<_, _, ()>( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: offload kernel argument 0 contains a `Region` nested inside another type. Pass the `Region` by value so it can be mapped like a slice + --> $DIR/region_by_ref.rs:66:5 + | +LL | core::intrinsics::offload::<_, _, ()>( + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/offload/region_launch_bounds.rs b/tests/ui/offload/region_launch_bounds.rs new file mode 100644 index 0000000000000..8ce655b676320 --- /dev/null +++ b/tests/ui/offload/region_launch_bounds.rs @@ -0,0 +1,18 @@ +//@ run-fail +//@ edition: 2024 +//@ aux-crate: offload_strategies=offload_strategies.rs +//@ error-pattern: offload launch is not supported by the region's partitioning strategy + +#![feature(gpu_offload)] + +use core::offload::{Region, RegionLaunchCheck}; +use offload_strategies::Linear1D; + +fn main() { + let mut x = [0.0f32; 4]; + let region = Region::::new(&mut x[..]); + + // `Linear1D` only supports single-dimensional launches, so `[4, 2, 1]` is + // rejected. + region.__offload_check_launch([4, 2, 1], [1, 1, 1]); +} diff --git a/tests/ui/offload/region_not_copy.rs b/tests/ui/offload/region_not_copy.rs new file mode 100644 index 0000000000000..4cdda7fc159a0 --- /dev/null +++ b/tests/ui/offload/region_not_copy.rs @@ -0,0 +1,18 @@ +//@ edition: 2024 +//@ aux-crate: offload_strategies=offload_strategies.rs + +// This tests checks `Region` doesn't implement Copy. + +#![feature(gpu_offload)] + +use core::offload::Region; +use offload_strategies::Dummy; + +fn main() { + let mut x = [0.0f32; 4]; + let mut a = Region::::new(&mut x[..]); + let mut b = a; + if let (Some(_), Some(_)) = (a.get_mut(), b.get_mut()) { + //~^ ERROR borrow of moved value: `a` + } +} diff --git a/tests/ui/offload/region_not_copy.stderr b/tests/ui/offload/region_not_copy.stderr new file mode 100644 index 0000000000000..03d75825d49e6 --- /dev/null +++ b/tests/ui/offload/region_not_copy.stderr @@ -0,0 +1,13 @@ +error[E0382]: borrow of moved value: `a` + --> $DIR/region_not_copy.rs:15:34 + | +LL | let mut a = Region::::new(&mut x[..]); + | ----- move occurs because `a` has type `Region<'_, f32, Dummy>`, which does not implement the `Copy` trait +LL | let mut b = a; + | - value moved here +LL | if let (Some(_), Some(_)) = (a.get_mut(), b.get_mut()) { + | ^ value borrowed here after move + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`.