Repository navigation
[SYCL] Consistent is_device_copyable for sycl::accessor - #23250
inteltdelisle wants to merge 22 commits into
Conversation
Previously, is_device_copyable_v evaluated to different values for device and host code. Added explicit instantiation to consistently return false. Also added tests for other basic sycl types. Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
koparasy
left a comment
There was a problem hiding this comment.
A high level comment. What makes accessors change their behavior depending on host|device compilation?
By default |
|
@inteltdelisle thanks for the fix. Could you please describe a bit of a background for this change - was some specific test failing? Thanks! |
|
This fix was of result of a bug report submitted by @koparasy, who noted that the following code was inconsistent between device and host compilation: This code compiled for the device but not the host. The additional tests are simply there to verify the behavior of other basic sycl types. Worth noting that I did not add a test to verify the behavior of |
| template <typename DataT, int Dimensions, access_mode AccessMode> | ||
| struct is_device_copyable<host_accessor<DataT, Dimensions, AccessMode>> | ||
| : std::false_type {}; | ||
|
|
There was a problem hiding this comment.
I wonder if we should change the implementation of the accessor instead of specializing is_device_copyable. For example, adding an empty user-provided destructor will cause the types to be non-trivially copyable.
This seems better than specializing the trait because it also makes is_trivially_copyable consistent between host and device.
There was a problem hiding this comment.
I'll confirm locally whether that works. I was hesitant do change the accessor type itself, since that will have a less targeted impact. On the other hand the spec does state "Any type that is trivially copyable (as defined by the C++ core language) is implicitly device copyable." so that solution match the spec more closely.
There was a problem hiding this comment.
@gmlueck Adding the dtors instead works locally. I can push that change if it's a preferred solution.
There was a problem hiding this comment.
Spec-wise, I think it's better. Maybe @intel/llvm-reviewers-runtime can weigh in from the implementation side.
There was a problem hiding this comment.
@inteltdelisle Changing a type's trivial-copyability is an ABI breaking change. I am on board with @gmlueck's suggestion, but I think we should add the user-provided destructor under PREVIEW_BREAKING_CHANGES flag.
There was a problem hiding this comment.
@gmlueck Does SYCL require std::is_trivially_copyable_v<accessor> itself to evaluate consistently with sycl::is_device_copyable_v<accessor>? If not, wouldn't an explicit specialization of is_device_copyable be preferable to deliberately making the destructor non-trivial, since the latter changes additional C++ type properties unrelated to device copyability?
I am also wondering whether we actually want to break code that may rely on std::is_trivially_copyable_v<accessor>, even if the current behavior is somewhat obscure and differs between host and device compilation.
There was a problem hiding this comment.
In general, I think it's bad for traits to evaluate differently in the host vs. the device compilation passes because this can lead to constexpr expressions having different values in the two passes. I think this could lead to quite confusing behavior (e.g. SFINAE constraints evaluating different in the two passes). Therefore, I think std::is_trivially_copyable_v<accessor> should evaluate the same in both passes.
TBH, I think the SYCL spec should be changed to guarantee that all standard traits and all SYCL traits evaluate the same in all compiler passes. This is a battle for another day, though.
I agree that changing the trait in the device compiler could potentially break an application (thought I think it's pretty unlikely). Therefore, I agree that it's better to make this change at the next major release.
There was a problem hiding this comment.
@gmlueck I'm not super familiar with the release procedure yet, what does making this change at the next major release entail precisely? Do we merge this change in behind the PREVIEW_BREAKING_CHANGES?
There was a problem hiding this comment.
Yes, that's right. You would merge the change as it is now in this PR behind the PREVIEW_BREAKING_CHANGES macro. At the next major release, we search for all these macros and enable the code they protect by default. Merging the changes now under the macro provides two benefits:
- It allows user to see what potential breaking changes might be coming in the next major release by compiling with
-fpreview-breaking-changes. - It lets us address issues like this early, rather than waiting until right before the next major release.
Accessors are no longer trivially copyable to match the sycl spec more closely. Change is behind PREVIEW_BREAKING_CHANGES. Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
Previous definition of CheckDeviceCopyable was relying on is_device_copyable as a shortcut for checking kernel parameter legality. Renamed to CheckKernelParametersAreLegal and re-wrote implementation to match the sycl 2020 spec more closely. Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
Since sycl::accessor is not trivially copyable, special types require the normal destructor machinery. Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
There was a problem hiding this comment.
🟡 Changes recommended
Kernel legality predicates currently admit invalid host accessors, host-target accessors, and arbitrary non-device-copyable arguments.
4 open findings
What changed in this PR
Makes accessor device-copyability consistent while preserving legal kernel captures.
Changes:
- Adds non-trivial accessor destructors and explicit copy/move operations.
- Reworks kernel-parameter legality checks.
- Updates compiler lowering, ESIMD verification, and type-trait tests.
| File | Description |
|---|---|
sycl/test/basic_tests/implicit_device_copyable_types.cpp |
Adds device-copyability assertions. |
sycl/include/sycl/handler.hpp |
Broadens set_arg eligibility. |
sycl/include/sycl/detail/kernel_launch_helper.hpp |
Invokes the renamed legality check. |
sycl/include/sycl/detail/is_device_copyable.hpp |
Implements recursive kernel-parameter legality checks. |
sycl/include/sycl/accessor.hpp |
Adds accessor destructors, special members, and legality traits. |
llvm/lib/SYCLLowerIR/ESIMD/ESIMDVerifier.cpp |
Allows local-accessor destructors in ESIMD. |
clang/lib/Sema/SemaSYCL.cpp |
Finalizes generated variables with destructors. |
clang/lib/CodeGen/CGDeclCXX.cpp |
Skips work-group-scope destructor registration. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
| template <typename DataT, int Dimensions, access::mode AccessMode, | ||
| access::target AccessTarget, access::placeholder IsPlaceholder, | ||
| typename PropertyListT> | ||
| struct is_sycl_accessor<accessor<DataT, Dimensions, AccessMode, AccessTarget, | ||
| IsPlaceholder, PropertyListT>> | ||
| : std::true_type {}; |
| template <typename DataT, int Dimensions, access_mode AccessMode> | ||
| struct is_sycl_accessor<host_accessor<DataT, Dimensions, AccessMode>> | ||
| : std::true_type {}; |
| || (!std::is_abstract_v<remove_cv_ref_t<T>> && | ||
| !std::is_polymorphic_v<remove_cv_ref_t<T>> && | ||
| std::is_destructible_v<remove_cv_ref_t<T>> && | ||
| std::is_copy_constructible_v<remove_cv_ref_t<T>>); |
| #ifdef __INTEL_PREVIEW_BREAKING_CHANGES | ||
| static_assert(!sycl::is_device_copyable_v< | ||
| sycl::accessor<int, 1, sycl::access_mode::read_write>>); | ||
| static_assert(!sycl::is_device_copyable_v<sycl::local_accessor<int, 1>>); | ||
| static_assert(!sycl::is_device_copyable_v<sycl::host_accessor<int, 1>>); | ||
| #endif // __INTEL_PREVIEW_BREAKING_CHANGES |
elizabethandrews
left a comment
There was a problem hiding this comment.
Please add a FE test.
Updated is_legal_kernel_parameter to no longer accept any types composed of legal types. Changes to is device copyable extended to accessors. Added tests to cover new changes. Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
|
@gmlueck @koparasy Now that the tests are passing, I believe this is the set of changes that are needed to get Any thoughts on this approach? |
I will come back monday about this. Initially I imagined this will be a trivial direction. I got that wrong. I need to reason about this PR a little deeper. |
koparasy
left a comment
There was a problem hiding this comment.
With is_legal_kernel_parameter_v in ShouldEnableSetArg, the generic set_arg(int, T&&) is now enabled on host for every type is_sycl_accessor matches. Accessors with the default property list still reach the accessor-specific setArgHelper, but an accessor with a non-empty accessor_property_list, and unsampled_image_accessor / sampled_image_accessor, have no matching overload. IIUC they fall into the generic setArgHelper(T&&), which copies the host object bytes as kind_std_layout. Previously these failed to compile; now they compile and pass a wrong argument. Could you verify how those are resolved with some tests, and act accordingly?
Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
I refined the condition so that it bahaviour is unchanged and added tests to confirm this. I also added a static assert to make sure the std layour is not used incorrectly. |
|
@inteltdelisle thanks. This is way more complex, invasive than what I had initially imagined. Saying this does not mean you are doing anything wrong. Friday I will try to spend some time to think if we can simplify this. But overall this looks correct to me now. |
|
@inteltdelisle a type is trivially copyable only if all of its copy/move operations are trivial, assignment included. An // in class
accessor(const accessor &) = default;
accessor(accessor &&) = default;
accessor &operator=(const accessor &);
accessor &operator=(accessor &&);
// out of line
template <...> accessor<...> &accessor<...>::operator=(const accessor &) noexcept = default;
template <...> accessor<...> &accessor<...>::operator=(accessor &&) noexcept = default;It should mark the accessors non copyable on both sides. By doing this, you no longer need the compiler changes (I guess). |
Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
I pushed a commit to switch to this approach. One side effect to note here is that |
| /// static storage duration. | ||
| static void EmitDeclDestroy(CodeGenFunction &CGF, const VarDecl &D, | ||
| ConstantAddress Addr) { | ||
|
|
Do you mean "will NOT return .." or "will now return". If it is the later, we probably don't want that. Your previous solution in that case would be better. |
"Will now return". Although it's more accurate to say, will revert to destructors only being trivially destructible on the device, which is the current status before this PR. |
This reverts commit 9da10c4.
…//github.com/inteltdelisle/llvm into tdelisle/runtime/accessor_is_device_copyable
Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
Signed-off-by: Delisle, Thierry <thierry.delisle@intel.com>
elizabethandrews
left a comment
There was a problem hiding this comment.
@tahonermann can you also take a look please. The code changes look ok to me but I am not sure if the functional change has consequences I am not thinking off.
| // work-group local memory. There is no program exit on the device to run | ||
| // their destructor at, and SYCL 2020 requires the destructors of device | ||
| // copyable types to have no effect on the device, so don't register one. | ||
| if (CGF.getLangOpts().SYCLIsDevice) { |
There was a problem hiding this comment.
The comment does not fully cover the actual code changes. All locals are affected IIUC. The SYCL 2020 rule refers to only kernel captures but this change affects a variable declared in the kernel bocy as well right?.
In any case is this code even hit after after #23702 ?
There was a problem hiding this comment.
I think you can just update the comment to describe the status as well. Maybe add a FIXME so we can keep track of its removal.
| // Special type with a non-trivial destructor, like the device accessors. The | ||
| // destructor of the object created in the kernel to pass to the free function | ||
| // must be instantiated and defined, not just declared. | ||
| template <typename DataT> |
There was a problem hiding this comment.
I'm not sure if we should update the mock headers to include the destructor under some macro for now? @tahonermann can you weigh in?
| @@ -0,0 +1,29 @@ | |||
| // RUN: %clang_cc1 -fsycl-is-device -triple spir64-unknown-unknown -disable-llvm-passes -Wno-sycl-work-group-dtor -emit-llvm %s -o - | FileCheck %s | |||
There was a problem hiding this comment.
I see you passed -Wno-sycl-work-group-dtor. This PR adds a destructor to accessors, so wouldn't that mean that this warning would fire for every parallel_for_work_group kernel capturing an accessor ? Is this expected behavior?
| q.submit([&](handler &h) { | ||
| NonTrivial Captured; | ||
| h.parallel_for_work_group<class kernel>( | ||
| range<1>{1}, range<1>{1}, [=](group<1> G) { (void)Captured; }); |
There was a problem hiding this comment.
Could you also add a local declared inside the parallel_for_work_group body with a non-trivial destructor (not captured).
| // CHECK-NEXT: %0 = load ptr addrspace(4), ptr addrspace(4) %__arg__obj.addr.ascast, align 8 | ||
| // CHECK-NEXT: call spir_func void @{{.*}}annotated_ptr{{.*}}__init{{.*}} | ||
|
|
||
| // Special type with a non-trivial destructor, like the device accessors. The |
There was a problem hiding this comment.
Nit: Comment says "the device accessors". Test is for __SYCL_TYPE(work_group_memory)



sycl::is_device_copyable_v (and std::is_trivially_copyable_v) evaluated differently in host and device compilation, because the accessor special members are implemented differently under SYCL_DEVICE_ONLY.
For example, this compiled for the device but not for the host:
Traits that differ between compilation passes can make constexpr/SFINAE results diverge between host and device.
Fix (behind -fpreview-breaking-changes, since it changes trivial copyability and is ABI-breaking):
Accessor types (accessor, local_accessor/local_accessor_base, host_accessor, and the image accessors) get a user-provided empty destructor, so they are consistently not trivially copyable and, per SYCL 2020, not implicitly device copyable. The copy/move constructors and assignment operators are explicitly defaulted to avoid issues with implicit functions.
Kernel parameter legality is now separate from device copyability (Not ABI-breaking):
New sycl::detail::is_legal_kernel_parameter trait that follows the SYCL 2020 "legal kernel parameter" rules: device copyable types, device-target accessors, arrays of legal types, and structs whose fields and bases are all legal and that contain a special SYCL type. Host-target accessors are rejected.
CheckDeviceCopyableis replaced bylegal_kernel_parameters::validate_functor, which checks kernel captures against this trait.handler::set_arghas also been updated for consistency, reusing the trait were possible.The integration header now specializes
detail::is_legal_kernel_parameterinstead ofis_device_copyablefor free-function structs that contain special types.Compiler changes needed because accessors now have a non-trivial destructor:
SemaSYCL: finalize the destructor of special-object clones (FinalizeVarWithDestructor). Without this change, no definition is created for the dtor, since this is an inlined template, and calls to the destructor may fail to link.
CGDeclCXX: don't register a destructor for SYCL work-group-scope variables (e.g. the parallel_for_work_group kernel object). SYCL requires these destructors to have no effect and platforms are not required to declared/implement
__cxx_atexit.ESIMDVerifier: allow the local_accessor and local_accessor_base destructors.
Other: the accessor property-list conversion constructor is now constrained with enable_if instead of a static_assert, so CTAD and same-type copies pick the copy constructor.
Known limitation:
A struct that contains only legal members but has an illegal user-defined copy constructor or destructor is not detected.
Follow-up on the work-group scope attribute handling: #23461.
Tests:
New/updated tests for accessor special members, implicitly device copyable SYCL types, is_device_copyable (positive and negative), and set_arg. Each has RUN lines with and without -fpreview-breaking-changes.