Files
TheRock/compiler/amd-llvm/offload/include/OpenMP/OMPT/OmptTracingBuffer.h
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

413 lines
15 KiB
C++

//===- OmptTracingBuffer.h - Target independent OpenMP target RTL -- C++ *-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Interface to be used for generating and flushing OMPT device trace records.
//
//===----------------------------------------------------------------------===//
#ifndef OPENMP_LIBOMPTARGET_OMPTTRACINGBUFFER_H
#define OPENMP_LIBOMPTARGET_OMPTTRACINGBUFFER_H
#ifdef OMPT_SUPPORT
#include <atomic>
#include <cassert>
#include <condition_variable>
#include <cstdint>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <omp-tools.h>
#include "Shared/EnvironmentVar.h"
// Maximum number of devices supported in device tracing. No device tracing
// will be performed for any device-id larger than 1023.
#define MAX_NUM_DEVICES 1024
// TODO Start with 1 helper thread and add dynamically if required
// Number of helper threads must not execeed 32 since the
// thread-wait-tracker is 32 bits in length.
#define OMPT_NUM_HELPER_THREADS 1
/*
* Buffer manager for trace records generated by OpenMP master and
* worker threads. During device init, a tool may register a
* buffer-request and a buffer-completion callback. The buffer-request
* callback should be used to allocate new buffers as required. The
* buffer-complete callback should be used to return trace records to
* the tool.
*
* In addition to trace records, this class manages the helper threads
* for dispatching a range of trace records to the tool.
*/
class OmptTracingBufferMgr {
public:
/*
* A trace record (TR) holds the trace data. Its type
* can be ompt or native. Currently, only ompt type is implemented.
*/
/*
* A TR can be in the following states:
* TR_init: initial state
* TR_ready: An OpenMP thread marks a TR ready when it is done
* populating the TR
* TR_released: A helper thread marks a TR released after it has
* completed returning the TR to the tool
*/
enum TRStatus { TR_init, TR_ready, TR_released };
/*
* Metadata capturing the state of a buffer of trace records. Once a
* buffer is allocated by an OpenMP worker thread, trace records are
* carved out from that buffer by that same OpenMP thread alone. Thus
* the allocated buffer is thread-specific from the allocation/population
* standpoint. But it may be manipulated by helper threads.
*
* Id, DeviceId, Start, and TotalBytes are not changed once set.
* RemainingBytes could be written multiple times but only by the same
* thread. But Cursor and IsFull may be read/written by an OpenMP worker
* thread and read by helper threads. Hence, accesses of
* this 2nd set of locations need to be atomic or synchronized.
*/
struct Buffer {
uint64_t Id; // Unique identifier of the buffer
int64_t DeviceId; // Device for which this buffer is allocated
void *Start; // Start of allocated space for trace records
size_t TotalBytes; // Total number of bytes in the allocated space
size_t RemainingBytes; // Total number of unused bytes
// corresponding to Cursor
std::atomic<void *> Cursor; // Address of the last trace record carved out
std::atomic<bool> IsFull; // true if no more trace records can be
// accomodated, otherwise false
Buffer(uint64_t BufId, int64_t DevId, void *S, size_t Bytes, size_t Rem,
void *C, bool F)
: Id(BufId), DeviceId(DevId), Start(S), TotalBytes(Bytes),
RemainingBytes(Rem), Cursor(C), IsFull(F) {}
Buffer() = delete;
Buffer(const Buffer &) = delete;
Buffer &operator=(const Buffer &) = delete;
};
using BufPtr = std::shared_ptr<Buffer>;
private:
/// Envar to control whether a buffer should be flushed when it gets full.
BoolEnvar OMPX_FlushOnBufferFull;
/// Envar to control whether all buffers should be flushed during shutdown.
BoolEnvar OMPX_FlushOnShutdown;
// Internal variable for tracking threads to wait for flush
uint32_t ThreadFlushTracker;
// Internal variable for tracking threads shutting down
uint32_t ThreadShutdownTracker;
using MapId2Buf = std::map<uint64_t, BufPtr>;
// Map from id to corresponding buffer. The ids are assigned in
// increasing order of creation.
MapId2Buf Id2BufferMap;
// Trace record. We currently support OMPT data type only. The state
// (TRStatus type) is maintained inline in the trace record. The
// tool is expected to access only the OMPT record.
struct TraceRecord {
ompt_record_ompt_t TR;
std::atomic<TRStatus> TRState;
};
// Thread-specific array of pointers to a buffer. The buffer pointed to
// is the last one allocated by this thread for a given device. The ith
// element points to the buffer for the ith device. At most MAX_NUM_DEVICES
// devices are supported.
static thread_local BufPtr ArrayOfBufPtr[MAX_NUM_DEVICES];
/*
* A buffer is flushed when it fills up or when the tool invokes
* flush_trace. So it's possible that the same buffer may be flushed
* more than once. When a buffer is flushed the first time, a unique
* id (flush-id) is generated and assigned to that buffer. Even if
* it is flushed again, the previously assigned id is maintained for
* that buffer. This id is loosely used to determine the order in
* which the buffers are processed and the corresponding trace
* records released to the tool.
*/
struct FlushInfo {
uint64_t FlushId;
void *FlushCursor;
BufPtr FlushBuf;
FlushInfo() = default;
FlushInfo(uint64_t Id, void *CR, BufPtr Buf)
: FlushId{Id}, FlushCursor{CR}, FlushBuf{Buf} {}
};
/*
* A buffer may be in the following states:
* Flush_waiting: when a buffer is flushed, either because it is
* full or because the tool invokes ompt_flush_trace
* Flush_processing: when a helper thread claims the waiting buffer
* and is in the process of dispatching buffer-completion callbacks
* on an associated range of trace records. If all trace records are
* not released, the state may be reset to Flush_waiting after the
* buffer-completion callbacks return
*/
enum BufferFlushStatus { Flush_waiting, Flush_processing };
struct FlushMd {
void *FlushCursor;
BufPtr FlushBuf;
BufferFlushStatus FlushStatus;
FlushMd(void *CR, BufPtr Buf, BufferFlushStatus Status)
: FlushCursor{CR}, FlushBuf{Buf}, FlushStatus{Status} {}
FlushMd() = delete;
};
using MapId2Md = std::map<uint64_t, FlushMd>;
/*
* A map from a flush-id to metadata containing the current
* cursor. the corresponding buffer, and its flushed status. If a
* buffer is flushed multiple times, the cursor is updated to the
* furthest one
*/
MapId2Md Id2FlushMdMap;
using UMapBufPtr2Id = std::unordered_map<BufPtr, uint64_t>;
// A hash map from a buffer address to the corresponding flush-id
UMapBufPtr2Id FlushBufPtr2IdMap;
using USetCursor = std::unordered_set<void *>;
USetCursor LastCursors;
using UMapThd2Id = std::unordered_map<std::thread::id, uint32_t>;
// A hash map from a helper thread id to an integer
UMapThd2Id HelperThreadIdMap;
// Mutex to protect Id2BufferMap and Cursor2BufMdMap
std::mutex BufferMgrMutex;
// Mutex to protect FlushBufPtr2IdMap and Id2FlushMdMap
std::mutex FlushMutex;
// Mutex to protect metadata tracking last cursors of buffer-completion
// callbacks
std::mutex LastCursorMutex;
// Condition variable used by helper thread to signal that flush is requested
std::condition_variable FlushCv;
// Condition variable used while waiting for flushing to complete
std::condition_variable ThreadFlushCv;
// Condition variable used while waiting for threads to shutdown
std::condition_variable ThreadShutdownCv;
// TODO Separate out the helper thread into its own class
std::vector<std::thread> CompletionThreads;
/// Called when a buffer \p Buf may be flushed with \p Cursor as the
/// last allocated trace record in the buffer.
/// triggerFlushOnBufferFull should be called without holding any lock.
void triggerFlushOnBufferFull(void *Cursor, BufPtr Buf);
// Called to dispatch buffer-completion callbacks for the trace records in
// this buffer
void flushBuffer(FlushInfo);
// Dispatch a buffer-completion callback with a range of trace records
void dispatchCallback(int64_t DeviceId, void *Buffer, void *FirstCursor,
void *LastCursor);
// Add a last cursor
void addLastCursor(void *Cursor) {
std::unique_lock<std::mutex> Lock(LastCursorMutex);
LastCursors.emplace(Cursor);
}
// Remove a last cursor
void removeLastCursor(void *Cursor) {
std::unique_lock<std::mutex> Lock(LastCursorMutex);
assert(LastCursors.find(Cursor) != LastCursors.end());
LastCursors.erase(Cursor);
}
// Given a trace record pointer, initialize its metadata
void initTraceRecordMetaData(void *Rec);
// Given a device-id, get/set a pointer to the last allocated buffer metadata.
BufPtr getDeviceSpecificBuffer(int64_t DevId);
void setDeviceSpecificBuffer(int64_t DevId, BufPtr Buf);
// Reserve a candidate buffer for flushing, preventing other helper threads
// from accessing it
FlushInfo findAndReserveFlushedBuf(uint64_t FlushId);
// Unreserve a buffer so that other helper threads can process it
void unreserveFlushedBuf(const FlushInfo &);
// All done with this buffer, so the buffer and its metadata can be removed
void destroyFlushedBuf(const FlushInfo &);
// Add a new buffer by an OpenMP thread so that a helper thread can process it
uint64_t addNewFlushEntry(BufPtr Buf, void *Cursor);
// Get the next trace record
void *getNextTR(void *TR);
// Given a buffer, return the latest cursor
void *getBufferCursor(BufPtr);
// Is no more space remaining for trace records in this buffer?
bool isBufferFull(const FlushInfo &);
// Have all trace records in this buffer been returned to the tool?
bool isBufferOwned(const FlushInfo &);
// Dispatch a buffer-completion callback and indicate that the buffer can be
// deallocated
void dispatchBufferOwnedCallback(const FlushInfo &);
// Main entry point for a helper thread
void driveCompletion();
// Examine the flushed buffers and dispatch buffer-completion callbacks
void invokeCallbacks();
// The caller does not hold a lock while calling this method
void waitForFlushCompletion();
// Given a thread number, set the corresponding bit in the flush
// tracker. The caller must hold the flush lock.
void setThreadFlush(uint32_t ThreadNum) {
ThreadFlushTracker |= (1 << ThreadNum);
}
// Reset this thread's flush bit. The caller must hold the flush lock
void resetThisThreadFlush() {
std::thread::id ID = std::this_thread::get_id();
assert(HelperThreadIdMap.find(ID) != HelperThreadIdMap.end());
ThreadFlushTracker &= ~(1 << HelperThreadIdMap[ID]);
}
// Given a thread number, set the corresponding bit in the shutdown
// tracker. The caller must hold the flush lock.
void setThreadShutdown(uint32_t ThreadNum) {
ThreadShutdownTracker |= (1 << ThreadNum);
}
// Reset this thread's shutdown bit. The caller must hold the flush
// lock
void resetThisThreadShutdown() {
std::thread::id ID = std::this_thread::get_id();
assert(HelperThreadIdMap.find(ID) != HelperThreadIdMap.end());
ThreadShutdownTracker &= ~(1 << HelperThreadIdMap[ID]);
}
// Return true if this thread's flush bit is set. The caller must
// hold the flush lock
bool isThisThreadFlushWaitedUpon() {
std::thread::id ID = std::this_thread::get_id();
assert(HelperThreadIdMap.find(ID) != HelperThreadIdMap.end());
return (ThreadFlushTracker & (1 << HelperThreadIdMap[ID])) != 0;
}
// Return true if this thread's shutdown bit is set. The caller must
// hold the flush lock
bool isThisThreadShutdownWaitedUpon() {
std::thread::id ID = std::this_thread::get_id();
assert(HelperThreadIdMap.find(ID) != HelperThreadIdMap.end());
return (ThreadShutdownTracker & (1 << HelperThreadIdMap[ID])) != 0;
}
// The caller must not hold the flush lock
bool amIHelperThread() {
std::unique_lock<std::mutex> flush_lock(FlushMutex);
if (HelperThreadIdMap.find(std::this_thread::get_id()) !=
HelperThreadIdMap.end())
return true;
return false;
}
// The caller must not hold the flush lock
bool areHelperThreadsAvailable();
// The caller must hold the appropriate lock
void init();
// The caller must hold the flush lock
void createHelperThreads();
// The caller must hold the flush lock
void destroyHelperThreads();
public:
OmptTracingBufferMgr()
: OMPX_FlushOnBufferFull("LIBOMPTARGET_OMPT_FLUSH_ON_BUFFER_FULL", true),
OMPX_FlushOnShutdown("LIBOMPTARGET_OMPT_FLUSH_ON_SHUTDOWN", true) {
// no need to hold locks for init() since object is getting constructed
// here.
init();
}
OmptTracingBufferMgr(const OmptTracingBufferMgr &) = delete;
OmptTracingBufferMgr &operator=(const OmptTracingBufferMgr &) = delete;
// The caller must not hold the flush lock
void startHelperThreads();
// The caller must not hold the flush lock. The helper threads are shut down
// without flushing any outstanding trace records.
void shutdownHelperThreads();
// The caller must not hold the flush lock. The helper threads are shut down
// after flushing all outstanding trace records for all devices.
void flushAndShutdownHelperThreads();
// Assign a cursor for a new trace record. This will assign a trace record
// for the provided device-id, allocating a new buffer if required.
void *assignCursor(ompt_callbacks_t Type, int64_t DeviceId);
// Get the size of a trace record
size_t getTRSize() { return sizeof(TraceRecord); }
// Get the status of a trace record. This function does not acquire
// a lock. If locking is required, the caller must hold a lock.
TRStatus getTRStatus(void *Rec);
// Set the status of a trace record. This function does not acquire
// a lock. If locking is required, the caller must hold a lock.
void setTRStatus(void *Rec, TRStatus);
// Is this a last cursor of a buffer completion callback?
bool isLastCursor(void *Cursor) {
std::unique_lock<std::mutex> Lock(LastCursorMutex);
return LastCursors.find(Cursor) != LastCursors.end();
}
// Called for flushing outstanding buffers for the provided device-id.
int flushAllBuffers(int DeviceId);
};
#else
class OmptTracingBufferMgr {};
#endif // OMPT_SUPPORT
#endif // OPENMP_LIBOMPTARGET_OMPTTRACINGBUFFER_H