Files
rocm-systems/projects/rocprofiler-sdk/source/lib/rocprofiler/rocprofiler_config.cpp
T
Jonathan R. Madsen 4a8c101839 SortIncludes: true (#19)
* Update .clang-format

- set SortIncludes to true

* Reformat source with includes sorted

[ROCm/rocprofiler-sdk commit: 41b1d91841]
2023-08-24 19:19:48 -05:00

702 líneas
27 KiB
C++

#include <rocprofiler/config.h>
#include <rocprofiler/rocprofiler.h>
#include "config_helpers.hpp"
#include "config_internal.hpp"
#include <roctracer/roctx.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <atomic>
#include <cstddef>
#include <iostream>
#include <mutex>
#include <hsa/hsa.h>
#include <hsa/hsa_api_trace.h>
#include <hsa/hsa_ext_amd.h>
#include <hsa/hsa_ext_image.h>
typedef enum
{
ACTIVITY_API_PHASE_ENTER = 0,
ACTIVITY_API_PHASE_EXIT = 1
} activity_api_phase_t;
typedef struct roctx_api_data_s
{
union
{
struct
{
const char* message;
roctx_range_id_t id;
};
struct
{
const char* message;
} roctxMarkA;
struct
{
const char* message;
} roctxRangePushA;
struct
{
const char* message;
} roctxRangePop;
struct
{
const char* message;
roctx_range_id_t id;
} roctxRangeStartA;
struct
{
const char* message;
roctx_range_id_t id;
} roctxRangeStop;
} args;
} roctx_api_data_t;
// helper macros ensuring C and C++ structs adhere to specific naming convention
#define ROCP_PUBLIC_CONFIG(TYPE) ::rocprofiler_##TYPE
#define ROCP_PRIVATE_CONFIG(TYPE) ::rocprofiler::internal::TYPE
// Below asserts at compile time that the external C object has the same size as internal
// C++ object, e.g.,
// sizeof(rocprofiler_domain_config) == sizeof(rocprofiler::internal::domain_config)
#define ROCP_ASSERT_CONFIG_ABI(TYPE) \
static_assert(sizeof(ROCP_PUBLIC_CONFIG(TYPE)) == sizeof(ROCP_PRIVATE_CONFIG(TYPE)), \
"Error! rocprofiler_" #TYPE " ABI error");
// Below asserts at compile time that the external C struct members has the same offset as
// internal C++ struct members
#define ROCP_ASSERT_CONFIG_OFFSET_ABI(TYPE, PUB_FIELD, PRIV_FIELD) \
static_assert(offsetof(ROCP_PUBLIC_CONFIG(TYPE), PUB_FIELD) == \
offsetof(ROCP_PRIVATE_CONFIG(TYPE), PRIV_FIELD), \
"Error! rocprofiler_" #TYPE "." #PUB_FIELD " ABI offset error"); \
static_assert(sizeof(ROCP_PUBLIC_CONFIG(TYPE)::PUB_FIELD) == \
sizeof(ROCP_PRIVATE_CONFIG(TYPE)::PRIV_FIELD), \
"Error! rocprofiler_" #TYPE "." #PUB_FIELD " ABI size error");
// this defines a template specialization for ensuring that the reinterpret_cast is only
// applied between public C structs and private C++ structs which are compatible.
#define ROCP_DEFINE_API_CAST_IMPL(INPUT_TYPE, OUTPUT_TYPE) \
namespace traits \
{ \
template <> \
struct api_cast<INPUT_TYPE> \
{ \
using input_type = INPUT_TYPE; \
using output_type = OUTPUT_TYPE; \
\
output_type* operator()(input_type* _v) const \
{ \
return reinterpret_cast<output_type*>(_v); \
} \
\
const output_type* operator()(const input_type* _v) const \
{ \
return reinterpret_cast<const output_type*>(_v); \
} \
}; \
}
// define C -> C++ and C++ -> C casting rules
#define ROCP_DEFINE_API_CAST_D(TYPE) \
ROCP_DEFINE_API_CAST_IMPL(ROCP_PUBLIC_CONFIG(TYPE), ROCP_PRIVATE_CONFIG(TYPE)) \
ROCP_DEFINE_API_CAST_IMPL(ROCP_PRIVATE_CONFIG(TYPE), ROCP_PUBLIC_CONFIG(TYPE))
// use only when C++ struct is just an alias for C struct
#define ROCP_DEFINE_API_CAST_S(TYPE) \
ROCP_DEFINE_API_CAST_IMPL(ROCP_PUBLIC_CONFIG(TYPE), ROCP_PRIVATE_CONFIG(TYPE))
namespace
{
namespace traits
{
// left undefined to ensure template specialization
template <typename PublicT>
struct api_cast;
// ensure api_cast<decltype(a)> where decltype(a) is const Tp equates to api_cast<Tp>
template <typename PublicT>
struct api_cast<const PublicT> : api_cast<PublicT>
{};
// ensure api_cast<decltype(a)> where decltype(a) is Tp& equates to api_cast<Tp>
template <typename PublicT>
struct api_cast<PublicT&> : api_cast<PublicT>
{};
// ensure api_cast<decltype(a)> where decltype(a) is Tp* equates to api_cast<Tp>
template <typename PublicT>
struct api_cast<PublicT*> : api_cast<PublicT>
{};
} // namespace traits
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
//
// SEE BELOW! VERY IMPORTANT!
//
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
//
// EVERY NEW CONFIG AND ALL OF ITS MEMBER FIELDS NEED TO HAVE THESE COMPILE TIME CHECKS!
//
// these checks verify the two structs have the same size and that each
// member field has the same size and offset into the struct
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ROCP_ASSERT_CONFIG_ABI(config)
ROCP_ASSERT_CONFIG_OFFSET_ABI(config, size, size)
ROCP_ASSERT_CONFIG_OFFSET_ABI(config, compat_version, compat_version)
ROCP_ASSERT_CONFIG_OFFSET_ABI(config, api_version, api_version)
ROCP_ASSERT_CONFIG_OFFSET_ABI(config, reserved0, context_idx)
ROCP_ASSERT_CONFIG_OFFSET_ABI(config, user_data, user_data)
ROCP_ASSERT_CONFIG_OFFSET_ABI(config, buffer, buffer)
ROCP_ASSERT_CONFIG_OFFSET_ABI(config, domain, domain)
ROCP_ASSERT_CONFIG_OFFSET_ABI(config, filter, filter)
ROCP_ASSERT_CONFIG_ABI(domain_config)
ROCP_ASSERT_CONFIG_OFFSET_ABI(domain_config, callback, user_sync_callback)
ROCP_ASSERT_CONFIG_OFFSET_ABI(domain_config, reserved0, domains)
ROCP_ASSERT_CONFIG_OFFSET_ABI(domain_config, reserved1, opcodes)
ROCP_ASSERT_CONFIG_ABI(buffer_config)
ROCP_ASSERT_CONFIG_OFFSET_ABI(buffer_config, callback, callback)
ROCP_ASSERT_CONFIG_OFFSET_ABI(buffer_config, buffer_size, buffer_size)
// ROCP_ASSERT_CONFIG_OFFSET_ABI(buffer_config, reserved0, buffer)
ROCP_ASSERT_CONFIG_OFFSET_ABI(buffer_config, reserved1, buffer_idx)
ROCP_DEFINE_API_CAST_D(config)
ROCP_DEFINE_API_CAST_D(domain_config)
ROCP_DEFINE_API_CAST_D(buffer_config)
ROCP_DEFINE_API_CAST_S(filter_config)
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
//
// SEE ABOVE! VERY IMPORTANT!
//
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/// use this to ensure that reinterpret_cast from public C struct to internal C++ struct
/// is valid, e.g. guard against accidentally casting to wrong type
template <typename Tp>
auto
rocp_cast(Tp* _val)
{
return traits::api_cast<Tp>{}(_val);
}
/// helper function for making copies of the fields in rocprofiler_config. If the config
/// field needs to be copied in some special way, use a template specialization of the
/// "construct" function in the allocator to handle this, e.g.:
///
/// using special_config = ::rocprofiler::internal::special_config;
///
/// template <>
/// void
/// allocator<special_config, 8>::construct(special_config* const _p,
/// const special_config& _v) const
/// {
/// auto _tmp = special_config{};
/// // ... special copy of fields from _v into _tmp
///
/// // placement new of _tmp into _p
/// _p = new(_p) special_config{ _tmp };
/// }
///
/// template <>
/// void
/// allocator<special_config, 8>::construct(special_config* const _p,
/// special_config&& _v) const
/// {
/// auto _tmp = std::move(_v);
/// // ... perform special needs
///
/// // placement new of _tmp into _p
/// _p = new(_p) special_config{ std::move(_tmp) };
/// }
///
template <typename Tp, typename Up>
Tp*&
copy_config_field(Tp*& _dst, Up* _src_v)
{
static auto _allocator = allocator<Tp>{};
if constexpr(!std::is_same<Tp, Up>::value)
{
using PrivateT = typename traits::api_cast<Up>::output_type;
static_assert(std::is_same<PrivateT, Tp>::value, "Error incorrect field copy");
auto _src = rocp_cast(_src_v);
if(_src)
{
_dst = _allocator.allocate(1);
_allocator.construct(_dst, *_src);
}
return _dst;
}
else
{
if(_src_v)
{
_dst = _allocator.allocate(1);
_allocator.construct(_dst, *_src_v);
}
return _dst;
}
}
auto&
get_configs_buffer()
{
static char
_v[::rocprofiler::internal::max_configs_count * sizeof(rocprofiler::internal::config)];
return _v;
}
auto&
get_configs_mutex()
{
static auto _v = std::mutex{};
return _v;
}
inline uint32_t
get_tid()
{
return syscall(__NR_gettid);
}
constexpr auto rocp_max_configs = ::rocprofiler::internal::max_configs_count;
} // namespace
namespace rocprofiler
{
namespace internal
{
std::array<rocprofiler::internal::config*, max_configs_count>&
get_registered_configs()
{
static auto _v = std::array<rocprofiler::internal::config*, max_configs_count>{};
return _v;
}
std::array<std::atomic<rocprofiler::internal::config*>, max_configs_count>&
get_active_configs()
{
static auto _v = std::array<std::atomic<rocprofiler::internal::config*>, max_configs_count>{};
return _v;
}
} // namespace internal
} // namespace rocprofiler
extern "C" {
rocprofiler_status_t
rocprofiler_allocate_config(rocprofiler_config* _inp_cfg)
{
// perform checks that rocprofiler can be activated
::memset(_inp_cfg, 0, sizeof(rocprofiler_config));
auto* _cfg = rocp_cast(_inp_cfg);
_cfg->size = sizeof(::rocprofiler_config);
_cfg->compat_version = 0;
_cfg->api_version = ROCPROFILER_API_VERSION_ID;
_cfg->context_idx = std::numeric_limits<decltype(_cfg->context_idx)>::max();
// initial value checks
assert(_cfg->size == sizeof(rocprofiler::internal::config));
assert(_cfg->compat_version == 0);
assert(_cfg->api_version == ROCPROFILER_API_VERSION_ID);
assert(_cfg->buffer == nullptr);
assert(_cfg->domain == nullptr);
assert(_cfg->filter == nullptr);
assert(_cfg->context_idx ==
std::numeric_limits<decltype(rocprofiler::internal::config::context_idx)>::max());
// ... allocate any internal space needed to handle another config ...
{
auto _lk = std::unique_lock<std::mutex>{get_configs_mutex()};
// ...
}
return ROCPROFILER_STATUS_SUCCESS;
}
rocprofiler_status_t
rocprofiler_validate_config(const rocprofiler_config* cfg_v)
{
const auto* cfg = rocp_cast(cfg_v);
if(cfg->buffer == nullptr) return ROCPROFILER_STATUS_ERROR_BUFFER_NOT_FOUND;
if(cfg->filter == nullptr) return ROCPROFILER_STATUS_ERROR_FILTER_NOT_FOUND;
if(cfg->domain == nullptr || cfg->domain->domains == 0)
return ROCPROFILER_STATUS_ERROR_INCORRECT_DOMAIN;
return ROCPROFILER_STATUS_SUCCESS;
}
rocprofiler_status_t
rocprofiler_start_config(rocprofiler_config* cfg_v, rocprofiler_context_id_t* context_id)
{
if(rocprofiler_validate_config(cfg_v) != ROCPROFILER_STATUS_SUCCESS)
{
std::cerr << "rocprofiler_start_config() provided an invalid configuration. tool "
"should use rocprofiler_validate_config() to check whether the "
"config is valid and adapt accordingly to issues before trying to "
"start the configuration."
<< std::endl;
abort();
}
auto* cfg = rocp_cast(cfg_v);
uint64_t idx = rocp_max_configs;
{
auto _lk = std::unique_lock<std::mutex>{get_configs_mutex()};
for(size_t i = 0; i < rocp_max_configs; ++i)
{
if(rocprofiler::internal::get_registered_configs().at(i) == nullptr)
{
idx = i;
break;
}
}
}
// too many configs already registered
if(idx == rocp_max_configs) return ROCPROFILER_STATUS_ERROR_CONTEXT_NOT_ACTIVE;
cfg->context_idx = idx;
context_id->handle = idx;
// using the context id, compute the location in the buffer of configs
auto* _offset = get_configs_buffer() + (idx * sizeof(rocprofiler::internal::config));
// placement new into the buffer
auto* _copy_cfg = new(_offset) rocprofiler::internal::config{*cfg};
// make copies of non-null config fields
copy_config_field(_copy_cfg->buffer, cfg->buffer);
copy_config_field(_copy_cfg->domain, cfg->domain);
copy_config_field(_copy_cfg->filter, cfg->filter);
// store until "deallocation"
rocprofiler::internal::get_registered_configs().at(idx) = _copy_cfg;
using config_t = rocprofiler::internal::config;
// atomic swap the pointer into the "active" array used internally
config_t* _expected = nullptr;
bool success = rocprofiler::internal::get_active_configs().at(idx).compare_exchange_strong(
_expected, rocprofiler::internal::get_registered_configs().at(idx));
if(!success) return ROCPROFILER_STATUS_ERROR_HAS_ACTIVE_CONTEXT; // need relevant enum
return ROCPROFILER_STATUS_SUCCESS;
}
rocprofiler_status_t
rocprofiler_stop_config(rocprofiler_context_id_t idx)
{
// atomically assign the config pointer to NULL so that it is skipped in future
// callbacks
auto* _expected =
rocprofiler::internal::get_active_configs().at(idx.handle).load(std::memory_order_relaxed);
bool success = rocprofiler::internal::get_active_configs()
.at(idx.handle)
.compare_exchange_strong(_expected, nullptr);
if(!success)
return ROCPROFILER_STATUS_ERROR_CONTEXT_NOT_FOUND; // compare exchange strong
// failed
return ROCPROFILER_STATUS_SUCCESS;
}
rocprofiler_status_t
rocprofiler_domain_add_domain(struct rocprofiler_domain_config* _inp_cfg,
rocprofiler_tracer_activity_domain_t _domain)
{
auto* _cfg = rocp_cast(_inp_cfg);
if(_domain <= ROCPROFILER_TRACER_ACTIVITY_DOMAIN_NONE ||
_domain >= ROCPROFILER_TRACER_ACTIVITY_DOMAIN_LAST)
return ROCPROFILER_STATUS_ERROR_INVALID_DOMAIN_ID;
_cfg->domains |= (1 << _domain);
return ROCPROFILER_STATUS_SUCCESS;
}
rocprofiler_status_t
rocprofiler_domain_add_domains(struct rocprofiler_domain_config* _inp_cfg,
rocprofiler_tracer_activity_domain_t* _domains,
size_t _ndomains)
{
for(size_t i = 0; i < _ndomains; ++i)
{
auto _status = rocprofiler_domain_add_domain(_inp_cfg, _domains[i]);
if(_status != ROCPROFILER_STATUS_SUCCESS) return _status;
}
return ROCPROFILER_STATUS_SUCCESS;
}
rocprofiler_status_t
rocprofiler_domain_add_op(struct rocprofiler_domain_config* _inp_cfg,
rocprofiler_tracer_activity_domain_t _domain,
uint32_t _op)
{
auto* _cfg = rocp_cast(_inp_cfg);
if(_domain <= ROCPROFILER_TRACER_ACTIVITY_DOMAIN_NONE ||
_domain >= ROCPROFILER_TRACER_ACTIVITY_DOMAIN_LAST)
return ROCPROFILER_STATUS_ERROR_INVALID_DOMAIN_ID;
if(_op >= get_domain_max_op(_domain)) return ROCPROFILER_STATUS_ERROR_INVALID_OPERATION_ID;
auto _offset = (_domain * rocprofiler::internal::domain_ops_offset);
_cfg->opcodes.set(_offset + _op, true);
return ROCPROFILER_STATUS_SUCCESS;
}
rocprofiler_status_t
rocprofiler_domain_add_ops(struct rocprofiler_domain_config* _inp_cfg,
rocprofiler_tracer_activity_domain_t _domain,
uint32_t* _ops,
size_t _nops)
{
for(size_t i = 0; i < _nops; ++i)
{
auto _status = rocprofiler_domain_add_op(_inp_cfg, _domain, _ops[i]);
if(_status != ROCPROFILER_STATUS_SUCCESS) return _status;
}
return ROCPROFILER_STATUS_SUCCESS;
}
// ------------------------------------------------------------------------------------ //
//
// demo of internal implementation
//
// ------------------------------------------------------------------------------------ //
void
api_callback(rocprofiler_tracer_activity_domain_t domain,
uint32_t cid,
const void* /*callback_data*/,
void*)
{
for(const auto& aitr : rocprofiler::internal::get_active_configs())
{
auto* itr = aitr.load();
if(!itr) continue;
// below should be valid so this might need to raise error
if(!itr->domain) continue;
// if the given domain + op is not enabled, skip this config
if(!(*itr->domain)(domain, cid)) continue;
if(itr->filter)
{
if(domain == ROCPROFILER_TRACER_ACTIVITY_DOMAIN_ROCTX)
{}
else if(domain == ROCPROFILER_TRACER_ACTIVITY_DOMAIN_HSA_API)
{
if(itr->filter->hsa_function_id && itr->filter->hsa_function_id(cid) == 0) continue;
}
else if(domain == ROCPROFILER_TRACER_ACTIVITY_DOMAIN_HIP_API)
{
if(itr->filter->hip_function_id && itr->filter->hip_function_id(cid) == 0) continue;
}
}
auto& _domain = (*itr->domain);
auto& _correlation = (*itr->correlation_id);
auto _correlation_id = rocprofiler::internal::correlation_config::get_unique_record_id();
if(_correlation.external_id_callback)
_correlation.external_id =
_correlation.external_id_callback(domain, cid, _correlation_id);
auto timestamp_ns = []() -> uint64_t {
return std::chrono::steady_clock::now().time_since_epoch().count();
};
(void) _domain;
(void) timestamp_ns;
/*
auto _header = rocprofiler_record_header_t{ROCPROFILER_TRACER_RECORD,
rocprofiler_record_id_t{_correlation_id}};
auto _op_id = rocprofiler_tracer_operation_id_t{cid};
auto _agent_id = rocprofiler_agent_id_t{0};
auto _queue_id = rocprofiler_queue_id_t{0};
auto _thread_id = rocprofiler_thread_id_t{get_tid()};
auto _context = rocprofiler_context_id_t{itr->context_idx};
auto _timestamp_raw = rocprofiler_timestamp_t{timestamp_ns()};
auto _timestamp = rocprofiler_record_header_timestamp_t{_timestamp_raw, _timestamp_raw};
if(domain == ROCPROFILER_TRACER_ACTIVITY_DOMAIN_ROCTX)
{
auto _api_data = rocprofiler_tracer_api_data_t{};
const roctx_api_data_t* _data =
reinterpret_cast<const roctx_api_data_t*>(callback_data);
if(itr->filter && itr->filter->name && itr->filter->name(_data->args.message) == 0)
continue;
_api_data.roctx = _data;
auto _phase = rocprofiler_api_tracing_phase_t{ROCPROFILER_PHASE_ENTER};
_timestamp = {_timestamp_raw, _timestamp_raw};
auto _external_cid = rocprofiler_tracer_external_id_t{_data ? _data->args.id : 0};
auto _activity_cid = rocprofiler_tracer_activity_correlation_id_t{0};
const char* _name = _data->args.message;
_domain.user_sync_callback(rocprofiler_record_tracer_t{_header,
_external_cid,
ACTIVITY_DOMAIN_ROCTX,
_op_id,
_api_data,
_activity_cid,
_timestamp,
_agent_id,
_queue_id,
_thread_id,
_phase,
_name},
_context);
}
else if(domain == ROCPROFILER_TRACER_ACTIVITY_DOMAIN_HSA_API)
{
auto _api_data = rocprofiler_tracer_api_data_t{};
const hsa_api_data_t* _data = reinterpret_cast<const hsa_api_data_t*>(callback_data);
_api_data.hsa = _data;
auto _phase = rocprofiler_api_tracing_phase_t{(_data->phase == ACTIVITY_API_PHASE_ENTER)
? ROCPROFILER_PHASE_ENTER
: ROCPROFILER_PHASE_EXIT};
if(_phase == ROCPROFILER_PHASE_ENTER)
_timestamp.begin = _timestamp_raw;
else
_timestamp.end = _timestamp_raw;
auto _external_cid = rocprofiler_tracer_external_id_t{0};
auto _activity_cid =
rocprofiler_tracer_activity_correlation_id_t{_data->correlation_id};
const char* _name = nullptr;
_domain.user_sync_callback(rocprofiler_record_tracer_t{_header,
_external_cid,
ACTIVITY_DOMAIN_HSA_API,
_op_id,
_api_data,
_activity_cid,
_timestamp,
_agent_id,
_queue_id,
_thread_id,
_phase,
_name},
_context);
}
else if(domain == ROCPROFILER_TRACER_ACTIVITY_DOMAIN_HIP_API)
{
auto _api_data = rocprofiler_tracer_api_data_t{};
const hip_api_data_t* _data = reinterpret_cast<const hip_api_data_t*>(callback_data);
_api_data.hip = _data;
auto _phase = rocprofiler_api_tracing_phase_t{(_data->phase == ACTIVITY_API_PHASE_ENTER)
? ROCPROFILER_PHASE_ENTER
: ROCPROFILER_PHASE_EXIT};
if(_phase == ROCPROFILER_PHASE_ENTER)
_timestamp.begin = _timestamp_raw;
else
_timestamp.end = _timestamp_raw;
auto _external_cid = rocprofiler_tracer_external_id_t{0};
auto _activity_cid =
rocprofiler_tracer_activity_correlation_id_t{_data->correlation_id};
const char* _name = nullptr;
_domain.user_sync_callback(rocprofiler_record_tracer_t{_header,
_external_cid,
ACTIVITY_DOMAIN_HIP_API,
_op_id,
_api_data,
_activity_cid,
_timestamp,
_agent_id,
_queue_id,
_thread_id,
_phase,
_name},
_context);
}
*/
}
}
void
InitRoctracer()
{
for(const auto& itr : rocprofiler::internal::get_registered_configs())
{
if(!itr) continue;
// below should be valid so this might need to raise error
if(!itr->domain) continue;
for(auto ditr : {ROCPROFILER_TRACER_ACTIVITY_DOMAIN_HSA_API,
ROCPROFILER_TRACER_ACTIVITY_DOMAIN_HIP_API,
ROCPROFILER_TRACER_ACTIVITY_DOMAIN_ROCTX})
{
if((*itr->domain)(ditr))
{
if(itr->domain->user_sync_callback)
{
// ...
}
else
{
// ...
}
}
}
for(auto ditr : {ROCPROFILER_TRACER_ACTIVITY_DOMAIN_HSA_OPS,
ROCPROFILER_TRACER_ACTIVITY_DOMAIN_HIP_OPS})
{
if((*itr->domain)(ditr))
{
if(itr->domain->opcodes.none())
{
// ...
}
else
{
for(size_t i = 0; i < itr->domain->opcodes.size(); ++i)
{
if((*itr->domain)(ditr, i))
{
// ...
}
}
}
}
}
}
}
}