Skip to content

Commit cb9c774

Browse files
committed
[SYCL][FPGA] Initial implementation of pipe feature
Pipe are supported via generation SPIR-V friendly IR and lowering it directly to SPIR-V code. non-blocking pipes are supported; blocking pipes are supported as a workaround. Signed-off-by: Dmitry Sidorov <dmitry.sidorov@intel.com>
1 parent 63c738d commit cb9c774

File tree

5 files changed

+493
-0
lines changed

5 files changed

+493
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
SYCL INTEL spatial pipes
2+
========================
3+
4+
Introduction
5+
============
6+
7+
SPIR-V is first class target in which SYCL pipes should be representable, and
8+
pipes are already exposed within SPIR-V. For this implementation API functions
9+
call for SPIR-V friendly mangled functions instead of OpenCL built-ins.
10+
This document describes how SYCL pipes are being lowered to SPIR-V.
11+
12+
OpenCL 2.2 program pipe representation in SPIR-V
13+
================================================
14+
15+
The SPIR-V program pipe representation is used to be an underlying
16+
representation of intra-kernel and inter-kernel static pipe connectivity.
17+
The SPIR-V pipe representation exists in a series of pieces:
18+
19+
- OpTypePipeStorage: Type representing memory allocated for storage of data
20+
within a pipe. Used for OpenCL 2.2 program pipes (program-scope pipes) that
21+
the host program is not aware of, but that enables connectivity between
22+
kernels.
23+
24+
- OpConstantPipeStorage: Instruction that creates an OpTypePipeStorage object.
25+
Requires packet size (number of bytes) and capacity (number of packets) to be
26+
defined.
27+
28+
- OpTypePipe: A pipe object that can act as a read/write endpoint of some pipe
29+
storage, either allocated by the host and passed as a kernel argument, or
30+
allocated at "program scope" through a pipe storage object.
31+
32+
- OpCreatePipeFromPipeStorage: Creates a pipe object (that can be read/written)
33+
from an OpTypePipeStorage instance.
34+
35+
- OpReadPipe / OpWritePipe: Read packet from or write packet to a pipe object.
36+
37+
Lowering of kernel to kernel pipes to SPIR-V (non-blocking)
38+
===========================================================
39+
40+
This connectivity is achieved through OpTypePipeStorage which allows a SPIR-V
41+
device consumer to leverage static connectivity. An OpConstantPipeStorage
42+
instruction must create a single instance of OpPipeStorage for each kernel to
43+
kernel pipe type used by any kernel within the application.
44+
45+
OpTypePipe objects is created from OpPipeStorage using
46+
OpCreatePipeFromPipeStorage. The number of OpTypePipe objects created from an
47+
OpPipeStorage object is an implementation detail, as are the access qualifiers
48+
applied to those types. For example, an implementation is free to create a
49+
different OpTypePipe corresponding to each read and write, with unidirectional
50+
access qualifiers annotated, or it can create fewer OpTypePipe objects, although
51+
read and write pipes must be distinct according to OpReadPipe and OpWritePipe
52+
rules.
53+
54+
NOTE: The SPIR-V OpReadPipe and OpWritePipe instructions are non-blocking.
55+
56+
Details SPIR-V representation in LLVM IR
57+
========================================
58+
59+
Pipe built-ins are mangled in LLVM IR to make it SPIR-V friendly.
60+
As an example:
61+
62+
SPIR-V built-in | Mangled built-in in LLVM IR
63+
----------------------------+-----------------------------------------------
64+
OpReadPipe | __spirv_ReadPipe
65+
----------------------------+-----------------------------------------------
66+
OpWritePipe | __spirv_WritePipe
67+
----------------------------+-----------------------------------------------
68+
OpCreatePipeFromPipeStorage | __spirv_CreatePipeFromPipeStorage_{read|write}
69+
70+
More about SPIR-V representation in LLVM IR can be found under the link:
71+
.. _SPIRVRepresentationInLLVM.rst: https://github.com/KhronosGroup/SPIRV-LLVM-Translator/blob/master/docs/SPIRVRepresentationInLLVM.rst/
72+
73+
In SYCL headers the built-ins are declared as external functions with the
74+
appropriate mangling. The translator will transform calls of these built-ins
75+
into calls of SPIR-V instructions.
76+
77+
Example of SYCL -> LLVM IR -> SPIR-V -> LLVM-IR code transformations
78+
====================================================================
79+
Consider following SYCL device code:
80+
.. code:: cpp
81+
pipe<class some_pipe, int, 1>::write(42, SuccessCode);
82+
83+
After compiling this code with clang we will be given following piece of IR for
84+
the write pipe function call (NOTE: for this implementation clang-known
85+
OpenCL 2.0 pipe types are reused):
86+
.. code:: cpp
87+
define internal spir_func void @_ZN2cl4sycl4pipeIZ4mainE9some_pipeiLi1EE5writeEiRb(i32, i8* dereferenceable(1)) #4 align 2 {
88+
//...
89+
%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
90+
//...
91+
}
92+
93+
with following declaration:
94+
.. code:: cpp
95+
%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
96+
97+
SPIR-V translator will drop all of these manglings, just making a call of SPIR-V
98+
write pipe built-in:
99+
.. code:: cpp
100+
7 WritePipe 51 158 156 157 52 52
101+
102+
Resulting code for translation back to LLVM IR from SPIR-V are calls of OpenCL
103+
built-ins:
104+
.. code:: cpp
105+
define internal spir_func void @_ZN2cl4sycl4pipeIZ4mainE9some_pipeiLi1EE5writeEiRb(i32, i8*) #0 {
106+
//...
107+
%9 = call spir_func i32 @__write_pipe_2(%opencl.pipe_wo_t addrspace(1)* %6, i8 addrspace(4)* %8, i32 4, i32 4) //...
108+
}
109+
110+
again with write pipe declaration (but now it's built-in!):
111+
.. code:: cpp
112+
declare spir_func i32 @__write_pipe_2(%opencl.pipe_wo_t addrspace(1)*, i8 addrspace(4)*, i32, i32) #0
113+
114+
The first argument in a call of __write_pipe_2 OpenCL built-in is a pipe object,
115+
which is created as a result of SPIR-V built-in call
116+
__spirv_CreatePipeFromPipeStorage_{read|write} which has no OpenCL
117+
representation and therefore stays in IR before and after SPIR-V tool-chain as:
118+
.. code:: cpp
119+
%9 = call spir_func %opencl.pipe_wo_t addrspace(1)* @_Z39__spirv_CreatePipeFromPipeStorage_writeIiE8ocl_pipe11PipeStorage(%struct._ZTS11PipeStorage.PipeStorage* byval align 4 %6) #8
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
SYCL INTEL spatial pipes
2+
========================
3+
4+
Introduction
5+
============
6+
7+
Pipe is a memory object that stores data organized as a FIFO buffer.
8+
This implementation enables 2 classes of pipe connectivity:
9+
Cross kernel: Kernel A -> Kernel B
10+
Intra-kernel: Kernel A -> Kernel A
11+
12+
and 2 types of pipes: non-blocking and blocking.
13+
14+
Blocking calls wait until there is available capacity to commit data, or until
15+
data is available to be read. In all of these cases the pipe indirectly conveys
16+
side channel control information to the program, which can be used for flow
17+
control or many other purposes by an application.
18+
19+
Unsuccessful non-blocking pipe reads or writes do not impact the state or
20+
content of a pipe.
21+
22+
Pipe identification
23+
===================
24+
25+
Identification mechanism of a pipe is base on template type specialization of
26+
pipe class.
27+
28+
Consider following code:
29+
.. code:: cpp
30+
template <class name, typename dataT, size_t min_capacity = 0> class pipe;
31+
32+
where 'name' is a name of a pipe that helps to identify pipe;
33+
'dataT' is a type of data to store into the pipe, it is required to have
34+
a default constructor, a copy constructor and a destructor.
35+
'min_capacity' is the number of outstanding words that can be written to
36+
a pipe but not yet read from it.
37+
38+
The combined set of the three template parameters forms the type of a pipe.
39+
Any uses of a read/write method on that type operate on the same pipe by
40+
static connectivity of pipes.
41+
42+
Interaction with SYCL API
43+
=========================
44+
45+
Following member functions of pipe class are available for user:
46+
.. code:: cpp
47+
// Non-blocking pipes
48+
static dataT read(bool &success_code);
49+
static void write(const dataT &data, bool &success_code);
50+
51+
// Blocking pipes
52+
static dataT read();
53+
static void write(const dataT &data);
54+
55+
Writes to or reads from a pipe are accesses to a pipe with the same pipe type.
56+
57+
Simple example of SYCL program
58+
==============================
59+
Non-blocking pipes:
60+
.. code:: cpp
61+
using Pipe = pipe<class some_pipe, int, 1>;
62+
Queue.submit([&](handler& cgh) {
63+
auto read_acc = readBuf.get_access<access::mode::read>(cgh);
64+
cgh.single_task<class foo_nb>([=]() {
65+
bool SuccessCode;
66+
do {
67+
Pipe::write(read_add[0], SuccessCode); // Write into a some_pipe
68+
// allocated for integers
69+
// with a capacity of 1.
70+
} while (!SuccessCode);
71+
});
72+
});
73+
buffer<int, 1> writeBuf (data, range<1>(dataSize));
74+
Queue.submit([&](handler& cgh) {
75+
auto write_acc = writeBuf.get_access<access::mode::write>(cgh);
76+
cgh.single_task<class goo_nb>([=]() {
77+
bool SuccessCode;
78+
do {
79+
write_acc[0] = Pipe::read(SuccessCode); // Read data stored in the
80+
// pipe and put it in the
81+
// SYCL buffer.
82+
} while (!SuccessCode);
83+
});
84+
});
85+
86+
Blocking pipes:
87+
.. code:: cpp
88+
using Pipe = pipe<class some_pipe, int, 1>;
89+
Queue.submit([&](handler& cgh) {
90+
auto read_acc = readBuf.get_access<access::mode::read>(cgh);
91+
cgh.single_task<class foo_nb>([=]() {
92+
Pipe::write(read_add[0]); // Write '42' into a some_pipe allocated
93+
// for integers with a capacity of 1.
94+
});
95+
});
96+
buffer<int, 1> writeBuf (data, range<1>(dataSize));
97+
Queue.submit([&](handler& cgh) {
98+
auto write_acc = writeBuf.get_access<access::mode::write>(cgh);
99+
cgh.single_task<class goo_nb>([=]() {
100+
write_acc[0] = Pipe::read(); // Read data stored in the
101+
// pipe and put it in the
102+
// SYCL buffer.
103+
});
104+
});

sycl/include/CL/sycl.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <CL/sycl/multi_ptr.hpp>
2929
#include <CL/sycl/nd_item.hpp>
3030
#include <CL/sycl/nd_range.hpp>
31+
#include <CL/sycl/pipes.hpp>
3132
#include <CL/sycl/platform.hpp>
3233
#include <CL/sycl/pointers.hpp>
3334
#include <CL/sycl/program.hpp>

sycl/include/CL/sycl/pipes.hpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//==---------------- pipes.hpp - SYCL pipes ------------*- C++ -*-----------==//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
// ===--------------------------------------------------------------------=== //
8+
9+
#pragma once
10+
11+
#include <CL/__spirv/spirv_ops.hpp>
12+
#include <CL/__spirv/spirv_types.hpp>
13+
#include <CL/sycl/stl.hpp>
14+
15+
namespace cl {
16+
namespace sycl {
17+
18+
template <class name, class dataT, int32_t min_capacity = 0> class pipe {
19+
public:
20+
// Non-blocking pipes
21+
// Reading from pipe is lowered to SPIR-V instruction OpReadPipe via SPIR-V
22+
// friendly LLVM IR.
23+
static dataT read(bool &Success) {
24+
#ifdef __SYCL_DEVICE_ONLY__
25+
RPipeTy<dataT> RPipe =
26+
__spirv_CreatePipeFromPipeStorage_read<dataT>(&m_Storage);
27+
dataT TempData;
28+
Success = static_cast<bool>(
29+
__spirv_ReadPipe(RPipe, &TempData, m_Size, m_Alignment));
30+
return TempData;
31+
#else
32+
assert(!"Pipes are not supported on a host device!");
33+
#endif // __SYCL_DEVICE_ONLY__
34+
}
35+
36+
// Writing to pipe is lowered to SPIR-V instruction OpWritePipe via SPIR-V
37+
// friendly LLVM IR.
38+
static void write(const dataT &Data, bool &Success) {
39+
#ifdef __SYCL_DEVICE_ONLY__
40+
WPipeTy<dataT> WPipe =
41+
__spirv_CreatePipeFromPipeStorage_write<dataT>(&m_Storage);
42+
Success = static_cast<bool>(
43+
__spirv_WritePipe(WPipe, &Data, m_Size, m_Alignment));
44+
#else
45+
assert(!"Pipes are not supported on a host device!");
46+
#endif // __SYCL_DEVICE_ONLY__
47+
}
48+
49+
// Blocking pipes
50+
// Reading from pipe is lowered to SPIR-V instruction OpReadPipe via SPIR-V
51+
// friendly LLVM IR.
52+
static dataT read() {
53+
#ifdef __SYCL_DEVICE_ONLY__
54+
RPipeTy<dataT> RPipe =
55+
__spirv_CreatePipeFromPipeStorage_read<dataT>(&m_Storage);
56+
dataT TempData;
57+
// FIXME: this is workaround unless special SPIR-V decoration is implemented
58+
while (!__spirv_ReadPipe(RPipe, &TempData, m_Size, m_Alignment));
59+
return TempData;
60+
#else
61+
assert(!"Pipes are not supported on a host device!");
62+
#endif // __SYCL_DEVICE_ONLY__
63+
}
64+
65+
// Writing to pipe is lowered to SPIR-V instruction OpWritePipe via SPIR-V
66+
// friendly LLVM IR.
67+
static void write(const dataT &Data) {
68+
#ifdef __SYCL_DEVICE_ONLY__
69+
WPipeTy<dataT> WPipe =
70+
__spirv_CreatePipeFromPipeStorage_write<dataT>(&m_Storage);
71+
// FIXME: this is workaround unless special SPIR-V decoration is implemented
72+
while (!__spirv_WritePipe(WPipe, &Data, m_Size, m_Alignment));
73+
#else
74+
assert(!"Pipes are not supported on a host device!");
75+
#endif // __SYCL_DEVICE_ONLY__
76+
}
77+
78+
private:
79+
static constexpr int32_t m_Size = sizeof(dataT);
80+
static constexpr int32_t m_Alignment = alignof(dataT);
81+
static constexpr int32_t m_Capacity = min_capacity;
82+
#ifdef __SYCL_DEVICE_ONLY__
83+
static constexpr struct ConstantPipeStorage m_Storage = {m_Size, m_Alignment,
84+
m_Capacity};
85+
#endif // __SYCL_DEVICE_ONLY__
86+
};
87+
88+
} // namespace sycl
89+
} // namespace cl

0 commit comments

Comments
 (0)