Add queue intercept support to the runtime.

Queue intercept is exposed as two tools-only APIs via the API
intercept table.

Change-Id: Iac9602ed3143974d85c3569e9092295ad18037f8
This commit is contained in:
Sean Keely
2017-10-17 22:09:12 -05:00
parent 473be763ff
commit 0c7dde2d1f
16 changed files with 800 additions and 19 deletions
+1
View File
@@ -135,6 +135,7 @@ set ( SRCS "core/util/lnx/os_linux.cpp"
"core/runtime/hsa_ext_amd.cpp"
"core/runtime/hsa_ext_interface.cpp"
"core/runtime/interrupt_signal.cpp"
"core/runtime/intercept_queue.cpp"
"core/runtime/ipc_signal.cpp"
"core/runtime/isa.cpp"
"core/runtime/runtime.cpp"
@@ -1102,3 +1102,19 @@ hsa_status_t HSA_API hsa_amd_register_system_event_handler(
void* data) {
return amdExtTable->hsa_amd_register_system_event_handler_fn(type, callback, data);
}
// Mirrors Amd Extension Apis
hsa_status_t hsa_amd_queue_intercept_create(
hsa_agent_t agent_handle, uint32_t size, hsa_queue_type32_t type,
void (*callback)(hsa_status_t status, hsa_queue_t* source, void* data), void* data,
uint32_t private_segment_size, uint32_t group_segment_size, hsa_queue_t** queue) {
return amdExtTable->hsa_amd_queue_intercept_create_fn(
agent_handle, size, type, callback, data, private_segment_size, group_segment_size, queue);
}
// Mirrors Amd Extension Apis
hsa_status_t hsa_amd_queue_intercept_register(hsa_queue_t* queue,
hsa_amd_queue_intercept_handler callback,
void* user_data) {
return amdExtTable->hsa_amd_queue_intercept_register_fn(queue, callback, user_data);
}
+3 -2
View File
@@ -43,11 +43,12 @@
#ifndef HSA_RUNTME_CORE_INC_SHARED_H_
#define HSA_RUNTME_CORE_INC_SHARED_H_
#include "core/util/utils.h"
#include <assert.h>
#include <cstring>
#include <functional>
#include <memory>
#include "core/util/utils.h"
namespace core {
/// @brief Base class encapsulating the allocator and deallocator for
+3 -1
View File
@@ -59,6 +59,8 @@ class AqlQueue : public core::Queue, private core::LocalSignal, public core::Sig
return signal->IsType(&rtti_id_);
}
static __forceinline bool IsType(core::Queue* queue) { return queue->IsType(&rtti_id_); }
// Acquires/releases queue resources and requests HW schedule/deschedule.
AqlQueue(GpuAgent* agent, size_t req_size_pkts, HSAuint32 node_id,
ScratchInfo& scratch, core::HsaEventCallback callback,
@@ -337,7 +339,7 @@ class AqlQueue : public core::Queue, private core::LocalSignal, public core::Sig
}
protected:
bool _IsA(rtti_t id) const override { return id == &rtti_id_; }
bool _IsA(Queue::rtti_t id) const override { return id == &rtti_id_; }
/// @brief Disallow destroying doorbell apart from its queue.
void doDestroySignal() override { assert(false); }
@@ -51,6 +51,8 @@
namespace core {
class HostQueue : public Queue {
public:
static __forceinline bool IsType(core::Queue* queue) { return queue->IsType(&rtti_id_); }
HostQueue(hsa_region_t region, uint32_t ring_size, hsa_queue_type32_t type,
uint32_t features, hsa_signal_t doorbell_signal);
@@ -158,7 +160,11 @@ class HostQueue : public Queue {
void operator delete(void*, void*) {}
protected:
bool _IsA(Queue::rtti_t id) const { return id == &rtti_id_; }
private:
static int rtti_id_;
static const size_t kRingAlignment = 256;
const uint32_t size_;
bool active_;
@@ -0,0 +1,413 @@
////////////////////////////////////////////////////////////////////////////////
//
// The University of Illinois/NCSA
// Open Source License (NCSA)
//
// Copyright (c) 2014-2015, Advanced Micro Devices, Inc. All rights reserved.
//
// Developed by:
//
// AMD Research and AMD HSA Software Development
//
// Advanced Micro Devices, Inc.
//
// www.amd.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal with the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// - Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimers.
// - Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimers in
// the documentation and/or other materials provided with the distribution.
// - Neither the names of Advanced Micro Devices, Inc,
// nor the names of its contributors may be used to endorse or promote
// products derived from this Software without specific prior written
// permission.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS WITH THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////
#ifndef HSA_RUNTIME_CORE_INC_INTERCEPT_QUEUE_H_
#define HSA_RUNTIME_CORE_INC_INTERCEPT_QUEUE_H_
#include <vector>
#include <memory>
#include <utility>
#include "core/inc/runtime.h"
#include "core/inc/queue.h"
#include "core/inc/signal.h"
#include "core/inc/interrupt_signal.h"
#include "core/inc/exceptions.h"
#include "core/util/locks.h"
namespace core {
// @brief Generic container to forward Queue interfaces into Queue* member.
// Class only has utility as a base type customized Queue wrappers.
class QueueWrapper : public Queue {
public:
std::unique_ptr<Queue> wrapped;
explicit QueueWrapper(std::unique_ptr<Queue> queue) : Queue(), wrapped(std::move(queue)) {
if (Queue::Shared::shared_object() == NULL) {
return;
}
memcpy(&amd_queue_, &wrapped->amd_queue_, sizeof(amd_queue_t));
wrapped->set_public_handle(wrapped.get(), public_handle_);
}
~QueueWrapper() {}
hsa_status_t Inactivate() override { return wrapped->Inactivate(); }
uint64_t LoadReadIndexAcquire() override { return wrapped->LoadReadIndexAcquire(); }
uint64_t LoadReadIndexRelaxed() override { return wrapped->LoadReadIndexRelaxed(); }
uint64_t LoadWriteIndexRelaxed() override { return wrapped->LoadWriteIndexRelaxed(); }
uint64_t LoadWriteIndexAcquire() override { return wrapped->LoadWriteIndexAcquire(); }
void StoreReadIndexRelaxed(uint64_t value) override {
return wrapped->StoreReadIndexRelaxed(value);
}
void StoreReadIndexRelease(uint64_t value) override {
return wrapped->StoreReadIndexRelease(value);
}
void StoreWriteIndexRelaxed(uint64_t value) override {
return wrapped->StoreWriteIndexRelaxed(value);
}
void StoreWriteIndexRelease(uint64_t value) override {
return wrapped->StoreWriteIndexRelease(value);
}
uint64_t CasWriteIndexAcqRel(uint64_t expected, uint64_t value) override {
return wrapped->CasWriteIndexAcqRel(expected, value);
}
uint64_t CasWriteIndexAcquire(uint64_t expected, uint64_t value) override {
return wrapped->CasWriteIndexAcquire(expected, value);
}
uint64_t CasWriteIndexRelaxed(uint64_t expected, uint64_t value) override {
return wrapped->CasWriteIndexRelaxed(expected, value);
}
uint64_t CasWriteIndexRelease(uint64_t expected, uint64_t value) override {
return wrapped->CasWriteIndexRelease(expected, value);
}
uint64_t AddWriteIndexAcqRel(uint64_t value) override {
return wrapped->AddWriteIndexAcqRel(value);
}
uint64_t AddWriteIndexAcquire(uint64_t value) override {
return wrapped->AddWriteIndexAcquire(value);
}
uint64_t AddWriteIndexRelaxed(uint64_t value) override {
return wrapped->AddWriteIndexRelaxed(value);
}
uint64_t AddWriteIndexRelease(uint64_t value) override {
return wrapped->AddWriteIndexRelease(value);
}
hsa_status_t SetCUMasking(const uint32_t num_cu_mask_count, const uint32_t* cu_mask) override {
return wrapped->SetCUMasking(num_cu_mask_count, cu_mask);
}
void ExecutePM4(uint32_t* cmd_data, size_t cmd_size_b) override {
wrapped->ExecutePM4(cmd_data, cmd_size_b);
}
protected:
void do_set_public_handle(hsa_queue_t* handle) {
public_handle_ = handle;
wrapped->set_public_handle(wrapped.get(), handle);
}
};
// @brief Generic container for a proxy queue.
// Presents an proxy packet buffer and doorbell signal for an underlying Queue. Write index
// operations act on the proxy buffer while all other operations pass through to the underlying
// queue.
class QueueProxy : public QueueWrapper {
public:
explicit QueueProxy(std::unique_ptr<Queue> queue) : QueueWrapper(std::move(queue)) {}
uint64_t LoadReadIndexAcquire() override {
return atomic::Load(&amd_queue_.read_dispatch_id, std::memory_order_acquire);
}
uint64_t LoadReadIndexRelaxed() override {
return atomic::Load(&amd_queue_.read_dispatch_id, std::memory_order_relaxed);
}
void StoreReadIndexRelaxed(uint64_t value) override { assert(false); }
void StoreReadIndexRelease(uint64_t value) override { assert(false); }
uint64_t LoadWriteIndexRelaxed() override {
return atomic::Load(&amd_queue_.write_dispatch_id, std::memory_order_relaxed);
}
uint64_t LoadWriteIndexAcquire() override {
return atomic::Load(&amd_queue_.write_dispatch_id, std::memory_order_acquire);
}
void StoreWriteIndexRelaxed(uint64_t value) override {
atomic::Store(&amd_queue_.write_dispatch_id, value, std::memory_order_relaxed);
}
void StoreWriteIndexRelease(uint64_t value) override {
atomic::Store(&amd_queue_.write_dispatch_id, value, std::memory_order_release);
}
uint64_t CasWriteIndexAcqRel(uint64_t expected, uint64_t value) override {
return atomic::Cas(&amd_queue_.write_dispatch_id, value, expected, std::memory_order_acq_rel);
}
uint64_t CasWriteIndexAcquire(uint64_t expected, uint64_t value) override {
return atomic::Cas(&amd_queue_.write_dispatch_id, value, expected, std::memory_order_acquire);
}
uint64_t CasWriteIndexRelaxed(uint64_t expected, uint64_t value) override {
return atomic::Cas(&amd_queue_.write_dispatch_id, value, expected, std::memory_order_relaxed);
}
uint64_t CasWriteIndexRelease(uint64_t expected, uint64_t value) override {
return atomic::Cas(&amd_queue_.write_dispatch_id, value, expected, std::memory_order_release);
}
uint64_t AddWriteIndexAcqRel(uint64_t value) override {
return atomic::Add(&amd_queue_.write_dispatch_id, value, std::memory_order_acq_rel);
}
uint64_t AddWriteIndexAcquire(uint64_t value) override {
return atomic::Add(&amd_queue_.write_dispatch_id, value, std::memory_order_acquire);
}
uint64_t AddWriteIndexRelaxed(uint64_t value) override {
return atomic::Add(&amd_queue_.write_dispatch_id, value, std::memory_order_relaxed);
}
uint64_t AddWriteIndexRelease(uint64_t value) override {
return atomic::Add(&amd_queue_.write_dispatch_id, value, std::memory_order_release);
}
};
// @brief Provides packet intercept and rewrite capability for a queue.
// Host-side dispatches are processed during doorbell ring.
// Device-side dispatches are processed as an asynchronous signal event.
class InterceptQueue : public QueueProxy, private LocalSignal, public Signal {
public:
// typedef void(*intercept_packet_writer)(const AqlPacket* pkts, uint64_t pkt_count);
// typedef void(*intercept_handler)(const AqlPacket* pkts, uint64_t pkt_count, uint64_t
// user_pkt_index, void* data, intercept_packet_writer writer);
std::vector<std::pair<hsa_amd_queue_intercept_handler, void*>> interceptors;
explicit InterceptQueue(std::unique_ptr<Queue> queue);
~InterceptQueue();
void AddInterceptor(hsa_amd_queue_intercept_handler interceptor, void* data) {
interceptors.push_back(std::make_pair(interceptor, data));
}
hsa_status_t Inactivate() override {
active_ = false;
return wrapped->Inactivate();
}
private:
// Serialize packet interception processing.
KernelMutex lock_;
// Largest processed packet index.
uint64_t next_packet_;
// Post interception packet overflow buffer
std::vector<AqlPacket> overflow_;
// Index at which async intercept processing was scheduled.
uint64_t retry_index_;
// Event signal to use for async packet processing and control flag.
InterruptSignal* async_doorbell_;
std::atomic<bool> quit_;
// Indicates queue active/inactive state.
std::atomic<bool> active_;
static const hsa_signal_value_t DOORBELL_MAX = 0xFFFFFFFFFFFFFFFFull;
static bool HandleAsyncDoorbell(hsa_signal_value_t value, void* arg);
static void PacketWriter(const void* pkts, uint64_t pkt_count);
bool Submit(const AqlPacket* packets, uint64_t count);
static void Submit(const void* pkts, uint64_t pkt_count, uint64_t user_pkt_index, void* data,
hsa_amd_queue_intercept_packet_writer writer);
/*
* Remaining Queue and Signal interface definitions.
*/
public:
/// @brief Update signal value using Relaxed semantics
///
/// @param value Value of signal to update with
void StoreRelaxed(hsa_signal_value_t value);
/// @brief Update signal value using Release semantics
///
/// @param value Value of signal to update with
void StoreRelease(hsa_signal_value_t value) {
std::atomic_thread_fence(std::memory_order_release);
StoreRelaxed(value);
}
/// @brief This operation is illegal
hsa_signal_value_t LoadRelaxed() {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t LoadAcquire() {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t WaitRelaxed(hsa_signal_condition_t condition, hsa_signal_value_t compare_value,
uint64_t timeout, hsa_wait_state_t wait_hint) {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t WaitAcquire(hsa_signal_condition_t condition, hsa_signal_value_t compare_value,
uint64_t timeout, hsa_wait_state_t wait_hint) {
assert(false);
return 0;
}
/// @brief This operation is illegal
void AndRelaxed(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void AndAcquire(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void AndRelease(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void AndAcqRel(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void OrRelaxed(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void OrAcquire(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void OrRelease(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void OrAcqRel(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void XorRelaxed(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void XorAcquire(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void XorRelease(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void XorAcqRel(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void AddRelaxed(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void AddAcquire(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void AddRelease(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void AddAcqRel(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void SubRelaxed(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void SubAcquire(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void SubRelease(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
void SubAcqRel(hsa_signal_value_t value) { assert(false); }
/// @brief This operation is illegal
hsa_signal_value_t ExchRelaxed(hsa_signal_value_t value) {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t ExchAcquire(hsa_signal_value_t value) {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t ExchRelease(hsa_signal_value_t value) {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t ExchAcqRel(hsa_signal_value_t value) {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t CasRelaxed(hsa_signal_value_t expected, hsa_signal_value_t value) {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t CasAcquire(hsa_signal_value_t expected, hsa_signal_value_t value) {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t CasRelease(hsa_signal_value_t expected, hsa_signal_value_t value) {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t CasAcqRel(hsa_signal_value_t expected, hsa_signal_value_t value) {
assert(false);
return 0;
}
/// @brief This operation is illegal
hsa_signal_value_t* ValueLocation() const {
assert(false);
return NULL;
}
/// @brief This operation is illegal
HsaEvent* EopEvent() {
assert(false);
return NULL;
}
static __forceinline bool IsType(core::Signal* signal) { return signal->IsType(&rtti_id_); }
static __forceinline bool IsType(core::Queue* queue) { return queue->IsType(&rtti_id_); }
protected:
bool _IsA(Queue::rtti_t id) const { return id == &rtti_id_; }
private:
static int rtti_id_;
};
} // namespace core
#endif // HSA_RUNTIME_CORE_INC_INTERCEPT_QUEUE_H_
+6
View File
@@ -306,6 +306,10 @@ class Queue : public Checked<0xFA3906A679F9DB49>,
hsa_queue_t* public_handle() const { return public_handle_; }
typedef void* rtti_t;
bool IsType(rtti_t id) { return _IsA(id); }
protected:
static void set_public_handle(Queue* ptr, hsa_queue_t* handle) {
ptr->do_set_public_handle(handle);
@@ -315,6 +319,8 @@ class Queue : public Checked<0xFA3906A679F9DB49>,
}
hsa_queue_t* public_handle_;
virtual bool _IsA(rtti_t id) const = 0;
private:
DISALLOW_COPY_AND_ASSIGN(Queue);
};
+1
View File
@@ -50,6 +50,7 @@
#include "core/inc/hsa_ext_interface.h"
#include "core/inc/hsa_internal.h"
#include "core/inc/hsa_ext_amd_impl.h"
#include "core/inc/agent.h"
#include "core/inc/memory_region.h"
@@ -964,10 +964,8 @@ void GpuAgent::AcquireQueueScratch(ScratchInfo& scratch) {
// Attempt to trim the maximum number of concurrent waves to allow scratch to fit.
// This is somewhat dangerous as it limits the number of concurrent waves from future dispatches
// on the queue if those waves use even small amounts of scratch.
#ifndef NDEBUG
if (core::Runtime::runtime_singleton_->flag().enable_queue_fault_message())
fprintf(stderr, "Failed to map requested scratch - reducing queue occupancy.\n");
#endif
debug_print("Failed to map requested scratch - reducing queue occupancy.\n");
uint64_t num_cus = properties_.NumFComputeCores / properties_.NumSIMDPerCU;
uint64_t size_per_wave = AlignUp(scratch.size_per_thread * properties_.WaveFrontSize, 1024);
uint64_t total_waves = scratch.size / size_per_wave;
@@ -994,10 +992,8 @@ void GpuAgent::AcquireQueueScratch(ScratchInfo& scratch) {
// Failed to allocate minimal scratch
assert(scratch.queue_base == NULL && "bad scratch data");
#ifndef NDEBUG
if (core::Runtime::runtime_singleton_->flag().enable_queue_fault_message())
fprintf(stderr, "Could not allocate scratch for one wave per CU.\n");
#endif
debug_print("Could not allocate scratch for one wave per CU.\n");
}
void GpuAgent::ReleaseQueueScratch(void* base) {
@@ -47,6 +47,7 @@
namespace core {
int HostQueue::rtti_id_ = 0;
std::atomic<uint32_t> HostQueue::queue_count_(0x80000000);
HostQueue::HostQueue(hsa_region_t region, uint32_t ring_size,
@@ -47,6 +47,17 @@
#include <iostream>
// Tools only APIs.
namespace AMD {
hsa_status_t hsa_amd_queue_intercept_register(hsa_queue_t* queue,
hsa_amd_queue_intercept_handler callback,
void* user_data);
hsa_status_t hsa_amd_queue_intercept_create(
hsa_agent_t agent_handle, uint32_t size, hsa_queue_type32_t type,
void (*callback)(hsa_status_t status, hsa_queue_t* source, void* data), void* data,
uint32_t private_segment_size, uint32_t group_segment_size, hsa_queue_t** queue);
}
namespace core {
HsaApiTable hsa_api_table_;
@@ -382,6 +393,8 @@ void HsaApiTable::UpdateAmdExts() {
amd_ext_api.hsa_amd_ipc_signal_create_fn = AMD::hsa_amd_ipc_signal_create;
amd_ext_api.hsa_amd_ipc_signal_attach_fn = AMD::hsa_amd_ipc_signal_attach;
amd_ext_api.hsa_amd_register_system_event_handler_fn = AMD::hsa_amd_register_system_event_handler;
amd_ext_api.hsa_amd_queue_intercept_create_fn = AMD::hsa_amd_queue_intercept_create;
amd_ext_api.hsa_amd_queue_intercept_register_fn = AMD::hsa_amd_queue_intercept_register;
}
class Init {
@@ -44,8 +44,8 @@
#include <typeinfo>
#include <exception>
#include <set>
#include "hsakmt.h"
#include <utility>
#include <memory>
#include "core/inc/runtime.h"
#include "core/inc/agent.h"
@@ -56,6 +56,7 @@
#include "core/inc/default_signal.h"
#include "core/inc/interrupt_signal.h"
#include "core/inc/ipc_signal.h"
#include "core/inc/intercept_queue.h"
#include "core/inc/exceptions.h"
template <class T>
@@ -134,12 +135,10 @@ hsa_status_t handleException() {
// Rethrow exceptions caught from callbacks.
// e.rethrow_nested();
// return HSA_STATUS_ERROR;
#ifndef NDEBUG
} catch (const std::exception& e) {
fprintf(stderr, "Unhandled exception: %s\n", e.what());
debug_print("Unhandled exception: %s\n", e.what());
assert(false && "Unhandled exception.");
return HSA_STATUS_ERROR;
#endif
} catch (...) {
assert(false && "Unhandled exception.");
abort();
@@ -798,6 +797,45 @@ hsa_status_t hsa_amd_ipc_signal_attach(const hsa_amd_ipc_signal_t* handle,
CATCH;
}
// For use by tools only - not in library export table.
hsa_status_t hsa_amd_queue_intercept_create(
hsa_agent_t agent_handle, uint32_t size, hsa_queue_type32_t type,
void (*callback)(hsa_status_t status, hsa_queue_t* source, void* data), void* data,
uint32_t private_segment_size, uint32_t group_segment_size, hsa_queue_t** queue) {
TRY;
hsa_queue_t* lower_queue;
hsa_status_t err = HSA::hsa_queue_create(agent_handle, size, type, callback, data,
private_segment_size, group_segment_size, &lower_queue);
if (err != HSA_STATUS_SUCCESS) return err;
std::unique_ptr<core::Queue> lowerQueue(core::Queue::Convert(lower_queue));
std::unique_ptr<core::InterceptQueue> upperQueue(new core::InterceptQueue(std::move(lowerQueue)));
if (!upperQueue->IsSharedObjectAllocationValid()) return HSA_STATUS_ERROR_OUT_OF_RESOURCES;
*queue = core::Queue::Convert(upperQueue.get());
upperQueue.release();
return HSA_STATUS_SUCCESS;
CATCH;
}
// For use by tools only - not in library export table.
hsa_status_t hsa_amd_queue_intercept_register(hsa_queue_t* queue,
hsa_amd_queue_intercept_handler callback,
void* user_data) {
TRY;
IS_OPEN();
IS_BAD_PTR(callback);
core::Queue* cmd_queue = core::Queue::Convert(queue);
IS_VALID(cmd_queue);
if (!core::InterceptQueue::IsType(cmd_queue)) return HSA_STATUS_ERROR_INVALID_QUEUE;
core::InterceptQueue* iQueue = static_cast<core::InterceptQueue*>(cmd_queue);
iQueue->AddInterceptor(callback, user_data);
return HSA_STATUS_SUCCESS;
CATCH;
}
hsa_status_t hsa_amd_register_system_event_handler(
hsa_amd_event_t type,
hsa_status_t (*callback)(const void* event_specific_data, void* data),
@@ -0,0 +1,260 @@
////////////////////////////////////////////////////////////////////////////////
//
// The University of Illinois/NCSA
// Open Source License (NCSA)
//
// Copyright (c) 2014-2015, Advanced Micro Devices, Inc. All rights reserved.
//
// Developed by:
//
// AMD Research and AMD HSA Software Development
//
// Advanced Micro Devices, Inc.
//
// www.amd.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal with the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// - Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimers.
// - Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimers in
// the documentation and/or other materials provided with the distribution.
// - Neither the names of Advanced Micro Devices, Inc,
// nor the names of its contributors may be used to endorse or promote
// products derived from this Software without specific prior written
// permission.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS WITH THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////
#include "core/inc/intercept_queue.h"
#include "core/util/utils.h"
namespace core {
struct InterceptFrame {
InterceptQueue* queue;
uint64_t pkt_index;
size_t interceptor_index;
};
static thread_local InterceptFrame Cursor = {nullptr, 0, 0};
static const uint16_t kInvalidHeader = (HSA_PACKET_TYPE_INVALID << HSA_PACKET_HEADER_TYPE) |
(1 << HSA_PACKET_HEADER_BARRIER) |
(HSA_FENCE_SCOPE_NONE << HSA_PACKET_HEADER_ACQUIRE_FENCE_SCOPE) |
(HSA_FENCE_SCOPE_NONE << HSA_PACKET_HEADER_RELEASE_FENCE_SCOPE);
static const uint16_t kBarrierHeader = (HSA_PACKET_TYPE_BARRIER_AND << HSA_PACKET_HEADER_TYPE) |
(1 << HSA_PACKET_HEADER_BARRIER) |
(HSA_FENCE_SCOPE_NONE << HSA_PACKET_HEADER_ACQUIRE_FENCE_SCOPE) |
(HSA_FENCE_SCOPE_NONE << HSA_PACKET_HEADER_RELEASE_FENCE_SCOPE);
static const hsa_barrier_and_packet_t kBarrierPacket = {kInvalidHeader, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int InterceptQueue::rtti_id_ = 0;
InterceptQueue::InterceptQueue(std::unique_ptr<Queue> queue)
: QueueProxy(std::move(queue)),
LocalSignal(0),
Signal(signal()),
next_packet_(0),
retry_index_(0),
quit_(false),
active_(true) {
if (Queue::Shared::shared_object() == NULL) {
return;
// TODO skeely: cleanup queue constructor failure.
// throw AMD::hsa_exception(HSA_STATUS_ERROR_OUT_OF_RESOURCES, "Failed to allocate Queue ABI
// block.\n");
}
void* buffer = Runtime::runtime_singleton_->system_allocator()(
wrapped->amd_queue_.hsa_queue.size * sizeof(hsa_agent_dispatch_packet_t), 4096,
MemoryRegion::AllocateNoFlags);
if (buffer == nullptr) throw std::bad_alloc();
MAKE_NAMED_SCOPE_GUARD(buffGuard,
[&]() { Runtime::runtime_singleton_->system_deallocator()(buffer); });
amd_queue_.hsa_queue.base_address = buffer;
// Match the queue's signal ABI block to async_doorbell_'s
// This allows us to use the queue's signal ABI block from devices to trigger async_doorbell while
// host side use jumps directly to the queue's signal implementation.
async_doorbell_ = new InterruptSignal(DOORBELL_MAX);
MAKE_NAMED_SCOPE_GUARD(sigGuard, [&]() { async_doorbell_->DestroySignal(); });
this->signal_ = async_doorbell_->signal_;
amd_queue_.hsa_queue.doorbell_signal = Signal::Convert(this);
// Install an async handler for device side dispatches.
auto err = Runtime::runtime_singleton_->SetAsyncSignalHandler(
core::Signal::Convert(async_doorbell_), HSA_SIGNAL_CONDITION_NE,
async_doorbell_->LoadRelaxed(), HandleAsyncDoorbell, this);
if (err != HSA_STATUS_SUCCESS)
throw AMD::hsa_exception(err, "Doorbell handler registration failed.\n");
// Install copy submission interceptor.
AddInterceptor(Submit, this);
sigGuard.Dismiss();
buffGuard.Dismiss();
}
InterceptQueue::~InterceptQueue() {
if (Queue::Shared::shared_object() == NULL) {
return;
}
active_ = false;
// Kill the async doorbell handler
// Doorbell may not be used during or after queue destroy, however an interrupt may be in flight.
// Ensure doorbell value is not 0, mark for exit, wake handler and wait for termination value.
async_doorbell_->StoreRelaxed(DOORBELL_MAX);
quit_ = true;
hsa_signal_value_t val = async_doorbell_->ExchRelaxed(1);
if (val != 0)
async_doorbell_->WaitRelaxed(HSA_SIGNAL_CONDITION_EQ, 0, -1, HSA_WAIT_STATE_BLOCKED);
async_doorbell_->DestroySignal();
// Release buffer resources
Runtime::runtime_singleton_->system_deallocator()(amd_queue_.hsa_queue.base_address);
}
bool InterceptQueue::HandleAsyncDoorbell(hsa_signal_value_t value, void* arg) {
InterceptQueue* queue = reinterpret_cast<InterceptQueue*>(arg);
if (queue->quit_) {
queue->async_doorbell_->StoreRelaxed(0);
return false;
}
queue->async_doorbell_->StoreRelaxed(DOORBELL_MAX);
queue->StoreRelease(value);
return true;
}
void InterceptQueue::PacketWriter(const void* pkts, uint64_t pkt_count) {
Cursor.interceptor_index--;
auto& handler = Cursor.queue->interceptors[Cursor.interceptor_index];
handler.first(pkts, pkt_count, Cursor.pkt_index, handler.second, PacketWriter);
}
void InterceptQueue::Submit(const void* pkts, uint64_t pkt_count, uint64_t user_pkt_index,
void* data, hsa_amd_queue_intercept_packet_writer writer) {
InterceptQueue* queue = reinterpret_cast<InterceptQueue*>(data);
const AqlPacket* packets = (const AqlPacket*)pkts;
// Submit final packet transform to hardware.
if (queue->Submit(packets, pkt_count)) return;
// Could not submit final packets, stash for later.
assert(queue->overflow_.empty() && "Packet intercept error: overflow buffer not empty.\n");
for (uint64_t i = 0; i < pkt_count; i++) queue->overflow_.push_back(packets[i]);
}
bool InterceptQueue::Submit(const AqlPacket* packets, uint64_t count) {
if (count == 0) return true;
AqlPacket* ring = reinterpret_cast<AqlPacket*>(wrapped->amd_queue_.hsa_queue.base_address);
uint64_t mask = wrapped->amd_queue_.hsa_queue.size - 1;
while (true) {
uint64_t write = wrapped->LoadWriteIndexRelaxed();
uint64_t read = wrapped->LoadReadIndexRelaxed();
uint64_t free_slots = wrapped->amd_queue_.hsa_queue.size - (write - read);
// If out of space defer packet insertion.
if (free_slots <= count) {
// If there is not already a pending retry point add one.
if (retry_index_ <= read) {
// Reserve and wait for one slot.
write = wrapped->AddWriteIndexRelaxed(1);
read = write - wrapped->amd_queue_.hsa_queue.size + 1;
while (wrapped->LoadReadIndexRelaxed() < read) os::YieldThread();
// Submit barrer which will wake async queue processing.
ring[write & mask].barrier_and = kBarrierPacket;
ring[write & mask].barrier_and.completion_signal = Signal::Convert(async_doorbell_);
atomic::Store(&ring[write & mask].barrier_and.header, kBarrierHeader,
std::memory_order_release);
HSA::hsa_signal_store_screlease(wrapped->amd_queue_.hsa_queue.doorbell_signal, write);
// Record the retry point
retry_index_ = write;
}
return false;
}
// Attempt to reserve useable queue space
uint64_t new_write = wrapped->CasWriteIndexRelaxed(write, write + count);
if (new_write == write) {
AqlPacket first = packets[0];
uint16_t header = first.dispatch.header;
first.dispatch.header = kInvalidHeader;
ring[write & mask] = first;
for (uint64_t i = 1; i < count; i++) ring[(write + i) & mask] = packets[i];
atomic::Store(&ring[write & mask].dispatch.header, header, std::memory_order_release);
HSA::hsa_signal_store_screlease(wrapped->amd_queue_.hsa_queue.doorbell_signal,
write + count - 1);
return true;
}
}
}
void InterceptQueue::StoreRelaxed(hsa_signal_value_t value) {
if (!active_) return;
// If called recursively defer to async doorbell thread.
if (Cursor.queue != nullptr) {
debug_print("Likely incorrect queue use observed in an interceptor.\n");
async_doorbell_->StoreRelaxed(value);
return;
}
ScopedAcquire<KernelMutex> lock(&lock_);
// Submit overflow packets.
if (!overflow_.empty()) {
if (!Submit(&overflow_[0], overflow_.size())) return;
overflow_.clear();
}
Cursor.queue = this;
AqlPacket* ring = reinterpret_cast<AqlPacket*>(amd_queue_.hsa_queue.base_address);
uint64_t mask = wrapped->amd_queue_.hsa_queue.size - 1;
// Loop over valid packets and process.
uint64_t end = LoadWriteIndexAcquire();
uint64_t i;
for (i = next_packet_; i < end; i++) {
if (!ring[i & mask].IsValid()) break;
// Process callbacks.
Cursor.interceptor_index = interceptors.size() - 1;
Cursor.pkt_index = i;
auto& handler = interceptors[Cursor.interceptor_index];
handler.first(&ring[i & mask], 1, i, handler.second, PacketWriter);
// Invalidate consumed packet
atomic::Store(&ring[i & mask].dispatch.header, kInvalidHeader, std::memory_order_release);
}
next_packet_ = i;
Cursor.queue = nullptr;
}
} // namespace core
+1 -3
View File
@@ -1320,12 +1320,10 @@ void Runtime::LoadTools() {
add = (tool_add_t)os::GetExportAddress(tool, "AddAgent");
if (add) add(this);
}
#ifndef NDEBUG
else {
if (flag().report_tool_load_failures())
fprintf(stderr, "Tool lib \"%s\" failed to load.\n", names[i].c_str());
debug_print("Tool lib \"%s\" failed to load.\n", names[i].c_str());
}
#endif
}
}
}
+16 -2
View File
@@ -78,12 +78,15 @@ static __forceinline void _aligned_free(void* ptr) { return free(ptr); }
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
#include "intrin.h"
#define __ALIGNED__(x) __declspec(align(x))
#if (_MSC_VER < 1800)
#if (_MSC_VER < 1800) // < VS 2013
static __forceinline unsigned long long int strtoull(const char* str,
char** endptr, int base) {
return static_cast<unsigned long long>(_strtoui64(str, endptr, base));
}
#endif
#if (_MSC_VER < 1900) // < VS 2015
#define thread_local __declspec(thread)
#endif
#else
#error "Compiler and/or processor not identified."
#endif
@@ -102,7 +105,18 @@ static __forceinline unsigned long long int strtoull(const char* str,
if (!(exp)) \
fprintf(stderr, "Warning: " STRING(exp) " in %s, " __FILE__ ":" STRING(__LINE__) "\n", \
__PRETTY_FUNCTION__); \
} while (false);
} while (false)
#endif
#ifdef NDEBUG
#define debug_print(fmt, ...) \
do { \
} while (false)
#else
#define debug_print(fmt, ...) \
do { \
fprintf(stderr, fmt, ##__VA_ARGS__); \
} while (false)
#endif
// A macro to disallow the copy and move constructor and operator= functions
+15
View File
@@ -81,6 +81,19 @@ static inline uint32_t Min(const uint32_t a, const uint32_t b) {
return (a > b) ? b : a;
}
// Declarations of APIs intended for use only by tools.
typedef void (*hsa_amd_queue_intercept_packet_writer)(const void* pkts, uint64_t pkt_count);
typedef void (*hsa_amd_queue_intercept_handler)(const void* pkts, uint64_t pkt_count,
uint64_t user_pkt_index, void* data,
hsa_amd_queue_intercept_packet_writer writer);
hsa_status_t hsa_amd_queue_intercept_register(hsa_queue_t* queue,
hsa_amd_queue_intercept_handler callback,
void* user_data);
hsa_status_t hsa_amd_queue_intercept_create(
hsa_agent_t agent_handle, uint32_t size, hsa_queue_type32_t type,
void (*callback)(hsa_status_t status, hsa_queue_t* source, void* data), void* data,
uint32_t private_segment_size, uint32_t group_segment_size, hsa_queue_t** queue);
// Structure of Version used to identify an instance of Api table
// Must be the first member (offsetof == 0) of all API tables.
// This is the root of the table passing ABI.
@@ -170,6 +183,8 @@ struct AmdExtTable {
decltype(hsa_amd_ipc_signal_create)* hsa_amd_ipc_signal_create_fn;
decltype(hsa_amd_ipc_signal_attach)* hsa_amd_ipc_signal_attach_fn;
decltype(hsa_amd_register_system_event_handler)* hsa_amd_register_system_event_handler_fn;
decltype(hsa_amd_queue_intercept_create)* hsa_amd_queue_intercept_create_fn;
decltype(hsa_amd_queue_intercept_register)* hsa_amd_queue_intercept_register_fn;
};
// Table to export HSA Core Runtime Apis