Skip to content

[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

Merged
merged 3 commits into from
Jul 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -554,8 +554,8 @@ ALIAS("generic", __generic , KEYOPENCLC | KEYOPENCLCXX)
KEYWORD(__kernel , KEYOPENCLC | KEYOPENCLCXX)
ALIAS("kernel", __kernel , KEYOPENCLC | KEYOPENCLCXX)
// OpenCL access qualifiers
KEYWORD(__read_only , KEYOPENCLC | KEYOPENCLCXX)
KEYWORD(__write_only , KEYOPENCLC | KEYOPENCLCXX)
KEYWORD(__read_only , KEYOPENCLC | KEYOPENCLCXX | KEYSYCL)
KEYWORD(__write_only , KEYOPENCLC | KEYOPENCLCXX | KEYSYCL)
KEYWORD(__read_write , KEYOPENCLC | KEYOPENCLCXX)
ALIAS("read_only", __read_only , KEYOPENCLC | KEYOPENCLCXX)
ALIAS("write_only", __write_only , KEYOPENCLC | KEYOPENCLCXX)
Expand All @@ -570,6 +570,7 @@ KEYWORD(vec_step , KEYOPENCLC | KEYALTIVEC | KEYZVECTOR)
KEYWORD(__builtin_omp_required_simd_align, KEYALL)

KEYWORD(pipe , KEYOPENCLC | KEYOPENCLCXX)
KEYWORD(__pipe , KEYSYCL)

// Borland Extensions.
KEYWORD(__pascal , KEYALL)
Expand Down
16 changes: 12 additions & 4 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2562,7 +2562,8 @@ bool Parser::ParseImplicitInt(DeclSpec &DS, CXXScopeSpec *SS,

// Early exit as Sema has a dedicated missing_actual_pipe_type diagnostic
// for incomplete declarations such as `pipe p`.
if (getLangOpts().OpenCLCPlusPlus && DS.isTypeSpecPipe())
if ((getLangOpts().OpenCLCPlusPlus || getLangOpts().SYCLIsDevice) &&
DS.isTypeSpecPipe())
return false;

if (getLangOpts().CPlusPlus &&
Expand Down Expand Up @@ -3783,6 +3784,11 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS,
}
isInvalid = DS.SetTypePipe(true, Loc, PrevSpec, DiagID, Policy);
break;
case tok::kw___pipe:
if (getLangOpts().SYCLIsDevice)
// __pipe keyword is defined only for SYCL kernel language
isInvalid = DS.SetTypePipe(true, Loc, PrevSpec, DiagID, Policy);
break;
#define GENERIC_IMAGE_TYPE(ImgType, Id) \
case tok::kw_##ImgType##_t: \
isInvalid = DS.SetTypeSpecType(DeclSpec::TST_##ImgType##_t, Loc, PrevSpec, \
Expand Down Expand Up @@ -4902,8 +4908,9 @@ bool Parser::isDeclarationSpecifier(bool DisambiguatingWithExpression) {
default: return false;

case tok::kw_pipe:
case tok::kw___pipe:
return (getLangOpts().OpenCL && getLangOpts().OpenCLVersion >= 200) ||
getLangOpts().OpenCLCPlusPlus;
getLangOpts().OpenCLCPlusPlus || getLangOpts().SYCLIsDevice;

case tok::identifier: // foo::bar
// Unfortunate hack to support "Class.factoryMethod" notation.
Expand Down Expand Up @@ -5391,8 +5398,9 @@ static bool isPtrOperatorToken(tok::TokenKind Kind, const LangOptions &Lang,
if (Kind == tok::star || Kind == tok::caret)
return true;

if (Kind == tok::kw_pipe &&
((Lang.OpenCL && Lang.OpenCLVersion >= 200) || Lang.OpenCLCPlusPlus))
if ((Kind == tok::kw_pipe || Kind == tok::kw___pipe) &&
((Lang.OpenCL && Lang.OpenCLVersion >= 200) || Lang.OpenCLCPlusPlus ||
Lang.SYCLIsDevice))
return true;

if (!Lang.CPlusPlus)
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Parse/ParseTentative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,8 @@ Parser::isCXXDeclarationSpecifier(Parser::TPResult BracedCastResult,
case tok::kw___read_write:
// OpenCL pipe
case tok::kw_pipe:
// SYCL pipe
case tok::kw___pipe:

// GNU
case tok::kw_restrict:
Expand Down
14 changes: 5 additions & 9 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9286,15 +9286,11 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
for (const ParmVarDecl *Param : NewFD->parameters()) {
QualType PT = Param->getType();

// OpenCL 2.0 pipe restrictions forbids pipe packet types to be non-value
// types.
if (getLangOpts().OpenCLVersion >= 200 || getLangOpts().OpenCLCPlusPlus) {
if(const PipeType *PipeTy = PT->getAs<PipeType>()) {
QualType ElemTy = PipeTy->getElementType();
if (ElemTy->isReferenceType() || ElemTy->isPointerType()) {
Diag(Param->getTypeSpecStartLoc(), diag::err_reference_pipe_type );
D.setInvalidType();
}
if (const PipeType *PipeTy = PT->getAs<PipeType>()) {
QualType ElemTy = PipeTy->getElementType();
if (ElemTy->isReferenceType() || ElemTy->isPointerType()) {
Diag(Param->getTypeSpecStartLoc(), diag::err_reference_pipe_type );
D.setInvalidType();
}
}
}
Expand Down
18 changes: 14 additions & 4 deletions clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1365,7 +1365,8 @@ static QualType ConvertDeclSpecToType(TypeProcessingState &state) {
// errors.
declarator.setInvalidType(true);
} else if ((S.getLangOpts().OpenCLVersion >= 200 ||
S.getLangOpts().OpenCLCPlusPlus) &&
S.getLangOpts().OpenCLCPlusPlus ||
S.getLangOpts().SYCLIsDevice) &&
DS.isTypeSpecPipe()) {
S.Diag(DeclLoc, diag::err_missing_actual_pipe_type)
<< DS.getSourceRange();
Expand Down Expand Up @@ -2322,7 +2323,7 @@ QualType Sema::BuildArrayType(QualType T, ArrayType::ArraySizeModifier ASM,
// OpenCL v2.0 s6.12.5 - Arrays of blocks are not supported.
// OpenCL v2.0 s6.16.13.1 - Arrays of pipe type are not supported.
// OpenCL v2.0 s6.9.b - Arrays of image/sampler type are not supported.
if (getLangOpts().OpenCL || getLangOpts().SYCLIsDevice) {
if (getLangOpts().OpenCL) {
const QualType ArrType = Context.getBaseElementType(T);
if (ArrType->isBlockPointerType() || ArrType->isPipeType() ||
ArrType->isSamplerT() || ArrType->isImageType()) {
Expand Down Expand Up @@ -4607,11 +4608,20 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
}
}

if (LangOpts.OpenCL) {
// OpenCL v2.0 s6.12.5 - A pipe cannot be the return value of a
// function. Disrespect this for SYCL.
if (T->isPipeType()) {
S.Diag(D.getIdentifierLoc(), diag::err_opencl_invalid_return)
<< T << 1 /*hint off*/;
D.setInvalidType(true);
}
}

if (LangOpts.OpenCL || LangOpts.SYCLIsDevice) {
// OpenCL v2.0 s6.12.5 - A block cannot be the return value of a
// function.
if (T->isBlockPointerType() || T->isImageType() || T->isSamplerT() ||
T->isPipeType()) {
if (T->isBlockPointerType() || T->isImageType() || T->isSamplerT()) {
S.Diag(D.getIdentifierLoc(), diag::err_opencl_invalid_return)
<< T << 1 /*hint off*/;
D.setInvalidType(true);
Expand Down
119 changes: 119 additions & 0 deletions sycl/doc/extensions/SYCL_pipes_lowering_to_SPIRV.rst
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
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand the sentence.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
====================================================================
Consider following SYCL device code:
.. code:: cpp
pipe<class some_pipe, int, 1>::write(42, SuccessCode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this SuccessCode?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Basically in these PR introduced 2 types of pipes: non-blocking and blocking (in API they are looking like write(dataT, bool) Vs write(dataT).
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.

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:
Pipe::write(42); // for blocking pipes
or
bool Success = false;
while (!Success)
Pipe::write(42, Success); // non-blocking pipes


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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
104 changes: 104 additions & 0 deletions sycl/doc/extensions/SYCL_pipes_usage.rst
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);
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>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it legal to have a local class some_pipe declaration like here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
});
});
14 changes: 13 additions & 1 deletion sycl/include/CL/__spirv/spirv_ops.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,21 @@ template <typename dataT>
extern void __spirv_SubgroupBlockWriteINTEL(__global uint32_t *Ptr,
dataT Data) noexcept;

template <typename dataT>
extern int32_t __spirv_ReadPipe(RPipeTy<dataT> Pipe, dataT *Data,
int32_t Size, int32_t Alignment) noexcept;
template <typename dataT>
extern int32_t __spirv_WritePipe(WPipeTy<dataT> Pipe, dataT *Data,
int32_t Size, int32_t Alignment) noexcept;
template <typename dataT>
extern RPipeTy<dataT> __spirv_CreatePipeFromPipeStorage_read(
const ConstantPipeStorage *Storage) noexcept;
template <typename dataT>
extern WPipeTy<dataT> __spirv_CreatePipeFromPipeStorage_write(
const ConstantPipeStorage *Storage) noexcept;

extern void __spirv_ocl_prefetch(const __global char *Ptr,
size_t NumBytes) noexcept;

#else // if !__SYCL_DEVICE_ONLY__

template <typename dataT>
Expand Down
Loading