diff --git a/sycl/include/CL/sycl/detail/common.hpp b/sycl/include/CL/sycl/detail/common.hpp index 67eedca888f15..f277f470c2e77 100644 --- a/sycl/include/CL/sycl/detail/common.hpp +++ b/sycl/include/CL/sycl/detail/common.hpp @@ -31,6 +31,9 @@ namespace detail { constexpr const char *SYCL_STREAM_NAME = "sycl"; // Stream name being used for traces generated from the SYCL plugin layer constexpr const char *SYCL_PICALL_STREAM_NAME = "sycl.pi"; +// Stream name being used for traces generated from PI calls. This stream +// contains information about function arguments. +constexpr const char *SYCL_PIDEBUGCALL_STREAM_NAME = "sycl.pi.debug"; // Data structure that captures the user code location information using the // builtin capabilities of the compiler struct code_location { diff --git a/sycl/include/CL/sycl/detail/pi.def b/sycl/include/CL/sycl/detail/pi.def index 5133ed2fa9210..730b4afa50c0c 100644 --- a/sycl/include/CL/sycl/detail/pi.def +++ b/sycl/include/CL/sycl/detail/pi.def @@ -14,6 +14,10 @@ // This is for convinience of doing same thing for all interfaces, e.g. // declare, define, initialize. // +// This list is used to define PiApiKind enum, which is part of external +// interface. To avoid ABI breakage, please, add new entries to the end of the +// list. +// // Platform _PI_API(piPlatformsGet) _PI_API(piPlatformGetInfo) diff --git a/sycl/include/CL/sycl/detail/pi.hpp b/sycl/include/CL/sycl/detail/pi.hpp index cd0cbd3dd790e..4d621d2e33113 100644 --- a/sycl/include/CL/sycl/detail/pi.hpp +++ b/sycl/include/CL/sycl/detail/pi.hpp @@ -184,6 +184,26 @@ uint64_t emitFunctionBeginTrace(const char *FName); /// \param FName The name of the PI API call void emitFunctionEndTrace(uint64_t CorrelationID, const char *FName); +/// Notifies XPTI subscribers about PI function calls and packs call arguments. +/// +/// \param FuncID is the API hash ID from PiApiID type trait. +/// \param FName The name of the PI API call. +/// \param ArgsData is a pointer to packed function call arguments. +uint64_t emitFunctionWithArgsBeginTrace(uint32_t FuncID, const char *FName, + unsigned char *ArgsData); + +/// Notifies XPTI subscribers about PI function call result. +/// +/// \param CorrelationID The correlation ID for the API call generated by the +/// emitFunctionWithArgsBeginTrace() call. +/// \param FuncID is the API hash ID from PiApiID type trait. +/// \param FName The name of the PI API call. +/// \param ArgsData is a pointer to packed function call arguments. +/// \param Result is function call result value. +void emitFunctionWithArgsEndTrace(uint64_t CorrelationID, uint32_t FuncID, + const char *FName, unsigned char *ArgsData, + pi_result Result); + // A wrapper for passing around byte array properties class ByteArray { public: @@ -393,3 +413,5 @@ namespace RT = cl::sycl::detail::pi; } // namespace sycl } // __SYCL_INLINE_NAMESPACE(cl) + +#undef _PI_API diff --git a/sycl/include/CL/sycl/detail/type_traits.hpp b/sycl/include/CL/sycl/detail/type_traits.hpp index d1f45601c500f..c70347bb4826f 100644 --- a/sycl/include/CL/sycl/detail/type_traits.hpp +++ b/sycl/include/CL/sycl/detail/type_traits.hpp @@ -14,6 +14,7 @@ #include #include +#include #include __SYCL_INLINE_NAMESPACE(cl) { @@ -344,6 +345,12 @@ template using const_if_const_AS = DataT; #endif +template struct function_traits {}; + +template struct function_traits { + using ret_type = Ret; + using args_type = std::tuple; +}; } // namespace detail } // namespace sycl diff --git a/sycl/source/detail/pi.cpp b/sycl/source/detail/pi.cpp index 25109bd3a6669..7549a0195c0df 100644 --- a/sycl/source/detail/pi.cpp +++ b/sycl/source/detail/pi.cpp @@ -46,6 +46,8 @@ namespace detail { xpti_td *GSYCLGraphEvent = nullptr; /// Event to be used by PI layer related activities xpti_td *GPICallEvent = nullptr; +/// Event to be used by PI layer calls with arguments +xpti_td *GPIArgCallEvent = nullptr; /// Constants being used as placeholder until one is able to reliably get the /// version of the SYCL runtime constexpr uint32_t GMajVer = 1; @@ -135,6 +137,42 @@ void emitFunctionEndTrace(uint64_t CorrelationID, const char *FName) { #endif // XPTI_ENABLE_INSTRUMENTATION } +uint64_t emitFunctionWithArgsBeginTrace(uint32_t FuncID, const char *FuncName, + unsigned char *ArgsData) { + uint64_t CorrelationID = 0; +#ifdef XPTI_ENABLE_INSTRUMENTATION + if (xptiTraceEnabled()) { + uint8_t StreamID = xptiRegisterStream(SYCL_PIDEBUGCALL_STREAM_NAME); + CorrelationID = xptiGetUniqueId(); + + xpti::function_with_args_t Payload{FuncID, FuncName, ArgsData, nullptr, + nullptr}; + + xptiNotifySubscribers( + StreamID, (uint16_t)xpti::trace_point_type_t::function_with_args_begin, + GPIArgCallEvent, nullptr, CorrelationID, &Payload); + } +#endif + return CorrelationID; +} + +void emitFunctionWithArgsEndTrace(uint64_t CorrelationID, uint32_t FuncID, + const char *FuncName, unsigned char *ArgsData, + pi_result Result) { +#ifdef XPTI_ENABLE_INSTRUMENTATION + if (xptiTraceEnabled()) { + uint8_t StreamID = xptiRegisterStream(SYCL_PIDEBUGCALL_STREAM_NAME); + + xpti::function_with_args_t Payload{FuncID, FuncName, ArgsData, &Result, + nullptr}; + + xptiNotifySubscribers( + StreamID, (uint16_t)xpti::trace_point_type_t::function_with_args_end, + GPIArgCallEvent, nullptr, CorrelationID, &Payload); + } +#endif +} + void contextSetExtendedDeleter(const cl::sycl::context &context, pi_context_extended_deleter func, void *user_data) { @@ -430,6 +468,14 @@ static void initializePlugins(std::vector *Plugins) { GPICallEvent = xptiMakeEvent("PI Layer", &PIPayload, xpti::trace_algorithm_event, xpti_at::active, &PiInstanceNo); + + xptiInitialize(SYCL_PIDEBUGCALL_STREAM_NAME, GMajVer, GMinVer, GVerStr); + xpti::payload_t PIArgPayload( + "Plugin Interface Layer (with function arguments)"); + uint64_t PiArgInstanceNo; + GPIArgCallEvent = xptiMakeEvent("PI Layer with arguments", &PIArgPayload, + xpti::trace_algorithm_event, xpti_at::active, + &PiArgInstanceNo); #endif } diff --git a/sycl/source/detail/plugin.hpp b/sycl/source/detail/plugin.hpp index 4027bcefc759d..c7933c86db554 100644 --- a/sycl/source/detail/plugin.hpp +++ b/sycl/source/detail/plugin.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -25,7 +26,62 @@ namespace sycl { namespace detail { #ifdef XPTI_ENABLE_INSTRUMENTATION extern xpti::trace_event_data_t *GPICallEvent; +extern xpti::trace_event_data_t *GPIArgCallEvent; #endif + +template +struct array_fill_helper; + +template struct PiApiArgTuple; + +#define _PI_API(api) \ + template <> struct PiApiArgTuple { \ + using type = typename function_traits::args_type; \ + }; + +#include +#undef _PI_API + +template +struct array_fill_helper { + static void fill(unsigned char *Dst, T &&Arg) { + using ArgsTuple = typename PiApiArgTuple::type; + // C-style cast is required here. + auto RealArg = (std::tuple_element_t)(Arg); + *(std::remove_cv_t> *)Dst = RealArg; + } +}; + +template +struct array_fill_helper { + static void fill(unsigned char *Dst, const T &&Arg, Args &&... Rest) { + using ArgsTuple = typename PiApiArgTuple::type; + // C-style cast is required here. + auto RealArg = (std::tuple_element_t)(Arg); + *(std::remove_cv_t> *)Dst = RealArg; + array_fill_helper::fill( + Dst + sizeof(decltype(RealArg)), std::forward(Rest)...); + } +}; + +template +constexpr size_t totalSize(const std::tuple &) { + return (sizeof(Ts) + ...); +} + +template +auto packCallArguments(ArgsT &&... Args) { + using ArgsTuple = typename PiApiArgTuple::type; + + constexpr size_t TotalSize = totalSize(ArgsTuple{}); + + std::array ArgsData; + array_fill_helper::fill(ArgsData.data(), + std::forward(Args)...); + + return ArgsData; +} + /// The plugin class provides a unified interface to the underlying low-level /// runtimes for the device-agnostic SYCL runtime. /// @@ -75,6 +131,10 @@ class plugin { // the per_instance_user_data field. const char *PIFnName = PiCallInfo.getFuncName(); uint64_t CorrelationID = pi::emitFunctionBeginTrace(PIFnName); + auto ArgsData = + packCallArguments(std::forward(Args)...); + uint64_t CorrelationIDWithArgs = pi::emitFunctionWithArgsBeginTrace( + static_cast(PiApiOffset), PIFnName, ArgsData.data()); #endif RT::PiResult R; if (pi::trace(pi::TraceLevel::PI_TRACE_CALLS)) { @@ -93,6 +153,9 @@ class plugin { #ifdef XPTI_ENABLE_INSTRUMENTATION // Close the function begin with a call to function end pi::emitFunctionEndTrace(CorrelationID, PIFnName); + pi::emitFunctionWithArgsEndTrace(CorrelationIDWithArgs, + static_cast(PiApiOffset), + PIFnName, ArgsData.data(), R); #endif return R; } diff --git a/sycl/tools/CMakeLists.txt b/sycl/tools/CMakeLists.txt index a21ae9c8f9de9..96110ea328f39 100644 --- a/sycl/tools/CMakeLists.txt +++ b/sycl/tools/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(sycl-ls) +add_subdirectory(pi-trace) # TODO: move each tool in its own sub-directory add_executable(get_device_count_by_type get_device_count_by_type.cpp) diff --git a/sycl/tools/pi-trace/CMakeLists.txt b/sycl/tools/pi-trace/CMakeLists.txt new file mode 100644 index 0000000000000..5e81d6ee6a3d1 --- /dev/null +++ b/sycl/tools/pi-trace/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(pi_trace SHARED pi_trace.cpp) +target_link_libraries(pi_trace PRIVATE xptifw) +target_include_directories(pi_trace PRIVATE "${XPTI_SOURCE_DIR}/include") +target_include_directories(pi_trace PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../xpti_helpers/") +target_include_directories(pi_trace PRIVATE "${sycl_inc_dir}") +target_include_directories(pi_trace PRIVATE "${sycl_src_dir}") + +if(UNIX) + target_link_libraries(pi_trace PRIVATE dl) +endif() + +if (XPTI_ENABLE_TBB) + target_link_libraries(pi_trace PRIVATE tbb) +endif() diff --git a/sycl/tools/pi-trace/pi_trace.cpp b/sycl/tools/pi-trace/pi_trace.cpp new file mode 100644 index 0000000000000..3fee6fd9f13e3 --- /dev/null +++ b/sycl/tools/pi-trace/pi_trace.cpp @@ -0,0 +1,84 @@ +//==----------- pi_trace.cpp.cpp -------------------------------------------==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +/// \file pi_trace.cpp +/// A sample XPTI subscriber to demonstrate how to collect PI function call +/// arguments. + +#include "xpti_trace_framework.h" + +#include "pi_arguments_handler.hpp" + +#include + +#include +#include +#include +#include +#include + +static uint8_t GStreamID = 0; +std::mutex GIOMutex; + +sycl::xpti_helpers::PiArgumentsHandler ArgHandler; + +// The lone callback function we are going to use to demonstrate how to attach +// the collector to the running executable +XPTI_CALLBACK_API void tpCallback(uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, const void *user_data); + +// Based on the documentation, every subscriber MUST implement the +// xptiTraceInit() and xptiTraceFinish() APIs for their subscriber collector to +// be loaded successfully. +XPTI_CALLBACK_API void xptiTraceInit(unsigned int major_version, + unsigned int minor_version, + const char *version_str, + const char *stream_name) { + if (std::string_view(stream_name) == "sycl.pi.arg") { + GStreamID = xptiRegisterStream(stream_name); + xptiRegisterCallback( + GStreamID, (uint16_t)xpti::trace_point_type_t::function_with_args_begin, + tpCallback); + xptiRegisterCallback( + GStreamID, (uint16_t)xpti::trace_point_type_t::function_with_args_end, + tpCallback); + +#define _PI_API(api) \ + ArgHandler.set##_##api([](auto &&... Args) { \ + std::cout << "---> " << #api << "(" \ + << "\n"; \ + sycl::detail::pi::printArgs(Args...); \ + std::cout << ") ---> "; \ + }); +#include +#undef _PI_API + } +} + +XPTI_CALLBACK_API void xptiTraceFinish(const char *stream_name) { + // NOP +} + +XPTI_CALLBACK_API void tpCallback(uint16_t TraceType, + xpti::trace_event_data_t *Parent, + xpti::trace_event_data_t *Event, + uint64_t Instance, const void *UserData) { + auto Type = static_cast(TraceType); + if (Type == xpti::trace_point_type_t::function_with_args_end) { + // Lock while we print information + std::lock_guard Lock(GIOMutex); + + const auto *Data = + static_cast(UserData); + + ArgHandler.handle(Data->function_id, Data->args_data); + std::cout << *static_cast(Data->ret_data) << "\n"; + } +} diff --git a/sycl/tools/xpti_helpers/pi_arguments_handler.hpp b/sycl/tools/xpti_helpers/pi_arguments_handler.hpp new file mode 100644 index 0000000000000..4cad50ba90c66 --- /dev/null +++ b/sycl/tools/xpti_helpers/pi_arguments_handler.hpp @@ -0,0 +1,96 @@ +//==---------- pi_arguments_handler.hpp - PI call arguments handler --------==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include +#include + +#include +#include + +__SYCL_INLINE_NAMESPACE(cl) { +namespace sycl { +namespace xpti_helpers { + +template +inline auto get(char *Data, const std::index_sequence &) { + // Our type should be last in Is sequence + using TargetType = + typename std::tuple_element::type; + + // Calculate sizeof all elements before target + target element then substract + // sizeof target element + const size_t Offset = + (sizeof(typename std::tuple_element::type) + ...) - + sizeof(TargetType); + return *(typename std::decay::type *)(Data + Offset); +} + +template +inline TupleT unpack(char *Data, + const std::index_sequence & /*1..TupleSize*/) { + return {get(Data, std::make_index_sequence{})...}; +} + +template struct to_function {}; + +template struct to_function> { + using type = std::function; +}; + +/// PiArgumentsHandler is a helper class to process incoming XPTI function call +/// events and unpack contained arguments. +/// +/// Usage: +/// +/// PiArgumentsHandler provides set_ member functions, that accept a +/// lambda with the same arguments as target PI API. Use it to set up handling +/// for particular API. By default an empty lambda is used. +/// +/// When an event is signaled, use PiArgumentsHandler::handle() member function +/// to process the incoming event and call necessary handler. +/// +/// See sycl/tools/pi-trace/ for an example. +class PiArgumentsHandler { +public: + void handle(uint32_t ID, void *ArgsData) { +#define _PI_API(api) \ + if (ID == static_cast(detail::PiApiKind::api)) { \ + MHandler##_##api(ArgsData); \ + return; \ + } +#include +#undef _PI_API + } + +#define _PI_API(api) \ + void set##_##api( \ + const typename to_function::args_type>::type &Handler) { \ + MHandler##_##api = [Handler](void *Data) { \ + using TupleT = \ + typename detail::function_traits::args_type; \ + TupleT Tuple = unpack( \ + (char *)Data, \ + std::make_index_sequence::value>{}); \ + std::apply(Handler, Tuple); \ + }; \ + } +#include +#undef _PI_API + +private: +#define _PI_API(api) \ + std::function MHandler##_##api = [](void *) {}; +#include +#undef _PI_API +}; +} // namespace xpti_helpers +} // namespace sycl +} // __SYCL_INLINE_NAMESPACE(cl) diff --git a/sycl/unittests/pi/CMakeLists.txt b/sycl/unittests/pi/CMakeLists.txt index 93a958850f9d8..53c69f5cae2fa 100644 --- a/sycl/unittests/pi/CMakeLists.txt +++ b/sycl/unittests/pi/CMakeLists.txt @@ -6,10 +6,12 @@ add_sycl_unittest(PiTests OBJECT EnqueueMemTest.cpp PiMock.cpp PlatformTest.cpp + pi_arguments_handler.cpp ) add_dependencies(PiTests sycl) target_include_directories(PiTests PRIVATE SYSTEM ${sycl_inc_dir}) +target_include_directories(PiTests PRIVATE ${sycl_src_dir}/../tools/xpti_helpers) if(SYCL_BUILD_PI_CUDA) add_subdirectory(cuda) diff --git a/sycl/unittests/pi/pi_arguments_handler.cpp b/sycl/unittests/pi/pi_arguments_handler.cpp new file mode 100644 index 0000000000000..ce9d817b8a628 --- /dev/null +++ b/sycl/unittests/pi/pi_arguments_handler.cpp @@ -0,0 +1,40 @@ +//==------- pi_arguments_handler.cpp --- A test for XPTI PI args helper ---===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include + +#include "pi_arguments_handler.hpp" + +#include + +#include + +TEST(PiArgumentsHandlerTest, CanUnpackArguments) { + sycl::xpti_helpers::PiArgumentsHandler Handler; + + const pi_uint32 NumPlatforms = 42; + pi_platform *Platforms = new pi_platform[NumPlatforms]; + + Handler.set_piPlatformsGet( + [&](pi_uint32 NP, pi_platform *Plts, pi_uint32 *Ret) { + EXPECT_EQ(NP, NumPlatforms); + EXPECT_EQ(Platforms, Plts); + EXPECT_EQ(Ret, nullptr); + }); + + constexpr size_t Size = sizeof(pi_uint32) + 2 * sizeof(void *); + std::array Data{0}; + *reinterpret_cast(Data.data()) = NumPlatforms; + *reinterpret_cast(Data.data() + sizeof(pi_uint32)) = + Platforms; + + uint32_t ID = static_cast(sycl::detail::PiApiKind::piPlatformsGet); + Handler.handle(ID, Data.data()); + + delete[] Platforms; +} diff --git a/xpti/include/xpti_data_types.h b/xpti/include/xpti_data_types.h index 7ed10832bb7c6..82a845b51138e 100644 --- a/xpti/include/xpti_data_types.h +++ b/xpti/include/xpti_data_types.h @@ -162,6 +162,22 @@ struct payload_t { } }; +/// A data structure that holds information about an API function call and its +/// arguments. +struct function_with_args_t { + /// A stable API function ID. It is a contract between the profiled system and + /// subscribers. + uint32_t function_id; + /// A null-terminated string, containing human-readable function name. + const char *function_name; + /// Pointer to packed function arguments. + void *args_data; + /// Pointer to the return value of the function. + void *ret_data; + /// [Provisional] Additional data, generated by the profiled system. + void *user_data; +}; + /// @brief Enumerator defining the global/basic trace point types /// @details The frame work defines the global/basic trace point types /// that are necessary for modeling parallel runtimes. A helper macro @@ -255,6 +271,10 @@ enum class trace_point_type_t : uint16_t { function_end = XPTI_TRACE_POINT_END(12), /// Use to notify that a new metadata entry is available for a given event metadata = XPTI_TRACE_POINT_BEGIN(13), + /// Used to trace function call begin and its arguments. + function_with_args_begin = XPTI_TRACE_POINT_BEGIN(14), + /// Used to trace function call end. + function_with_args_end = XPTI_TRACE_POINT_END(15), /// Indicates that the trace point is user defined and only the tool defined /// for a stream will be able to handle it user_defined = 1 << 7 diff --git a/xptifw/src/xpti_trace_framework.cpp b/xptifw/src/xpti_trace_framework.cpp index 6dae0dbe81c28..9f7fef6070717 100644 --- a/xptifw/src/xpti_trace_framework.cpp +++ b/xptifw/src/xpti_trace_framework.cpp @@ -1028,7 +1028,11 @@ class Framework { // to trace function calls without too much effort. if (!(UserData && (TraceType == (uint16_t)trace_point_type_t::function_begin || - TraceType == (uint16_t)trace_point_type_t::function_end))) { + TraceType == (uint16_t)trace_point_type_t::function_end || + TraceType == + (uint16_t)trace_point_type_t::function_with_args_begin || + TraceType == + (uint16_t)trace_point_type_t::function_with_args_end))) { return xpti::result_t::XPTI_RESULT_INVALIDARG; } }