-
Notifications
You must be signed in to change notification settings - Fork 769
[SYCL][FPGA] Initial implementation of pipes feature #292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
SYCL INTEL spatial pipes | ||
======================== | ||
|
||
Introduction | ||
============ | ||
|
||
SPIR-V is first class target in which SYCL pipes should be representable, and | ||
MrSidims marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pipes are already exposed within SPIR-V. For this implementation API functions | ||
call for SPIR-V friendly mangled functions instead of OpenCL built-ins. | ||
This document describes how SYCL pipes are being lowered to SPIR-V. | ||
|
||
OpenCL 2.2 program pipe representation in SPIR-V | ||
================================================ | ||
|
||
The SPIR-V program pipe representation is used to be an underlying | ||
representation of intra-kernel and inter-kernel static pipe connectivity. | ||
The SPIR-V pipe representation exists in a series of pieces: | ||
|
||
- OpTypePipeStorage: Type representing memory allocated for storage of data | ||
within a pipe. Used for OpenCL 2.2 program pipes (program-scope pipes) that | ||
the host program is not aware of, but that enables connectivity between | ||
kernels. | ||
|
||
- OpConstantPipeStorage: Instruction that creates an OpTypePipeStorage object. | ||
Requires packet size (number of bytes) and capacity (number of packets) to be | ||
defined. | ||
|
||
- OpTypePipe: A pipe object that can act as a read/write endpoint of some pipe | ||
storage, either allocated by the host and passed as a kernel argument, or | ||
allocated at "program scope" through a pipe storage object. | ||
|
||
- OpCreatePipeFromPipeStorage: Creates a pipe object (that can be read/written) | ||
from an OpTypePipeStorage instance. | ||
|
||
- OpReadPipe / OpWritePipe: Read packet from or write packet to a pipe object. | ||
|
||
Lowering of kernel to kernel pipes to SPIR-V (non-blocking) | ||
=========================================================== | ||
|
||
This connectivity is achieved through OpTypePipeStorage which allows a SPIR-V | ||
device consumer to leverage static connectivity. An OpConstantPipeStorage | ||
instruction must create a single instance of OpPipeStorage for each kernel to | ||
kernel pipe type used by any kernel within the application. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not understand the sentence. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OpPipeStorage object is a SPIR-V representation of "global pipe". OpConstantPipeStorage is a structure of 3 integers which plays a role of pipe storage initializer. |
||
|
||
OpTypePipe objects is created from OpPipeStorage using | ||
OpCreatePipeFromPipeStorage. The number of OpTypePipe objects created from an | ||
OpPipeStorage object is an implementation detail, as are the access qualifiers | ||
applied to those types. For example, an implementation is free to create a | ||
different OpTypePipe corresponding to each read and write, with unidirectional | ||
access qualifiers annotated, or it can create fewer OpTypePipe objects, although | ||
read and write pipes must be distinct according to OpReadPipe and OpWritePipe | ||
rules. | ||
|
||
NOTE: The SPIR-V OpReadPipe and OpWritePipe instructions are non-blocking. | ||
|
||
Details SPIR-V representation in LLVM IR | ||
======================================== | ||
|
||
Pipe built-ins are mangled in LLVM IR to make it SPIR-V friendly. | ||
As an example: | ||
|
||
SPIR-V built-in | Mangled built-in in LLVM IR | ||
----------------------------+----------------------------------------------- | ||
OpReadPipe | __spirv_ReadPipe | ||
----------------------------+----------------------------------------------- | ||
OpWritePipe | __spirv_WritePipe | ||
----------------------------+----------------------------------------------- | ||
OpCreatePipeFromPipeStorage | __spirv_CreatePipeFromPipeStorage_{read|write} | ||
|
||
More about SPIR-V representation in LLVM IR can be found under the link: | ||
.. _SPIRVRepresentationInLLVM.rst: https://github.com/KhronosGroup/SPIRV-LLVM-Translator/blob/master/docs/SPIRVRepresentationInLLVM.rst/ | ||
|
||
In SYCL headers the built-ins are declared as external functions with the | ||
appropriate mangling. The translator will transform calls of these built-ins | ||
into calls of SPIR-V instructions. | ||
|
||
Example of SYCL -> LLVM IR -> SPIR-V -> LLVM-IR code transformations | ||
MrSidims marked this conversation as resolved.
Show resolved
Hide resolved
|
||
==================================================================== | ||
Consider following SYCL device code: | ||
.. code:: cpp | ||
pipe<class some_pipe, int, 1>::write(42, SuccessCode); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I really should make the document more clear. Let me answer some of your questions here and after. So if you want to ensure all the data is written to the pipe (in case if it has enough capacity) you shall write following code: |
||
|
||
After compiling this code with clang we will be given following piece of IR for | ||
the write pipe function call (NOTE: for this implementation clang-known | ||
OpenCL 2.0 pipe types are reused): | ||
.. code:: cpp | ||
define internal spir_func void @_ZN2cl4sycl4pipeIZ4mainE9some_pipeiLi1EE5writeEiRb(i32, i8* dereferenceable(1)) #4 align 2 { | ||
//... | ||
%12 = call spir_func i32 @_Z17__spirv_WritePipeIiEi8ocl_pipePT_ii(%opencl.pipe_wo_t addrspace(1)* %10, i32 addrspace(4)* %11, i32 4, i32 4) #8 | ||
//... | ||
} | ||
|
||
with following declaration: | ||
.. code:: cpp | ||
%12 = call spir_func i32 @_Z17__spirv_WritePipeIiEi8ocl_pipePT_ii(%opencl.pipe_wo_t addrspace(1)* %10, i32 addrspace(4)* %11, i32 4, i32 4) #8 | ||
|
||
SPIR-V translator will drop all of these manglings, just making a call of SPIR-V | ||
write pipe built-in: | ||
.. code:: cpp | ||
7 WritePipe 51 158 156 157 52 52 | ||
|
||
Resulting code for translation back to LLVM IR from SPIR-V are calls of OpenCL | ||
built-ins: | ||
.. code:: cpp | ||
define internal spir_func void @_ZN2cl4sycl4pipeIZ4mainE9some_pipeiLi1EE5writeEiRb(i32, i8*) #0 { | ||
//... | ||
%9 = call spir_func i32 @__write_pipe_2(%opencl.pipe_wo_t addrspace(1)* %6, i8 addrspace(4)* %8, i32 4, i32 4) //... | ||
} | ||
|
||
again with write pipe declaration (but now it's built-in!): | ||
.. code:: cpp | ||
declare spir_func i32 @__write_pipe_2(%opencl.pipe_wo_t addrspace(1)*, i8 addrspace(4)*, i32, i32) #0 | ||
|
||
The first argument in a call of __write_pipe_2 OpenCL built-in is a pipe object, | ||
which is created as a result of SPIR-V built-in call | ||
__spirv_CreatePipeFromPipeStorage_{read|write} which has no OpenCL | ||
representation and therefore stays in IR before and after SPIR-V tool-chain as: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unclear. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have rewrote this one, please check it. |
||
.. code:: cpp | ||
%9 = call spir_func %opencl.pipe_wo_t addrspace(1)* @_Z39__spirv_CreatePipeFromPipeStorage_writeIiE8ocl_pipe11PipeStorage(%struct._ZTS11PipeStorage.PipeStorage* byval align 4 %6) #8 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
SYCL INTEL spatial pipes | ||
======================== | ||
|
||
Introduction | ||
============ | ||
|
||
Pipe is a memory object that stores data organized as a FIFO buffer. | ||
This implementation enables 2 classes of pipe connectivity: | ||
Cross kernel: Kernel A -> Kernel B | ||
Intra-kernel: Kernel A -> Kernel A | ||
|
||
and 2 types of pipes: non-blocking and blocking. | ||
|
||
Blocking calls wait until there is available capacity to commit data, or until | ||
data is available to be read. In all of these cases the pipe indirectly conveys | ||
side channel control information to the program, which can be used for flow | ||
control or many other purposes by an application. | ||
|
||
Unsuccessful non-blocking pipe reads or writes do not impact the state or | ||
content of a pipe. | ||
|
||
Pipe identification | ||
=================== | ||
|
||
Identification mechanism of a pipe is base on template type specialization of | ||
pipe class. | ||
|
||
Consider following code: | ||
.. code:: cpp | ||
template <class name, typename dataT, size_t min_capacity = 0> class pipe; | ||
|
||
where 'name' is a name of a pipe that helps to identify pipe; | ||
'dataT' is a type of data to store into the pipe, it is required to have | ||
a default constructor, a copy constructor and a destructor. | ||
'min_capacity' is the number of outstanding words that can be written to | ||
a pipe but not yet read from it. | ||
|
||
The combined set of the three template parameters forms the type of a pipe. | ||
Any uses of a read/write method on that type operate on the same pipe by | ||
static connectivity of pipes. | ||
|
||
Interaction with SYCL API | ||
========================= | ||
|
||
Following member functions of pipe class are available for user: | ||
.. code:: cpp | ||
// Non-blocking pipes | ||
static dataT read(bool &success_code); | ||
MrSidims marked this conversation as resolved.
Show resolved
Hide resolved
|
||
static void write(const dataT &data, bool &success_code); | ||
|
||
// Blocking pipes | ||
static dataT read(); | ||
static void write(const dataT &data); | ||
|
||
Writes to or reads from a pipe are accesses to a pipe with the same pipe type. | ||
|
||
Simple example of SYCL program | ||
============================== | ||
Non-blocking pipes: | ||
.. code:: cpp | ||
using Pipe = pipe<class some_pipe, int, 1>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it legal to have a local There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I don't try to use it after - yes. It's a forward declaration that adds appropriate mangling for fields and methods of the template class pipe. |
||
Queue.submit([&](handler& cgh) { | ||
auto read_acc = readBuf.get_access<access::mode::read>(cgh); | ||
cgh.single_task<class foo_nb>([=]() { | ||
bool SuccessCode; | ||
do { | ||
Pipe::write(read_add[0], SuccessCode); // Write into a some_pipe | ||
// allocated for integers | ||
// with a capacity of 1. | ||
} while (!SuccessCode); | ||
}); | ||
}); | ||
buffer<int, 1> writeBuf (data, range<1>(dataSize)); | ||
Queue.submit([&](handler& cgh) { | ||
auto write_acc = writeBuf.get_access<access::mode::write>(cgh); | ||
cgh.single_task<class goo_nb>([=]() { | ||
bool SuccessCode; | ||
do { | ||
write_acc[0] = Pipe::read(SuccessCode); // Read data stored in the | ||
// pipe and put it in the | ||
// SYCL buffer. | ||
} while (!SuccessCode); | ||
}); | ||
}); | ||
|
||
Blocking pipes: | ||
.. code:: cpp | ||
using Pipe = pipe<class some_pipe, int, 1>; | ||
Queue.submit([&](handler& cgh) { | ||
auto read_acc = readBuf.get_access<access::mode::read>(cgh); | ||
cgh.single_task<class foo_nb>([=]() { | ||
Pipe::write(read_add[0]); // Write '42' into a some_pipe allocated | ||
// for integers with a capacity of 1. | ||
}); | ||
}); | ||
buffer<int, 1> writeBuf (data, range<1>(dataSize)); | ||
Queue.submit([&](handler& cgh) { | ||
auto write_acc = writeBuf.get_access<access::mode::write>(cgh); | ||
cgh.single_task<class goo_nb>([=]() { | ||
write_acc[0] = Pipe::read(); // Read data stored in the | ||
// pipe and put it in the | ||
// SYCL buffer. | ||
}); | ||
}); |
Uh oh!
There was an error while loading. Please reload this page.