Properly implement the gpu-kernel ABI for amdgpu - #162177
Conversation
|
cc @bjorn3 This PR changes rustc_public cc @oli-obk, @celinval, @ouz-a, @makai410
|
|
rustbot has assigned @petrochenkov. Use Why was this reviewer chosen?The reviewer was selected based on:
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@rustbot reroll |
|
I think this now sends our slices through the aggregate path, not the (Scalar)Pair anymore, can you add a test to confirm that? It would break Rust Offload, but for now we can make a PR to overwrite this change for functions that are a |
|
Yes, scalar pair (and therefore all fat pointers, including slices) are handled as aggregates with this and passed as byref ptr. Are you sure that this breaks Rust Offload? I will add a test that passes a slice. |
371ff93 to
b5b814c
Compare
This comment has been minimized.
This comment has been minimized.
b5b814c to
6a47e13
Compare
|
Pre-committed the tests, fixed the now perma-link in the commit message and added a test taking a slice as argument. Total diff (just adding the slice test): https://github.com/rust-lang/rust/compare/371ff93ae5d9f8cbbc07c15451218ecbeab39a9c..b5b814cbbf6c93535e33ce4232f698570dec25ce |
6a47e13 to
ddd9a73
Compare
|
Sorry, one more force-push to fix the tests that failed in CI (amended in the first commit). Just |
This comment has been minimized.
This comment has been minimized.
ddd9a73 to
a90629b
Compare
|
And one more to fix the aarch64-only variant of the test. Diff: https://github.com/rust-lang/rust/compare/ddd9a73295fbd2136470a8d0938a7fb565b6e0c5..a90629b7e87febc01a7e6bd4c56bba2232472c05 |
|
r? @ZuseZ4 |
|
Is byref ptr different from ptr in any way other than byref ptr having more UB? Also please make sure to add a copy of byref ptr parameters. Rustc assumes that it can write through ptr arguments, but byref ptr makes that UB. |
Yes, byref ptr (and byval ptr) are both different from ptr. On the calling side, a ptr would be passed in as a pointer. Arguably, there’s not much difference in the called gpu-kernel itself if you look just at the IR going into LLVM. The argument is handled like a pointer. Later in the backend part that lowers arguments to reads from the argument memory region, byref ptr is handled differently than ptr, either reading a pointer from the argument memory, or reading the value directly. For gpu-kernel, this is observable in “user code”, i.e. outside the compiler, because the user assembles a memory region to pass as arguments when launching a gpu-kernel on the GPU through some API (cuda/hip/hsa/sycl). So, if the function signature in Rust is
I think the change in |
That would be byval, right? On x86_64 there is absolutely no difference in emitted assembly between
👍 |
x86 doesn’t really have a use for byref, so yeah, there it is probably just handled like byval. The byval is defined as the value being placed on the stack, which would be |
|
Does the amdgpu kernel ABI pass pointers into the constant memory region as arguments for |
It’s… complicated, as there are no calls to gpu-kernel functions (at least no call in the LLVM IR The kernel is compiled in a way to read from that memory. There is a pass in the LLVM backend that takes the IR arguments on an amdgpu_kernel function and replaces them with
Not quite sure I understand the question completely, but I think “the latter” is the correct answer. LLVM/clang does use // CHECK: define amdgpu_kernel void @kernel_struct_arg(ptr addrspace(4) noalias nofree noundef readnone byref([12 x i8]) align 4 captures(none) dereferenceable(12) {{%.+}})
#[no_mangle]
pub extern "gpu-kernel" fn kernel_struct_arg(_: StructArg) {}If it wasn’t clear, Rust should have always used byref to pass struct arguments to gpu-kernels on amdgpu. It just was broken so far. |
The exact way the user is supposed to assemble this memory region and launch the kernel is part of the calling convention.
For byref arguments does it also load a pointer into the constant memory region from
That is byref, not byval. byval normally refers to a fixed offset from the stack pointer, but my suggestion was to use an address space modifier to make it refer to a fixed offset from the constant memory region. You could kind of treat the constant memory region as a secondary stack to pass argument, right? Except that you can only pass arguments to I do understand that it isn't trivial to change in LLVM, but if it would have made sense to model it as byval with explicit address space in LLVM, perhaps we shouldn't be calling the rustc side construct ByRef, but something like InGpuConstantMemory or AmdgpuConstantMemory? |
a90629b to
c0e1ed5
Compare
|
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
It expects byref arguments to be stored at a fixed offset from the start of the constant memory region.
Yes, apart from it being not a stack ;) I’m open to rename ByRef to something else. Maybe AmdgpuKernelArg? I just pushed the change to use an enum (only the first commit changed). Mostly a mechanical change, though I added a |
Sounds fine to me. |
Both will be used by the amdgpu target to implement the `gpu-kernel` ABI. `address_space` specifies the address space of an indirect argument. `AmdgpuKernelArg` translates to LLVM’s byref, which is similar to on_stack/byval, however, there is no extra copy made, the pointer may not point to the stack but can point to some other address space, and the passed argument should not be modified. byval and byref are mutually exclusive, so change on_stack to an enum with the new states, Pointer (none), OnStack and AmdgpuKernelArg.
Add support to pass structs, arrays and vectors to amdgpu kernels. Scalars and vectors are taken by value, aggregates are passed by byref pointers. Structs containing a single scalar/vector are handled like a scalar. Judging from clang tests, nvptx seems to do somewhat the same, just using byval instead of byref: https://github.com/llvm/llvm-project/blob/3a8affeef4da19d39191aac316e189eca3214a8c/clang/test/CodeGenCUDA/kernel-args.cu I tested a couple of the lit test signatures on real hardware and it seems to work fine. Given the relatively simple implementation, I hope this amount of testing is enough (the C calling convention seems like a worse fit for Rust’s current ABI code, it’s still giving me headaches).
c0e1ed5 to
552358c
Compare
|
Renamed the enum variant to AmdgpuKernelArg (diff). |
| match size { | ||
| 1 => Some(Uniform::new(Reg::i8(), field.layout.size)), | ||
| 2 => Some(Uniform::new(Reg::i16(), field.layout.size)), | ||
| _ => Some(Uniform::new(Reg::i32(), field.layout.size)), |
There was a problem hiding this comment.
Is a plain i64 also supposed to be represented as [2 x i32]?
| match field.backend_repr { | ||
| BackendRepr::Scalar(_) | ||
| | BackendRepr::SimdVector { .. } | ||
| | BackendRepr::SimdScalableVector { .. } => { |
There was a problem hiding this comment.
You can probably bug!() on SimdScalableVector.
| continue; | ||
| } | ||
| classify_arg(cx, arg); | ||
| if fn_abi.conv == CanonAbi::GpuKernel { |
There was a problem hiding this comment.
You are no longer respecting pass_indirectly_in_non_rustic_abis for extern "C". Also is this target supposed to be able to link against existing C code?
There was a problem hiding this comment.
Also is this target supposed to be able to link against existing C code?
No, currently not.
I started implementing towards that but that is a much larger problem than the gpu-kernel ABI and will likely require larger additions to rustc’s ABI handling (concretely, passing a repr(C) struct by value needs to be an LLVM IR struct that is passed by value and I think we need the correct types there, so using the current cast does not work).
| meta_attrs: Option<ArgAttributes>, | ||
| address_space: Option<AddressSpace>, | ||
| mode: IndirectMode, | ||
| }, |
There was a problem hiding this comment.
Maybe unsized args could be split into a new PassMode::IndirectUnsized variant. Best left for a follow up PR. I can do it.
View all comments
Add support to pass structs, arrays and vectors to amdgpu kernels.
Scalars and vectors are taken by value, aggregates are passed by byref
pointers. Structs containing a single scalar/vector are handled like
a scalar.
Judging from clang tests, nvptx seems to do somewhat the same, just
using byval instead of byref: https://github.com/llvm/llvm-project/blob/e4e18dba3d77f4a3eea58bcc9ccae5a5498ede7c/clang/test/CodeGenCUDA/kernel-args.cu
I tested a couple of the lit test signatures on real hardware and it
seems to work fine. Given the relatively simple implementation, I hope
this amount of testing is enough (the C calling convention seems like
a worse fit for Rust’s current ABI code, it’s still giving me headaches).
This adds two members to
PassMode::Indirect.address_spacespecifies the address space of an on_stack/byval orby_ref pointer argument.
by_reftranslates to LLVM’s byref, which is similar to on_stack/byval,however, there is no extra copy made, the pointer may not point to the
stack but can point to some other address space, and the passed argument
should not be modified.
Both are used by the amdgpu target to implement the
gpu-kernelABI.
Tracking issue for the
gpu-kernelABI: #135467Tracking issue for the amdgpu target: #135024
If I read it correctly, I can’t notify the gpu-target group, so cc @kjetilkjeka, @kulst, @ZuseZ4, @workingjubilee