Implementation design for compile time constant properties¶
This document describes the implementation design for the DPC++ extension sycl_ext_oneapi_properties, which adds a general mechanism for specifying properties which are known at compile time. This extension is not itself a feature, but rather a building block that can be incorporated into other features.
There are a number of situations where we plan to use compile-time constant
properties, but this design document does not attempt to address them all.
Rather, it describes the design for each “category” of use and illustrates each
category with a specific feature. For example accessor
is used to illustrate
properties that are applied to a kernel argument, but the same technique could
be used for other variables that are captured as kernel arguments.
In all cases, the goal of this design is to explain how a DPC++ program that uses properties is consumed by the device compiler and eventually represented in LLVM IR. This typically involves some logic in the header files which results in a C++ annotation that contains the properties. The device compiler front-end is responsible for consuming this annotation and producing some corresponding LLVM IR. One of the goals of this design is to avoid changes to the front-end each time we add a new property, so the front-end is not required to understand each property it consumes. Instead, it follows a mechanical process for converting properties listed in the C++ annotation into LLVM IR, and this mechanical process need not be updated when we add new properties.
Once the information about properties is represented in IR, it is available to
compiler passes. For example, the sycl-post-link
tool might use a property
in order to perform one of its transformations. Some properties are consumed
by the DPC++ compiler, but others are transformed into SPIR-V for use by the
JIT compiler. This design document also describes how this SPIR-V
transformation is done.
Properties on a global variable type¶
One use for compile-time properties is with types that are used exclusively for declaring global variables. One such example is the sycl_ext_oneapi_device_global extension:
namespace sycl::ext::oneapi {
template <typename T, typename PropertyListT = experimental::empty_properties_t>
class device_global {/*...*/};
} // namespace sycl::ext::oneapi
The following code illustrates a device_global
variable that is declared with
two compile-time properties:
using sycl::ext::oneapi;
device_global<int, decltype(properties{device_image_scope, host_access_read})>
dm1;
The header file represents these properties with an internal C++ attribute
named [[__sycl_detail__::add_ir_attributes_global_variable()]]
whose value
is a list that is created through a template parameter pack expansion:
namespace sycl::ext::oneapi {
template <typename T, typename PropertyListT = experimental::empty_properties_t>
class device_global {/*...*/};
// Partial specialization to make PropertyListT visible as a parameter pack
// of properties.
template <typename T, typename ...Props>
class
#ifdef __SYCL_DEVICE_ONLY__
[[__sycl_detail__::add_ir_attributes_global_variable(
Props::meta_name..., Props::meta_value...
)]]
#endif
device_global<T, properties<Props...>> {/*...*/};
} // namespace sycl::ext::oneapi
The [[__sycl_detail__::add_ir_attributes_global_variable()]]
attribute has an
even number of parameters, assuming that the optional “filter list” parameter
is not specified (see below for a description of this parameter). The first
half of the parameters are the names of the properties, and the second half of
the parameters are the values for those properties. Each property has exactly
one value, so the property at parameter position 0 corresponds to the value at
position N / 2, etc. To illustrate using the same example as before, the
result of the parameter pack expansion would look like this:
namespace sycl::ext::oneapi {
template </* ... */> class
#ifdef __SYCL_DEVICE_ONLY__
[[__sycl_detail__::add_ir_attributes_global_variable(
"sycl-device-image-scope", // Name of first property
"sycl-host-access", // Name of second property
nullptr, // First property has no parameter
"read" // Value of second property
)]]
#endif
device_global</* ... */> {/*...*/};
} // namespace sycl::ext::oneapi
The device compiler only uses the
[[__sycl_detail__::add_ir_attributes_global_variable()]]
attribute when the
decorated type is used to create an LLVM IR global variable and the global
variable’s type is either:
The type that is decorated by the attribute, or
An array of the type that is decorated by the attribute.
The device compiler front-end silently ignores the attribute when the decorated type is used in any other way.
When the device compiler front-end creates a global variable from the decorated
type as described above, it also adds one IR attribute to the global variable
for each property using
GlobalVariable::addAttribute(StringRef, StringRef)
. If the property
value is not already a string, it converts it to a string as described in
IR representation as IR attributes.
Note that the front-end does not need to understand any of the properties in order to do this translation.
Properties on kernel arguments¶
Another use of compile-time properties is with types that are used to define kernel arguments. For example, the sycl_ext_oneapi_accessor_properties extension could be redesigned to use compile-time properties. Such a redesign might look like:
namespace sycl {
template <typename dataT,
int dimensions,
access::mode accessmode,
access::target accessTarget,
access::placeholder isPlaceholder,
typename PropertyListT = ext::oneapi::experimental::empty_properties_t>
class __attribute__((sycl_special_class)) accessor {/* ... */};
} // namespace sycl
Typical usage would look like this (showing a hypothetical property named
foo
):
using sycl;
using sycl::ext::oneapi;
accessor acc(buf, cgh, properties{no_alias, foo<32>});
In the headers the C++ attribute
[[__sycl_detail__::add_ir_attributes_kernel_parameter()]]
is used to decorate
parameters of the __init
member function in the corresponding
sycl_special_class
decorated class. As before, the initial parameters are the
names of the properties and the subsequent parameters are the property values.
namespace sycl {
template <typename dataT,
int dimensions,
access::mode accessmode,
access::target accessTarget,
access::placeholder isPlaceholder,
typename PropertyListT = ext::oneapi::experimental::empty_properties_t>
class __attribute__((sycl_special_class)) accessor {/* ... */};
// Partial specialization to make PropertyListT visible as a parameter pack
// of properties.
template <typename dataT,
int dimensions,
access::mode accessmode,
access::target accessTarget,
access::placeholder isPlaceholder,
typename ...Props>
class __attribute__((sycl_special_class)) accessor<dataT,
dimensions,
accessmode,
accessTarget,
isPlaceholder,
properties<Props...>> {
dataT *ptr;
#ifdef __SYCL_DEVICE_ONLY__
void __init(
[[__sycl_detail__::add_ir_attributes_kernel_parameter(
Props::meta_name..., Props::meta_value...
)]]
dataT *_ptr) {
ptr = _ptr;
}
#endif
};
} // namespace sycl
Illustrating this with the previous example:
namespace sycl {
template </* ... */>
class __attribute__((sycl_special_class)) accessor</* ... */> {
dataT *ptr;
#ifdef __SYCL_DEVICE_ONLY__
void __init(
[[__sycl_detail__::add_ir_attributes_kernel_parameter(
"sycl-no-alias", // Name of first property
"sycl-foo", // Name of second property
nullptr, // First property has no parameter
32 // Value of second property
)]]
dataT *_ptr) {
ptr = _ptr;
}
#endif
};
} // namespace sycl
As the name implies, this C++ attribute is only used to decorate parameters of
the __init
member function of a class type that is as SYCL “special class”
(i.e. a class that is decorated with __attribute__((sycl_special_class))
).
The device compiler front-end ignores the attribute when it is used in any
other syntactic position.
When the front-end creates a kernel argument from a SYCL “special class”, it
copies all parameters of the __init
member function to the corresponding
kernel function. If a copied parameter is decorated with
[[__sycl_detail__::add_ir_attributes_kernel_parameter()]]
, the front-end adds
one LLVM IR attribute to the resulting kernel function parameter for each
property in the list. For example, this can be done by calling
Function::addParamAttrs(unsigned ArgNo, const AttrBuilder &)
. As
before, the IR attributes are added as strings, so the front-end must convert
the property value to a string if it is not already a string.
Properties on kernel functions¶
Compile-time properties can also be used to decorate kernel functions as
proposed in the sycl_ext_oneapi_kernel_properties extension. There
are two ways the application can specify these properties. The first is by
passing a properties
parameter to the function that submits the kernel:
namespace sycl {
class handler {
template <typename KernelName, typename KernelType, typename PropertyListT>
void single_task(PropertyListT properties, const KernelType &kernelFunc);
};
// namespace sycl
For example:
using sycl;
using sycl::ext::oneapi;
void foo(handler &cgh) {
cgh.single_task(
properties{sub_group_size<32>, device_has<aspect::fp16>},
[=] {/* ... */});
}
The second way an application can specify kernel properties is by adding a
member function named get(sycl::ext::oneapi::properties_tag)
to a named
kernel function object:
using sycl;
using sycl::ext::oneapi;
class MyKernel {
public:
void operator()() {/* ... */}
auto get(properties_tag) {
return properties{sub_group_size<32>, device_has<aspect::fp16>};
}
};
void foo(handler &cgh) {
MyKernel k;
cgh.single_task(k);
}
Internally, the header lowers both cases to a wrapper class which defines
operator()
, and that operator function becomes the “top level” kernel
function that is recognized by the front-end. The definition of this operator
is decorated with the C++ attribute
[[__sycl_detail__::add_ir_attributes_function()]]
, and the parameters to this
attribute represent the properties.
template<typename KernelType, typename PropertyListT>
class KernelSingleTaskWrapper;
// Partial specialization to make PropertyListT visible as a parameter pack
// of properties.
template<typename KernelType, typename ...Props>
class KernelSingleTaskWrapper<KernelType, properties<Props...>> {
KernelType k;
public:
KernelSingleTaskWrapper(KernelType k) : k(k) {}
#ifdef __SYCL_DEVICE_ONLY__
[[clang::sycl_kernel]]
[[__sycl_detail__::add_ir_attributes_function(
Props::meta_name..., Props::meta_value...
)]]
#endif
void operator()() const {k();}
};
Although the DPC++ headers only use the
[[__sycl_detail__::add_ir_attributes_function()]]
attribute on the definition
of a kernel function as shown above, the front-end recognizes it for any
function definition. The front-end adds one LLVM IR function attribute for
each property in the list. For example, this can be done by calling
Function::addFnAttr(StringRef, StringRef)
. As before, the IR attributes
are added as strings, so the front-end must convert the property value to a
string if it is not already a string.
NOTE: The intention is to replace the existing member functions like
handler::kernel_single_task()
with wrapper classes like
KernelSingleTaskWrapper
. We believe this will not cause problems for the
device compiler front-end because it recognizes kernel functions via the
[[clang::sycl_kernel]]
attribute, not by the name
handler::kernel_single_task()
.
Properties on a non-global variable type¶
Another use of compile-time properties is with types that are used to define
non-global variables. An example of this is the proposed annotated_ptr
class.
namespace sycl::ext::oneapi {
template <typename T, typename PropertyListT = experimental::empty_properties_t>
class annotated_ptr {
T *ptr;
public:
annotated_ptr(T *p) : ptr(p) {}
};
} // namespace sycl::ext::oneapi
where an example use looks like:
using sycl::ext::oneapi;
void foo(int *p) {
annotated_ptr<int, decltype(properties{foo, bar<32>})> aptr(p);
}
We again use a C++ attribute to represent the properties in the header. The
attribute [[__sycl_detail__::add_ir_annotations_member()]]
decorates one of
the member variables of the class, and the parameters to this attribute
represent the properties.
namespace sycl::ext::oneapi {
template <typename T, typename PropertyListT = experimental::empty_properties_t>
class annotated_ptr;
// Partial specialization to make PropertyListT visible as a parameter pack
// of properties.
template <typename T, typename ...Props>
class annotated_ptr<T, properties<Props...>> {
T *ptr
#ifdef __SYCL_DEVICE_ONLY__
[[__sycl_detail__::add_ir_annotations_member(
Props::meta_name..., Props::meta_value...
)]]
#endif
;
public:
annotated_ptr(T *p) : ptr(p) {}
};
} // namespace sycl::ext::oneapi
Illustrating this with properties from our previous example:
namespace sycl::ext::oneapi {
template <typename T, typename PropertyListT = experimental::empty_properties_t>
class annotated_ptr;
// Partial specialization to make PropertyListT visible as a parameter pack
// of properties.
template <typename T, typename ...Props>
class annotated_ptr<T, properties<Props...>> {
T *ptr
#ifdef __SYCL_DEVICE_ONLY__
[[__sycl_detail__::add_ir_annotations_member(
"sycl-foo", // Name of first property
"sycl-bar", // Name of second property
nullptr, // First property has no parameter
32 // Value of second property
)]]
#endif
;
public:
annotated_ptr(T *p) : ptr(p) {}
};
} // namespace sycl::ext::oneapi
When the device compiler generates code to reference the decorated member
variable, it emits a call to the LLVM intrinsic function
@llvm.ptr.annotation
that annotates the pointer to that member
variables, similar to the way the existing [[clang::annotate()]]
attribute
works.
The front-end encodes the properties from the C++ attribute
[[__sycl_detail__::add_ir_annotations_member()]]
into the
@llvm.ptr.annotation
call as follows:
The first parameter to
@llvm.ptr.annotation
is the pointer to annotate (as with any call to this intrinsic).The second parameter is the literal string
"sycl-properties"
.The third parameter is the name of the source file (as with any call to this intrinsic).
The fourth parameter is the line number (as with any call to this intrinsic).
The fifth parameter is a pointer to a constant global variable. The type of this variable is an anonymous structure. The first field of the structure is a pointer to a string literal representing the name of the first property. The second field of the structure is a pointer to a string literal representing the value of the first property. The third field of the structure is a pointer to a string literal representing the name of the second property, etc. Since each property has exactly one value, this tuple has an even number of elements. Pointers to property value strings may be a null-pointer, signalling a property without a value.
The resulting LLVM IR for the previous example would be:
@.str = private unnamed_addr constant [16 x i8] c"sycl-properties\00",
section "llvm.metadata"
@.str.1 = private unnamed_addr constant [9 x i8] c"file.cpp\00",
section "llvm.metadata"
@.str.2 = private unnamed_addr constant [9 x i8] c"sycl-foo\00",
section "llvm.metadata"
@.str.3 = private unnamed_addr constant [9 x i8] c"sycl-bar\00",
section "llvm.metadata"
@.str.4 = private unnamed_addr constant [3 x i8] c"32\00",
section "llvm.metadata"
@.args = private unnamed_addr constant { [9 x i8]*, i8*, [9 x i8]*, [3 x i8]* }
{
[9 x i8]* @.str.2, ; Name of first property "sycl-foo"
i8* null, ; Null indicates this property has no value
[9 x i8]* @.str.3, ; Name of second property "sycl-bar"
[3 x i8]* @.str.4 ; Value of second property
},
section "llvm.metadata"
define void @foo(i32* %ptr) {
%aptr = alloca %class.annotated_ptr
%ptr = getelementptr inbounds %class.annotated_ptr, %class.annotated_ptr* %aptr,
i32 0, i32 0
%1 = bitcast i32** %ptr to i8*
%2 = call i8* @llvm.ptr.annotation.p0i8(i8* nonnull %0,
i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i64 0, i64 0),
i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.1, i64 0, i64 0),
i32 3,
i8* bitcast ({ [9 x i8]*, i8*, [9 x i8]*, [3 x i8]* }* @.args to i8*))
%3 = bitcast i8* %2 to i32**
store i32* %ptr, i32** %3
ret void
}
NOTE: Calls to the @llvm.ptr.annotation
intrinsic function are known to
disable many clang optimizations. As a result, properties added to a
non-global variable will likely result in LLVM IR (and SPIR-V) that is not well
optimized. This puts more pressure on the SPIR-V consumer (e.g. JIT compiler)
to perform these optimizations.
Property representation in C++ attributes and in IR¶
As noted above, there are several C++ attributes that convey property names and values to the front-end:
[[__sycl_detail__::add_ir_attributes_global_variable()]]
[[__sycl_detail__::add_ir_attributes_kernel_parameter()]]
[[__sycl_detail__::add_ir_attributes_function()]]
[[__sycl_detail__::add_ir_annotations_member()]]
All of these attributes take a parameter list with the same format. There are
always an even number of parameters, where the first half are the property
names and the second half are the property values. (This assumes that the
initial optional parameter is not passed. See below for a description of this
optional parameter.) The property name is always a string literal or a
constexpr char *
expression. By convention, property names normally start
with the prefix "sycl-"
in order to avoid collision with non-SYCL IR
attributes, but this is not a strict requirement.
The property value can be a literal or constexpr
expression of the following
types:
const char *
.An integer type.
A floating point type.
A boolean type.
A character type.
An enumeration type.
nullptr_t
(reserved for the case when a property has no value).
All properties require a value when represented in the C++ attribute. If the
SYCL property has no value the header passes nullptr
.
IR representation as IR attributes¶
Properties that are implemented using the following C++ attributes are represented in LLVM IR as IR attributes:
[[__sycl_detail__::add_ir_attributes_global_variable()]]
[[__sycl_detail__::add_ir_attributes_kernel_parameter()]]
[[__sycl_detail__::add_ir_attributes_function()]]
When the front-end consumes these C++ attributes and produces IR, each property
name becomes an IR attribute name and each property value becomes the
attribute’s value. Because the attribute values must be strings, the front-end
converts each property value to a string. Integer and floating point values
are converted with the same format as std::to_string()
would produce.
Boolean values are converted to either "true"
or "false"
. Enumeration
values are first converted to an integer and then converted to a string with
the same format as std::to_string()
. The nullptr
value is converted to an
empty string (""
).
TODO: Should we allow property values that are type names? If so, I suppose they would be converted to a string representation of the mangled name?
TODO: Should we allow property values of other (non-fundamental) types? If
we allow this, we need to teach the front-end how to convert each type to a
string, which means the front-end needs to be changed each time we add a
property with a new non-fundamental type. This seems undesirable. However, if
we do not allow non-fundamental types, how do we represent properties like
work_group_size
, whose value is a 3-tuple of integers? Maybe we could just
allow std::tuple
, where the type of each element is one of the fundamental
types listed above.
IR representation via @llvm.ptr.annotation
¶
Properties that are implemented using
[[__sycl_detail__::add_ir_annotations_member()]]
, are represented in LLVM IR
as the fifth parameter to the @llvm.ptr.annotation
intrinsic function. This
parameter is a pointer to a global variable with fields corresponding to the
names and values of the properties in the following sequence:
Name of the first property
Value of the first property
Name of the second property
Value of the second property
Etc.
Every field in the global variable pointed to by this parameter are string
literals in seperate global variables. Property values are converted to strings
in the same way as described above, except that the nullptr
value and the
empty string (""
) is represented as null
in the global variable field.
Filtering properties¶
It is sometimes necessary to filter out certain properties so that only a subset of the properties in a list are represented in IR. There are two scenarios when this is useful.
In some cases, a property is used only in the header file itself, and there is no need to represent that property in LLVM IR. In order to avoid cluttering the IR with unneeded information, these properties can be “filtered out”, so that the front-end does not generate an IR representation.
Another case is when a class wants to represent some properties one way in the
IR while representing other properties in another way. For example, a future
version of accessor
might pass some properties to
[[__sycl_detail__::add_ir_attributes_kernel_parameter()]]
while passing other
properties to [[__sycl_detail__::add_ir_annotations_member()]]
. Again, the
header wants some way to “filter” the properties, such that some properties are
interpreted as “kernel parameter attributes” while other are interpreted as
“member annotations”.
To handle these cases, each of the following C++ attributes takes an optional first parameter that is a brace-enclosed list of property names:
[[__sycl_detail__::add_ir_attributes_global_variable()]]
[[__sycl_detail__::add_ir_attributes_kernel_parameter()]]
[[__sycl_detail__::add_ir_attributes_function()]]
[[__sycl_detail__::add_ir_annotations_member()]]
Since this brace-enclosed list acts somewhat like an initializer list, the
header must include <initializer_list>
prior to passing this optional first
parameter.
The front-end treats this list as a “pass list”, ignoring any property whose
name is not in the list. To illustrate, consider the following example where
accessor
treats some properties as “kernel parameter attributes” and others
as “member annotations”:
template <typename dataT,
int dimensions,
access::mode accessmode,
access::target accessTarget,
access::placeholder isPlaceholder,
typename ...Props>
class __attribute__((sycl_special_class)) accessor<dataT,
dimensions,
accessmode,
accessTarget,
isPlaceholder,
properties<Props...>> {
T *ptr
#ifdef __SYCL_DEVICE_ONLY__
[[__sycl_detail__::add_ir_annotations_member(
// The properties in this list are "member annotations".
{"sycl-bar"},
Props::meta_name..., Props::meta_value...
)]]
#endif
;
#ifdef __SYCL_DEVICE_ONLY__
void __init(
[[__sycl_detail__::add_ir_attributes_kernel_parameter(
// The properties in this list are "kernel parameter attributes".
{"sycl-no-alias", "sycl-foo"},
Props::meta_name..., Props::meta_value...
)]]
dataT *_ptr) {
ptr = _ptr;
}
#endif
}
Representing properties in SPIR-V¶
There is no mechanical process which converts all LLVM IR attributes to
SPIR-V. This is because we do not need all properties to be expressed in
SPIR-V and because there is no consistent way to represent properties in
SPIR-V. Therefore, the sycl-post-link
tool decides on a case-by-case basis
which properties are translated into SPIR-V and which representation to use.
We use the SPIR-V LLVM Translator to translate from LLVM IR to SPIR-V,
and that tool defines idiomatic LLVM IR representations that correspond
to various SPIR-V instructions. Therefore, the sycl-post-link
tool can
translate a property into a specific SPIR-V instruction by generating the
corresponding idiomatic LLVM IR. The following sections describe some common
cases.
Property on a kernel function¶
When a property on a kernel function needs to be represented in SPIR-V, we
generally translate the property into a SPIR-V OpExecutionMode instruction.
The SPIR-V LLVM Translator has an existing way to generate this instruction
when the LLVM IR contains the named metadata !spirv.ExecutionMode
as
illustrated below:
!spirv.ExecutionMode = !{!0, !1} ; Each operand in this metadata
; represents one OpExectionMode
; instruction that will be generated.
!0 = !{void ()* @bar, i32 42} ; The first operand identifies a kernel
; function. The second operand is the
; integer value of a SPIR-V execution
; mode.
!1 = !{void ()* @bar, i32 43, i32 3} ; Any additional operands in the metadata
; correspond to "extra operands" to the
; OpExecutionMode instruction. These
; operands must be integer literals.
Property on a kernel parameter¶
When a property on a kernel parameter needs to be represented in SPIR-V, we generally translate the property into a SPIR-V OpDecorate instruction for the corresponding OpFunctionParameter of the kernel function. Since the SPIR-V LLVM Translator does not have an existing way to generate these decorations, we propose the following mechanism.
An LLVM IR function definition may optionally have a metadata kind of
!spirv.ParameterDecorations
. If it does, that metadata node must have one
operand for each of the function’s parameters. Each of those operands is
another metadata node that describes the decorations for that parameter. To
illustrate:
define spir_kernel void @MyKernel(%arg1, %arg2) !spirv.ParameterDecorations !0 {
}
!0 = !{!1, !2} ; Each operand in this metadata represents the
; decorations for one kernel parameter.
!1 = !{!3, !4} ; The first kernel parameter has two decorations.
!2 = !{} ; The second kernel parameter has no decorations.
!3 = !{i32 7742} ; This is the integer value of the first decoration.
!4 = !{i32 7743, i32 10} ; The first operand is the integer value of the
; second decoration. Additional operands are
; "extra operands" to the decoration. These
; operands may be either integer literals or string
; literals.
Property on a global variable¶
When a property on a global variable needs to be represented in SPIR-V, we generally translate the property into a SPIR-V OpDecorate instruction for the corresponding module scope (global) OpVariable. Again, there is no existing mechanism to do this in the SPIR-V LLVM Translator, so we propose the following mechanism.
An LLVM IR global variable definition may optionally have a metadata kind of
!spirv.Decorations
. If it does, that metadata node has one operand for each
of the global variable’s decorations. To illustrate:
@MyVariable = global %MyClass !spirv.Decorations !0
!0 = !{!1, !2} ; Each operand in this metadata represents one
; decoration on the variable.
!1 = !{i32 7744} ; This is the integer value of the first decoration.
!2 = !{i32 7745, i32 20} ; The first operand is the integer value of the
; second decoration. Additional operands are
; "extra operands" to the decoration. These
; operands may be either integer literals or string
; literals.
Property on a structure member of a non-global variable¶
As we noted earlier, a property on a structure member variable is represented
in LLVM IR as a call to the intrinsic function @llvm.ptr.annotation
, where
the annotation string is "sycl-properties"
and the properties are represented
as metadata in the fifth parameter to @llvm.ptr.annotation
. In order to
understand how these SYCL properties are translated into SPIR-V, it’s useful to
review how a normal (i.e. non-SYCL) call to @llvm.ptr.annotation
is
translated.
The existing behavior of the SPIR-V LLVM Translator is to translate this call into one (or both) of the following:
An OpDecorate instruction that decorates the intermediate pointer value that is returned by the intrinsic (i.e. the pointer to the member variable).
An OpMemberDecorate instruction that decorates the member variable itself.
In both cases, the decoration is a single UserSemantic decoration where the string literal is the same as the string literal in the LLVM annotation.
An exception to this is for a selection of FPGA-related decorations. If these are supported during translation from LLVM IR to SPIR-V the corresponding decorations will be generated, and otherwise it will fall back to creating a single UserSemantic decoration. In general these decorations occur in the annotation string as a series of {X} and {X:Y} where X is a reserved name and Y is one or more words and numbers separated by a comma (,) or a colon (:), depending on the decoration.
As such we propose an extension to this functionality with the following changes:
To bring it in line with the format of the metadata decorations, the parsing of these decorations should allow the use of SPIR-V decoration identifiers rather than reserved names. With this there need not be any agreement between the translator and LLVM IR producer, as the identifiers are specified by the SPIR-V specification.
For decorations parsed with decoration identifiers, only the comma delimiter is valid for separating decoration values.
In addition to words and numbers, string literals enclosed by quotation marks are allowed as decoration values. No escapes are planned for this, so all symbols between starting quotation mark and ending quotation mark are considered part of the string literal.
When a member variable property needs to be represented in SPIR-V, the
sycl-post-link
tool converts the @llvm.ptr.annotation
intrinsic call
produced by [[__sycl_detail__::add_ir_annotations_member()]]
into another
@llvm.ptr.annotation
intrinsic call using this format. For example:
; Contains decorations:
; * 7744 with no value.
; * 7745 with 20 and "str 1" as the values.
@.str = private unnamed_addr constant [24 x i8] c"{7744}{7745:20,\22str 1\22}\00",
section "llvm.metadata"
@.str.1 = private unnamed_addr constant [9 x i8] c"file.cpp\00",
section "llvm.metadata"
define void @foo(i32* %ptr) {
...
; %0 points to the annotated member field.
%2 = call i8* @llvm.ptr.annotation.p0i8(i8* nonnull %0,
i8* getelementptr inbounds ([16 x i8], [16 x i8]* @.str, i64 0, i64 0),
i8* getelementptr inbounds ([9 x i8], [9 x i8]* @.str.1, i64 0, i64 0),
i32 3,
i8* null)
...
}
NOTE: To allow backwards compatibility with the old format, reverse translation of decorations will produce a decorations in the annotation string following the old format if the decoration had a reserved name.