Skip to content

Latest commit

 

History

History
1399 lines (1077 loc) · 47.7 KB

File metadata and controls

1399 lines (1077 loc) · 47.7 KB

sycl_ext_oneapi_kernel_compiler

Notice

Copyright © 2023 Intel Corporation. All rights reserved.

Khronos® is a registered trademark and SYCL™ and SPIR™ are trademarks of The Khronos Group Inc. OpenCL™ is a trademark of Apple Inc. used by permission by Khronos.

Contact

To report problems with this extension, please open a new issue at:

Dependencies

This extension is written against the SYCL 2020 revision 8 specification. All references below to the "core SYCL specification" or to section numbers in the SYCL specification refer to that revision.

This extension also depends on the following other SYCL extensions:

Status

This is an experimental extension specification, intended to provide early access to features and gather community feedback. Interfaces defined in this specification are implemented in DPC++, but they are not finalized and may change incompatibly in future versions of DPC++ without prior notice. Shipping software products should not rely on APIs defined in this specification.

Overview

This extension adds APIs that allow the application to dynamically generate the source code for a kernel, which it can then compile and enqueue to a device. This extension provides support for kernels written in SYCL according to the "free function kernel" syntax defined in sycl_ext_oneapi_free_function_kernels. However, other extensions could provide support for writing kernels in other languages.

The new APIs added by this extension are an expansion of the existing kernel_bundle capabilities. Thus, an application can create a kernel bundle from a source string and then build the bundle into "executable" bundle state. Once the application obtains a kernel object, it can use existing APIs from the core SYCL specification to set the value of kernel arguments and enqueue the kernel to a device.

Specification

Feature test macro

This extension provides a feature-test macro as described in the core SYCL specification. An implementation supporting this extension must predefine the macro SYCL_EXT_ONEAPI_KERNEL_COMPILER to one of the values defined in the table below. Applications can test for the existence of this macro to determine if the implementation supports this feature, or applications can test the macro’s value to determine which of the extension’s features the implementation supports.

Value Description

1

The APIs of this experimental extension are not versioned, so the feature-test macro always has this value.

New kernel bundle state

This extension adds the ext_oneapi_source enumerator to sycl::bundle_state to identify a kernel bundle that is represented as a source code string.

namespace sycl {

enum class bundle_state : /*unspecified*/ {
  // ...
  ext_oneapi_source
};

} // namespace sycl

New enumerator of kernel source languages

This extension adds the source_language enumeration, which identifies possible languages for a kernel bundle that is in ext_oneapi_source state:

namespace sycl::ext::oneapi::experimental {

enum class source_language : /*unspecified*/ {
  sycl
};

} // namespace sycl::ext::oneapi::experimental

The only enumerator defined by this extension is sycl, which indicates that the kernel is written in SYCL using the "free function kernel" syntax defined in sycl_ext_oneapi_free_function_kernels. Other extensions may provide other enumerators that correspond to other languages.

New member functions for the device class

This extension adds the following new member functions to the device class:

class device {

bool ext_oneapi_can_build(ext::oneapi::experimental::source_language lang);

};

Returns: The value true only if the device supports the ext::oneapi::experimental::build function on kernel bundles written in the source language lang.

class device {

bool ext_oneapi_can_compile(ext::oneapi::experimental::source_language lang);

};

Returns: The value true only if the device supports the ext::oneapi::experimental::compile function on kernel bundles written in the source language lang.

New free functions to create and build kernel bundles

This extension adds the following new free functions to create and build a kernel bundle in ext_oneapi_source state.

namespace sycl::ext::oneapi::experimental {

template<typename PropertyListT = empty_properties_t>                             (1)
kernel_bundle<bundle_state::ext_oneapi_source> create_kernel_bundle_from_source(
  const context& ctxt,
  source_language lang,
  const std::string& source,
  PropertyListT props = {})

template<typename PropertyListT = empty_properties_t>                             (2)
kernel_bundle<bundle_state::ext_oneapi_source> create_kernel_bundle_from_source(
  const context& ctxt,
  source_language lang,
  const std::vector<std::byte>& bytes,
  PropertyListT props = {})

} // namespace sycl::ext::oneapi::experimental

Constraints: Available only when PropertyListT is an instance of sycl::ext::oneapi::experimental::properties which contains no properties other than those listed below in the section "New properties for the create_kernel_bundle_from_source function".

Effects: Creates a new kernel bundle that represents a kernel written in the source language lang, where the source code is contained either by source (if the source language is a text format) or by bytes (if the source language is binary format). The bundle is associated with the context ctxt, and kernels from this bundle may only be submitted to a queue that shares the same context. The bundle’s set of associated devices is the set of devices contained in ctxt.

Each source language lang specifies whether the language is text format or binary format, and the application must use the overload that corresponds to that format. Applications must use overload (1) when the source language is text format and must use overload (2) when the source language is binary format. The sycl language is text format, so application must use overload (1) when creating a kernel bundle from this language.

Returns: The newly created kernel bundle, which has ext_oneapi_source state.

Throws:

  • An exception with the errc::invalid error code if the source language lang does not support one of the properties in PropertyListT.

  • Overload (1) throws an exception with the errc::invalid error code if the source language lang is binary format.

  • Overload (2) throws an exception with the errc::invalid error code if the source language lang is text format.

[Note: Calling this function does not attempt to compile the source code. As a result, syntax errors in source or bytes are not diagnosed by this function.

This function succeeds even if some devices in ctxt do not support the source language lang. However, the build and compile functions will fail if any of its devices return false for ext_oneapi_can_build(lang) and ext_oneapi_can_compile(lang) respectively. Therefore, applications should take care to omit devices that do not support lang for the functions they intend on calling. — end note]

namespace sycl::ext::oneapi::experimental {

template<typename PropertyListT = empty_properties_t>                 (1)
kernel_bundle<bundle_state::executable> build(
  const kernel_bundle<bundle_state::ext_oneapi_source>& sourceBundle,
  const std::vector<device> &devs,
  PropertyListT props = {})

template<typename PropertyListT = empty_properties_t>                 (2)
kernel_bundle<bundle_state::executable> build(
  const kernel_bundle<bundle_state::ext_oneapi_source>& sourceBundle,
  PropertyListT props = {})

} // namespace sycl::ext::oneapi::experimental

Constraints: Available only when PropertyListT is an instance of sycl::ext::oneapi::experimental::properties which contains no properties other than those listed below in the section "New properties for the build and compile functions".

Effects (1): The source code from sourceBundle is translated into one or more device images of state bundle_state::executable, and a new kernel bundle is created to contain these device images. The new bundle represents all of the kernels in sourceBundle that are compatible with at least one of the devices in devs. Any remaining kernels (those that are not compatible with any of the devices in devs) are not represented in the new kernel bundle.

The new bundle has the same associated context as sourceBundle, and the new bundle’s set of associated devices is devs (with duplicate devices removed).

Effects (2): Equivalent to build(sourceBundle, sourceBundle.get_devices(), props).

Returns: The newly created kernel bundle, which has executable state.

Throws:

  • An exception with the errc::invalid error code if any of the devices in devs return false for ext_oneapi_can_build with the source language of sourceBundle.

  • An exception with the errc::invalid error code if any of the devices in devs is not contained by the context associated with sourceBundle.

  • An exception with the errc::invalid error code if the source language lang does not support one of the properties in PropertyListT or if props contains a build_options property that contains an option that is not supported when building lang.

  • An exception with the errc::build error code if the compilation or linking operations fail. In this case, the exception what string provides a full build log, including descriptions of any errors, warning messages, and other diagnostics. This string is intended for human consumption, and the format may not be stable across implementations of this extension.

[Note: An uncaught errc::build exception may result in some or all of the source code used to create the kernel bundle being printed to the terminal. In situations where this is undesirable, developers must ensure that the exception is caught and handled appropriately. — end note]

namespace sycl::ext::oneapi::experimental {

template<typename PropertyListT = empty_properties_t>                 (1)
kernel_bundle<bundle_state::object> compile(
    const kernel_bundle<bundle_state::ext_oneapi_source>& sourceBundle,
    const std::vector<device>& devs, PropertyListT props={})

template<typename PropertyListT = empty_properties_t>                 (2)
kernel_bundle<bundle_state::object> compile(
  const kernel_bundle<bundle_state::ext_oneapi_source>& sourceBundle,
  PropertyListT props = {})

} // namespace sycl::ext::oneapi::experimental

Constraints: Available only when PropertyListT is an instance of sycl::ext::oneapi::experimental::properties which contains no properties other than those listed below in the section "New properties for the build and compile functions".

Effects (1): The source code from sourceBundle is translated into one or more device images of state bundle_state::object, and a new kernel bundle is created to contain these device images. The new bundle represents all of the kernels in sourceBundle that are compatible with at least one of the devices in devs. Any remaining kernels (those that are not compatible with any of the devices in devs) are not represented in the new kernel bundle.

The new bundle has the same associated context as sourceBundle, and the new bundle’s set of associated devices is devs (with duplicate devices removed).

Effects (2): Equivalent to compile(sourceBundle, sourceBundle.get_devices(), props).

Returns: The newly created kernel bundle, which has object state.

Throws:

  • An exception with the errc::invalid error code if any of the devices in devs return false for ext_oneapi_can_compile with the source language of sourceBundle.

  • An exception with the errc::invalid error code if any of the devices in devs is not contained by the context associated with sourceBundle.

  • An exception with the errc::invalid error code if the source language lang does not support one of the properties in PropertyListT or if props contains a build_options property that contains an option that is not supported when compiling lang.

  • An exception with the errc::build error code if the compilation operation fails. In this case, the exception what string provides a full build log, including descriptions of any errors, warning messages, and other diagnostics. This string is intended for human consumption, and the format may not be stable across implementations of this extension.

[Note: An uncaught errc::build exception may result in some or all of the source code used to create the kernel bundle being printed to the terminal. In situations where this is undesirable, developers must ensure that the exception is caught and handled appropriately. — end note]

New properties for the create_kernel_bundle_from_source function

This extension adds the following properties, which can be used in conjunction with the create_kernel_bundle_from_source function that is defined above:

namespace sycl::ext::oneapi::experimental {

struct include_files {
  include_files();                                                     (1)
  include_files(const std::string &name, const std::string &content);  (2)
  void add(const std::string &name, const std::string &content);       (3)
};
using include_files_key = include_files;

template<>
struct is_property_key<include_files_key> : std::true_type {};

} // namespace sycl::ext::oneapi::experimental

This property provides the name and content of include files that can be referenced from the source code in the source parameter to create_kernel_bundle_from_source. The property conceptually contains a collection of (Name, Content) pairs, where both Name and Content are strings. The Name is the name of an include file and the Content is the content of that include file.

When the source language is source_language::sycl, the source code can have #include statements where the name and content of the include file is defined by this property. For example, if the source code has #include "foo/bar.h", the compilation process will look at the include_files property to see if there is an entry whose Name is foo/bar.h. If such an entry is found, the compiler uses the associated Content as the content of the include file.

[Note: This property is only required if an #include statement references a file that is not already implicitly available. For more information about implicitly available headers, see the section "Including files when the language is sycl". — end note]

Effects (1): Creates a new include_files property with no (Name, Content) pairs.

Effects (2): Creates a new include_files property with a single (Name, Content) pair.

Effects (3): Adds a (Name, Content) pair to the property.

Throws (3):

  • An exception with the errc::invalid error code if there is already an entry with name in this property.

New properties for the build and compile functions

This extension adds the following properties, which can be used in conjunction with the build and compile function that is defined above:

namespace sycl::ext::oneapi::experimental {

struct build_options {
  build_options();                                      (1)
  build_options(const std::string &opt);                (2)
  build_options(const std::vector<std::string> &opts);  (3)
  void add(const std::string &opt);                     (4)
};
using build_options_key = build_options;

} // namespace sycl::ext::oneapi::experimental

This property provides build options that may affect the compilation or linking of the kernel, where each build option is a string. All source languages support the build_options property, but each source language defines the specific options that it supports. The source_language::sycl language does not define any standard build options, but an implementation may support implementation-defined options.

Effects (1): Constructs a build_options property with no build options.

Effects (2): Constructs a build_options property with a single build option.

Effects (3): Constructs a build_options property from a vector of build options.

Effects (4): Adds opt to the end of the property’s list of build options.

namespace sycl::ext::oneapi::experimental {

struct save_log {
  save_log(std::string *to);  (1)
};
using save_log_key = save_log;

} // namespace sycl::ext::oneapi::experimental

This property allows the caller to request a log to be created with additional information about the compilation and linking operations. Use of this property is not required in order to get information about a failed build. When a build fails, an exception is thrown and the exception’s what string provides a description of the error.

Instead, the save_log property provides information about a build operation that succeeds. This might include warning messages or other diagnostics. All source languages support the save_log property, but each source language defines the specific information that is provided in the log. The source_language::sycl language does not define any specific information that is provided in the log, so implementations are free to provide any information they choose here. In general, the log information is intended for human consumption, and the format may not be stable across implementations of this extension.

Effects (1): Constructs a save_log property with a pointer to a std::string. If the to pointer is not null, when the build function completes successfully, the string pointed at by to will contain the log.

Remarks (1): When to is not null, the string object it points to must remain valid for all calls to build taking this save_log property.

namespace sycl::ext::oneapi::experimental {

struct registered_names {
  registered_names();                                       (1)
  registered_names(const std::string &name);                (2)
  registered_names(const std::vector<std::string> &names);  (3)
  void add(const std::string &name);                        (4)
};
using registered_names_key = registered_names;

template<>
struct is_property_key<registered_names_key> : std::true_type {};

} // namespace sycl::ext::oneapi::experimental

This property is useful when the source language represents names differently in the source code and the generated code. For example, C++ function names and the names of static variables at global scope are "mangled" in an implementation-defined way in the generated code. The precise meaning of this property is defined by each source language, but in general it allows the application to supply a list of names as they appear in the source code. The application can then get the corresponding raw (i.e. mangled) names after the code is compiled. See the section below "Obtaining a kernel when the language is sycl" for a description of how this property is used with the source_language::sycl language.

Effects (1): Creates a new registered_names property with no registered names.

Effects (2): Creates a new registered_names property with a single registered name.

Effects (3): Creates a new registered_names property from a vector of names.

Effects (4): Adds name to the property’s list of registered names.

Preconditions (2-4): Each source language defines its own requirements for the registered names. For the language source_language::sycl, each name must be a C++ expression for a pointer to a kernel function as defined below under "Obtaining a kernel when the language is sycl".

[Note: It is not an error to have duplicate names in a registered_names property, but the duplicates have no effect. — end note]

New free function for linking

This extension adds the following new free functions to link kernel bundles in object state. This differs from the regular sycl::link function the additional property list argument.

namespace sycl::ext::oneapi::experimental {

template<typename PropertyListT = empty_properties_t>
kernel_bundle<bundle_state::executable>
link(const std::vector<kernel_bundle<bundle_state::object>>& objectBundles,
     const std::vector<device>& devs, PropertyListT props = {});

} // namespace sycl::ext::oneapi::experimental

Constraints: Available only when PropertyListT is an instance of sycl::ext::oneapi::experimental::properties which contains no properties other than those listed below in the section "New properties for the link function".

Effects: Duplicate device images from objectBundles are eliminated as though they were joined via join(), then the remaining device images are translated into one or more new device images of state bundle_state::executable, and a new kernel bundle is created to contain these new device images. The new bundle represents all of the kernels in objectBundles that are compatible with at least one of the devices in devs. Any remaining kernels (those that are not compatible with any of the devices in devs) are not linked and not represented in the new bundle.

The new bundle has the same associated context as those in objectBundles, and the new bundle’s set of associated devices is devs (with duplicate devices removed).

Returns: The new kernel bundle.

Throws:

  • An exception with the errc::invalid error code if the bundles in objectBundles do not all have the same associated context.

  • An exception with the errc::invalid error code if any of the devices in devs are not in the set of associated devices for any of the bundles in objectBundles (as defined by kernel_bundle::get_devices()) or if the devs vector is empty.

  • An exception with the errc::build error code if the online link operation fails.

namespace sycl::ext::oneapi::experimental {

template<typename PropertyListT = empty_properties_t>                        (1)
kernel_bundle<bundle_state::executable>
link(const kernel_bundle<bundle_state::object>& objectBundle,
     const std::vector<device>& devs, PropertyListT props = {});

template<typename PropertyListT = empty_properties_t>                        (2)
kernel_bundle<bundle_state::executable>
link(const std::vector<kernel_bundle<bundle_state::object>>& objectBundles,
     PropertyListT props = {});

template<typename PropertyListT = empty_properties_t>                        (3)
kernel_bundle<bundle_state::executable>
link(const kernel_bundle<bundle_state::object>& objectBundle,
     PropertyListT props = {});

} // namespace sycl::ext::oneapi::experimental

Effects (1): Equivalent to link({objectBundle}, devs, props).

Effects (2): Equivalent to link(objectBundles, devs, props), where devs is the intersection of associated devices in common for all bundles in objectBundles.

Effects (3): Equivalent to link({objectBundle}, objectBundle.get_devices(), props).

This extension adds the following properties, which can be used in conjunction with the link function that is defined above:

namespace sycl::ext::oneapi::experimental {

struct fast_link {
  fast_link(bool do_fast_link = true);  (1)

  bool value;
};
using fast_link_key = fast_link;

template<> struct is_property_key<fast_link_key> : std::true_type {};

} // namespace sycl::ext::oneapi::experimental

This property instructs the link operation to do "fast linking". Enabling this instructs the implementation to use device binary images that have been pre-compiled.

Fast linking offers potentially faster link times, at the potential cost of slower kernel execution time. The latter effect would mainly be due to link-time optimizations the device compiler could have done during regular linking, that cannot be done when doing fast linking.

For example, the binaries produced may contain ahead-of-time compiled binary images together with just-in-time compiled binary images, with the kernels and exported functions potentially overlapping. When fast-linking is enabled, the implementation will try to use the ahead-of-time compiled binary images over their just-in-time compiled counterparts.

Effects (1): Creates a new fast_link property with a boolean value indicating whether the link operation should do fast-linking.

New constraint for kernel bundle member functions

This extension adds the following constraint to some of the kernel_bundle member functions from the core SYCL specification:

Constraints: This function is not available when State is bundle_state::ext_oneapi_source.

This new constraint applies to the following member functions:

  • empty;

  • All overloads and function templates of has_kernel;

  • get_kernel_ids;

  • contains_specialization_constants;

  • native_specialization_constant;

  • has_specialization_constant;

  • get_specialization_constant;

  • begin; and

  • end.

As a result, the only kernel_bundle member functions from the core SYCL specification that are available for bundles in ext_oneapi_source state are get_backend, get_context, and get_devices.

New constraint for join function

This extension adds the following constraint to the join functions from the core SYCL specification:

Constraints: This function is not available when State is bundle_state::ext_oneapi_source.

Interaction with existing kernel bundle member functions

Kernels created from online compilation of source code do not have any associated kernel_id. Therefore, the function kernel_bundle::get_kernel_ids returns an empty vector of kernel_id objects if the kernel bundle was created from a bundle of state bundle_state::ext_oneapi_source.

New kernel bundle member functions

This extensions adds the following new kernel_bundle member functions:

namespace sycl {

template <bundle_state State>
class kernel_bundle {
  // ...

  bool ext_oneapi_has_kernel(const std::string &name);
  kernel ext_oneapi_get_kernel(const std::string &name);
  std::string ext_oneapi_get_raw_kernel_name(const std::string &name);
};

} // namespace sycl
bool ext_oneapi_has_kernel(const std::string &name)

Constraints: This function is not available when State is bundle_state::ext_oneapi_source.

Returns: The value true only if the kernel bundle was created from a bundle of state bundle_state::ext_oneapi_source and if it defines a kernel whose name is name. The extension specification for each source language tells how the name string is correlated to kernels defined in that source language.

kernel ext_oneapi_get_kernel(const std::string &name)

Constraints: This function is available only when State is bundle_state::executable.

Returns: A kernel object representing the kernel in this bundle whose name is name.

Throws:

  • An exception with the errc::invalid error code if ext_oneapi_has_kernel(name) returns false.

std::string ext_oneapi_get_raw_kernel_name(const std::string &name)

Constraints: This function is not available when State is bundle_state::ext_oneapi_source.

Returns: If the kernel bundle was created from a bundle of state bundle_state::ext_oneapi_source and name was registered via registered_names, returns the compiler-generated (e.g. mangled) name for this kernel function. If the kernel bundle was created from a bundle of state bundle_state::ext_oneapi_source and name is the same as a compiler-generated name for a kernel defined in that bundle, that same name is returned.

Throws:

  • An exception with the errc::invalid error code if ext_oneapi_has_kernel(name) returns false.

Including files when the language is sycl

When the source language is source_language::sycl, the compiler searches multiple locations to find files referenced by #include statements. Any include files defined via the include_files property are searched first, followed by the directories below, in order:

  1. The current working directory.

  2. Any directory added explicitly to the search list via the build_options property.

Finally, the compiler searches a set of implicitly available header files, which do not need to be specified via the include_files property:

  • <sycl/sycl.hpp>;

  • The C++ standard library headers;

  • The SYCL backend headers "sycl/backend/<backend_name>.hpp" for any backends that the implementation supports; and

  • Any SYCL extension headers in "sycl/ext" for extensions that the implementation supports.

Obtaining a kernel when the language is sycl

When the kernel is defined in the language source_language::sycl, the host code may query for the kernel or obtain the kernel object using either the kernel’s name as it is generated by the compiler (i.e. the C++ mangled name) or by using the registered_names property.

Using the compiler-generated name

If the kernel is declared as extern "C", the compiler generates the kernel name exactly as it appears in the source code (i.e. there is no name mangling). Therefore, it is easy to query for the kernel by using the compiler-generated name. For example, if the kernel is defined like this in the source code string:

std::string source = R"""(
  #include <sycl/sycl.hpp>
  namespace syclexp = sycl::ext::oneapi::experimental;

  extern "C"
  SYCL_EXT_ONEAPI_FUNCTION_PROPERTY((syclexp::nd_range_kernel<1>))
  void foo(int *in, int *out) {/*...*/}
)""";

Then the application’s host code can query for the kernel like this:

sycl::kernel_bundle<sycl::bundle_state::executable> kb = /*...*/;
sycl::kernel k = kb.ext_oneapi_get_kernel("foo");

Using the registered_names property

When the kernel is not declared as extern "C", the compiler generates a mangled name, so it is more convenient to use the registered_names property. Each string in the property must be the C++ expression for a pointer to a kernel function. These expression strings are conceptually compiled at the bottom of source code. To illustrate, consider source code that defines a kernel like this:

std::string source = R"""(
  #include <sycl/sycl.hpp>
  namespace syclexp = sycl::ext::oneapi::experimental;

  namespace mykernels {

  SYCL_EXT_ONEAPI_FUNCTION_PROPERTY((syclexp::nd_range_kernel<1>))
  void bar(int *in, int *out) {/*...*/}

  } // namespace mykernels
)""";

The host code can compile this and get the kernel’s kernel object like so:

sycl::kernel_bundle<sycl::bundle_state::ext_oneapi_source> kb_src = /*...*/;

sycl::kernel_bundle<sycl::bundle_state::executable> kb = syclexp::build(kb_src,
  syclexp::properties{syclexp::registered_names{"mykernels::bar"}});

sycl::kernel k = kb.ext_oneapi_get_kernel("mykernels::bar");

The C++ expression "mykernels::bar" computes the address of the kernel function bar. The host code then passes the same string ("mykernels::bar") to ext_oneapi_get_kernel in order to get the kernel object. The string must have exactly the same content as the string that was used to construct the property, without even any whitespace differences.

The application can also obtain the compiler-generated (i.e. mangled) name for the kernel by calling ext_oneapi_get_raw_kernel_name like this:

sycl::kernel_bundle<sycl::bundle_state::ext_oneapi_source> kb_src = /*...*/;

sycl::kernel_bundle<sycl::bundle_state::executable> kb = syclexp::build(kb_src,
  syclexp::properties{syclexp::registered_names{"mykernels::bar"}});

std::string mangled_name = kb.ext_oneapi_get_raw_kernel_name("mykernels::bar");

Again, the string passed to ext_oneapi_get_raw_kernel_name must have exactly the same content as the string that was used to construct the registered_names property. The application may also pass this compiler-generated (i.e. mangled) name to ext_oneapi_get_kernel in order to get the kernel object.

Instantiating templated kernel functions

The registered_names property can also be used to instantiate a kernel that is defined as a function template. For example, consider source code that defines a kernel function template like this:

std::string source = R"""(
  #include <sycl/sycl.hpp>
  namespace syclexp = sycl::ext::oneapi::experimental;

  template<typename T>
  SYCL_EXT_ONEAPI_FUNCTION_PROPERTY((syclexp::nd_range_kernel<1>))
  void bartmpl(T *in, T *out) {/*...*/}
)""";

The application can use the registered_names property to instantiate the template for specific template arguments. For example, this host code instantiates the template twice and gets a kernel object for each instantiation:

sycl::kernel_bundle<sycl::bundle_state::ext_oneapi_source> kb_src = /*...*/;

sycl::kernel_bundle<sycl::bundle_state::executable> kb = syclexp::build(kb_src,
  syclexp::properties{syclexp::registered_names{{"bartmpl<float>", "bartmpl<int>"}});

sycl::kernel k_float = kb.ext_oneapi_get_kernel("bartmpl<float>");
sycl::kernel k_int = kb.ext_oneapi_get_kernel("bartmpl<int>");

Examples

Simple example

The following example demonstrates how a SYCL application can define a kernel as a string and then compile and launch it.

#include <sycl/sycl.hpp>
namespace syclexp = sycl::ext::oneapi::experimental;

static constexpr size_t NUM = 1024;
static constexpr size_t WGSIZE = 16;

int main() {
  sycl::queue q;

  // The source code for a kernel, defined as a SYCL "free function kernel".
  std::string source = R"""(
    #include <sycl/sycl.hpp>
    namespace syclext = sycl::ext::oneapi;
    namespace syclexp = sycl::ext::oneapi::experimental;

    extern "C"
    SYCL_EXT_ONEAPI_FUNCTION_PROPERTY((syclexp::nd_range_kernel<1>))
    void iota(float start, float *ptr) {
      size_t id = syclext::this_work_item::get_nd_item<1>().get_global_linear_id();
      ptr[id] = start + static_cast<float>(id);
    }
  )""";

  // Create a kernel bundle in "source" state.
  sycl::kernel_bundle<sycl::bundle_state::ext_oneapi_source> kb_src =
    syclexp::create_kernel_bundle_from_source(
      q.get_context(),
      syclexp::source_language::sycl,
      source);

  // Compile the kernel.  There is no need to use the "registered_names"
  // property because the kernel is declared extern "C".
  sycl::kernel_bundle<sycl::bundle_state::executable> kb_exe =
    syclexp::build(kb_src);

  // Get the kernel via its compiler-generated name.
  sycl::kernel iota = kb_exe.ext_oneapi_get_kernel("iota");

  float *ptr = sycl::malloc_shared<float>(NUM, q);
  q.submit([&](sycl::handler &cgh) {
    // Set the values of the kernel arguments.
    cgh.set_args(3.14f, ptr);

    // Launch the kernel according to its type, in this case an nd-range kernel.
    sycl::nd_range ndr{{NUM}, {WGSIZE}};
    cgh.parallel_for(ndr, iota);
  }).wait();
  sycl::free(ptr, q);
}

Disambiguating overloaded kernel functions

This example demonstrates how to use the registered_names property to disambiguate a kernel function that has several overloads.

#include <sycl/sycl.hpp>
namespace syclexp = sycl::ext::oneapi::experimental;

static constexpr size_t NUM = 1024;
static constexpr size_t WGSIZE = 16;

int main() {
  sycl::queue q;

  // The source code for two kernels defined as overloaded functions.
  std::string source = R"""(
    #include <sycl/sycl.hpp>
    namespace syclext = sycl::ext::oneapi;
    namespace syclexp = sycl::ext::oneapi::experimental;

    SYCL_EXT_ONEAPI_FUNCTION_PROPERTY((syclexp::nd_range_kernel<1>))
    void iota(float start, float *ptr) {
      size_t id = syclext::this_work_item::get_nd_item<1>().get_global_linear_id();
      ptr[id] = start + static_cast<float>(id);
    }

    SYCL_EXT_ONEAPI_FUNCTION_PROPERTY((syclexp::nd_range_kernel<1>))
    void iota(int start, int *ptr) {
      size_t id = syclext::this_work_item::get_nd_item<1>().get_global_linear_id();
      ptr[id] = start + static_cast<int>(id);
    }
  )""";

  // Create a kernel bundle in "source" state.
  sycl::kernel_bundle<sycl::bundle_state::ext_oneapi_source> kb_src =
    syclexp::create_kernel_bundle_from_source(
      q.get_context(),
      syclexp::source_language::sycl,
      source);

  // Compile the kernel.  Because there are two overloads of "iota", we need to
  // use a C++ cast to disambiguate between them.  Here, we are selecting the
  // "int" overload.
  std::string iota_name{"(void(*)(int, int*))iota"};
  sycl::kernel_bundle<sycl::bundle_state::executable> kb_exe =
    syclexp::build(kb_src, syclexp::properties{syclexp::registered_names{iota_name}});

  // Get the kernel by passing the same string we used to construct the
  // "registered_names" property.
  sycl::kernel iota = kb_exe.ext_oneapi_get_kernel(iota_name);

  int *ptr = sycl::malloc_shared<int>(NUM, q);
  q.submit([&](sycl::handler &cgh) {
    // Set the values of the kernel arguments.
    cgh.set_args(3, ptr);

    // Launch the kernel according to its type, in this case an nd-range kernel.
    sycl::nd_range ndr{{NUM}, {WGSIZE}};
    cgh.parallel_for(ndr, iota);
  }).wait();
  sycl::free(ptr, q);
}

Issues

  • Do we want to add an API similar to nvrtcGetTypeName? This does seem useful in some advanced cases, and it is not specific to CUDA. The implementation is fairly straightforward. You use typeid to get a std::type_info. You can then call type_info::name to get an implementation-defined name for the type. For clang on Linux, this returns the type’s mangled name. You can then call abi::__cxa_demangle to get an unmangled name for the type. I’m not sure about the details on Windows hosts, though. If type_info::name returns a mangled name on Windows too, then maybe we can still use abi::__cxa_demangle to get an unmangled name, but this needs to be checked.

    Another option might be to provide this functionality as a utility library. There is no inherent reason why this functionality needs to be built into DPC++. However, we don’t yet have a utility library where this would go, and it may be hard for customers to discover this functionality if it is defined outside of this extension.

Non-normative implementation notes for DPC++

Supported build_options when the language is sycl

The SYCL runtime compiler supports the following DPC++ options to be passed in the build_options property.

Some options have equivalent long (starting with --) and short (starting with -) option names. When using the long option name, an argument can be either separated by = in the same element of the build_options property, or given as a separate element. When using the short option name, an argument is either appended directly after the option name, or given as a separate element in the build_options property. The following example shows how to construct the build_options property with each of the forms.

build_options{{
  {"--include-directory=dir1"},
  {"--include-directory"}, {"dir2"},
  {"-Idir3"},
  {"-I"}, {"dir4"}
}};

Preprocessor options

--include-directory=<dir> (-I<dir>)

Add <dir> to to the search list for include files (see section "Including files when the language is sycl"). This is useful, for example, to compile kernels using external libraries.

--define-macro=<name>[=<value>] (-D<name>[=<value>])

Define macro <name>, optionally to the given <value>.

--undefine-macro=<name> (-U<name>)

Undefine macro <name>.

Diagnostic options

The build_options property accepts warning (-W) and remark (-R) emission options supported by the clang compiler. For an overview of these options, see https://clang.llvm.org/docs/DiagnosticsReference.html. The specific options available for SYCL runtime compilation depend on the version of the DPC++ compiler distributed with the SYCL runtime used by the application.

Note: Use the save_log property to obtain detailed output from the compilation process.

SYCL-specific options

-Xs<arg>

Pass <arg> to the backend of the device compiler. When using -Xs<arg>, a - is prepended to <arg> before handing it to the backend. Otherwise, <arg> is passed on unmodified.

For example, the following forms are equivalent:

build_options{{
  {"-XsDFOO=bar"},
  {"-Xs"}, {"-DFOO=bar"}
}};
-fsycl-rtc-mode

Relax the requirement that parameter types for free-function kernels must be forward-declarable.

--auto-pch

The first time this option is passed, the compiler finds the initial set of preprocessor directives (e.g., #define/#include) and comments in the compiled source string (the preamble) and precompiles it. Essentialy, it behaves like a precompiled header containing that preamble. On subsequent compilations, if the compiled source string has the same preamble and the same compilation options are used, the precompiled preamble is used, which speeds up compilation.

If the compiled source string has a different preamble or compilation options differ, a new precompiled preamble is generated, and that preamble can also be used to speed up subsequent compilations. These precompiled preambles are stored internally in memory, so they do not persist from one execution of the application to the next.

The preamble ends with the first statement that is not a preprocessor directive or a comment. For example, in the code below, the preamble ends immediately before the namespace syclext = statement.

#define SYCL_SIMPLE_SWIZZLES
#include <sycl/sycl.hpp>

// Auto-detected preamble ends before next line:
namespace syclext = sycl::ext::oneapi;
namespace syclexp = sycl::ext::oneapi::experimental;

extern "C"
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY((syclexp::nd_range_kernel<1>))
void iota(sycl::vec<int, 2> *p) {
    size_t id = syclext::this_work_item::get_nd_item<1>().get_global_linear_id();
    p[id] = p[id].xx();
}

The compiler uses the following factors when deciding whether a previously generated precompiled preamble can be used:

  • The preamble must exactly match (including whitespace and comments).

  • The compilation options must match (including the same order and the same spelling).

  • There are also certain restrictions that the user must avoid:

    • The content of each header file in the preamble must not change from one compilation to another.

    • It is not recommended to use the __DATE__ or __TIME__ macros in the preamble header files. Depending on the circumstances, these macros may be replaced with the date / time that corresponds to the time at which the precompiled preamble was generated, rather than the time at which the source string is compiled. See also the clang compiler options -Wpch-date-time and -Werror=pch-date-time, which cause the compiler to diagnose a warning or error in this scenario.

--persistent-auto-pch=<dir>

Similar to --auto-pch but the precompiled preamble is stored on the filesystem at the location <dir>. That allows an application to precompile the preamble once and reuse it across multiple invocation of the application which isn’t possible with --auto-pch that stores the precompiled preamble in-memory. This option is incompatible with --auto-pch.

Some notes about the current behavior:

  • Threads only communicate via the persistent cache on the filesystem with no other inter-thread communication to avoid extra synchronization overhead. In an unlikely event that a preamble is missing from the cache and multiple threads need it, each thread will build the preamble.

  • In the very unlikely event of a hash collision the precompiled preamble from the persistent cache is ignored and the source is compiled without it.

  • Unlike --auto-pch, the --persistent-auto-pch option indexes the precompiled preamble by a hash of the preamble content/compilation options, as opposed to using those directly as a key. As a result, there is a highly unlikely possibility that two different preambles will produce the same hash value. Even if this occurs, the compilation won’t fail. However, a compilation using the conflicting hash would proceed without pre-compiled preamble support as if this option wasn’t enabled.

Known issues and limitations when the language is sycl

Changing the compiler action or output

As the DPC++ frontend is integrated tightly in the runtime compilation pipeline, the application cannot change the runtime compiler’s action (e.g. -c, -S) or output file (-o). Similarly, options related to linking (e.g. -L) are incompatible, including the SYCL-specific -fsycl-link action. The implementation throws an exception with the errc::invalid error code when it detects an option that conflicts with the runtime compilation pipeline.

Ahead-of-time compilation

The kernels in a SYCL source string are compiled automatically to native code for all devices passed to the build function (see section "New free functions to create and build kernel bundles"). The implementation rejects the use of the -fsycl-targets= options to request ahead-of-time (AOT) compilation, and throws an exception with the errc::invalid error code when this option is detected. The application can use the -Xs option described above to pass options to the backend of the device compiler, but all other options to control AOT compilation are ignored.

invoke_simd

The SYCL runtime compiler currently does not implement the logic required to support the -fno-sycl-device-code-split-esimd option, and throws an exception with the errc::invalid error code when this option is detected. As a consequence, the invoke_simd functionality is unavailable. However, the SYCL runtime compiler supports ESIMD kernels and source strings containing a mix of SYCL and ESIMD kernels.

Sanitizers

The implementation currently lacks the necessary linking of device libraries to support device, memory and thread sanitizers for runtime-compiled code. If the -fsanitize= option is detected, an exception with the errc::invalid error code is thrown. Other means of activating the sanitizer (e.g. via -Xsycl-device-frontend) may cause the runtime compilation to fail.

Caching

The kernel_compiler implementation in DPC++ supports persistent caching. To enable it, set the the environment variable SYCL_CACHE_PERSISTENT=1. The location of the cache can be changed by setting SYCL_CACHE_DIR. Refer to https://intel.github.io/llvm/design/KernelProgramCache.html#persistent-cache for more details on how to control the cache.