/* Copyright (c) 2015 - 2021 Advanced Micro Devices, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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 AUTHORS 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 IN THE SOFTWARE. */ #include #include "hip_event.hpp" #if !defined(_MSC_VER) #include #endif void ipcEventCallback(hipStream_t stream, hipError_t status, void* user_data) { std::atomic *signal = reinterpret_cast*>(user_data); signal->store(0); return; } // ================================================================================================ bool createIpcEventShmemIfNeeded(hip::Event::ihipIpcEvent_t& ipc_evt) { #if !defined(_MSC_VER) if (ipc_evt.ipc_shmem_) { // ipc_shmem_ already created, no need to create it again return true; } char name_template[] = "/tmp/eventXXXXXX"; int temp_fd = mkstemp(name_template); ipc_evt.ipc_name_ = name_template; ipc_evt.ipc_name_.replace(0, 5, "/hip_"); if (!amd::Os::MemoryMapFileTruncated(ipc_evt.ipc_name_.c_str(), const_cast (reinterpret_cast(&(ipc_evt.ipc_shmem_))), sizeof(hip::ihipIpcEventShmem_t))) { return false; } ipc_evt.ipc_shmem_->owners = 1; ipc_evt.ipc_shmem_->read_index = -1; ipc_evt.ipc_shmem_->write_index = 0; for (uint32_t sig_idx = 0; sig_idx < IPC_SIGNALS_PER_EVENT; ++sig_idx) { ipc_evt.ipc_shmem_->signal[sig_idx] = 0; } close(temp_fd); return true; #else return false; #endif } namespace hip { bool Event::ready() { if (event_->status() != CL_COMPLETE) { event_->notifyCmdQueue(); } // Check HW status of the ROCcrl event. Note: not all ROCclr modes support HW status bool ready = g_devices[deviceId()]->devices()[0]->IsHwEventReady(*event_); if (!ready) { ready = (event_->status() == CL_COMPLETE); } return ready; } hipError_t Event::query() { amd::ScopedLock lock(lock_); // If event is not recorded, event_ is null, hence return hipSuccess if (event_ == nullptr) { return hipSuccess; } return ready() ? hipSuccess : hipErrorNotReady; } hipError_t Event::synchronize() { amd::ScopedLock lock(lock_); // If event is not recorded, event_ is null, hence return hipSuccess if (event_ == nullptr) { return hipSuccess; } // Check HW status of the ROCcrl event. Note: not all ROCclr modes support HW status static constexpr bool kWaitCompletion = true; if (!g_devices[deviceId()]->devices()[0]->IsHwEventReady(*event_, kWaitCompletion)) { event_->awaitCompletion(); } return hipSuccess; } hipError_t Event::elapsedTime(Event& eStop, float& ms) { amd::ScopedLock startLock(lock_); if (this == &eStop) { if (event_ == nullptr) { return hipErrorInvalidHandle; } if (flags & hipEventDisableTiming) { return hipErrorInvalidHandle; } if (!ready()) { return hipErrorNotReady; } ms = 0.f; return hipSuccess; } amd::ScopedLock stopLock(eStop.lock_); if (event_ == nullptr || eStop.event_ == nullptr) { return hipErrorInvalidHandle; } if ((flags | eStop.flags) & hipEventDisableTiming) { return hipErrorInvalidHandle; } if (!ready() || !eStop.ready()) { return hipErrorNotReady; } if (event_ == eStop.event_ && recorded_ && eStop.recorded_) { // Events are the same, which indicates the stream is empty and likely // eventRecord is called on another stream. For such cases insert and measure a // marker. amd::Command* command = new amd::Marker(*event_->command().queue(), kMarkerDisableFlush); command->enqueue(); command->awaitCompletion(); ms = static_cast(static_cast(command->event().profilingInfo().end_) - time())/1000000.f; command->release(); } else { // Note: with direct dispatch eStop.ready() relies on HW event, but CPU status can be delayed. // Hence for now make sure CPU status is updated by calling awaitCompletion(); event_->awaitCompletion(); eStop.event_->awaitCompletion(); ms = static_cast(eStop.time() - time())/1000000.f; } return hipSuccess; } int64_t Event::time() const { assert(event_ != nullptr); if (recorded_) { return static_cast(event_->profilingInfo().end_); } else { return static_cast(event_->profilingInfo().start_); } } hipError_t Event::streamWaitCommand(amd::Command*& command, amd::HostQueue* queue) { if (flags & hipEventInterprocess) { command = new amd::Marker(*queue, false); } else { amd::Command::EventWaitList eventWaitList; eventWaitList.push_back(event_); command = new amd::Marker(*queue, kMarkerDisableFlush, eventWaitList); } if (command == NULL) { return hipErrorOutOfMemory; } return hipSuccess; } hipError_t Event::enqueueStreamWaitCommand(hipStream_t stream, amd::Command* command) { if (flags & hipEventInterprocess) { auto t{new CallbackData{ipc_evt_.ipc_shmem_->read_index, ipc_evt_.ipc_shmem_}}; StreamCallback* cbo = new StreamCallback( stream, reinterpret_cast(WaitThenDecrementSignal), t, command); if (!command->setCallback(CL_COMPLETE, ihipStreamCallback, cbo)) { command->release(); return hipErrorInvalidHandle; } command->enqueue(); command->awaitCompletion(); return hipSuccess; } else { command->enqueue(); } return hipSuccess; } hipError_t Event::streamWait(hipStream_t stream, uint flags) { amd::HostQueue* queue = hip::getQueue(stream); if ((event_ == nullptr) || (event_->command().queue() == queue)) { return hipSuccess; } amd::ScopedLock lock(lock_); if (!(this->flags & hipEventInterprocess)) { if (!event_->notifyCmdQueue()) { return hipErrorLaunchOutOfResources; } } amd::Command* command; streamWaitCommand(command, queue); enqueueStreamWaitCommand(stream, command); if (!(this->flags & hipEventInterprocess)) { command->release(); } return hipSuccess; } hipError_t Event::recordCommand(amd::Command*& command, amd::HostQueue* queue) { bool recorded = isRecorded(); if ((flags & hipEventInterprocess) && !recorded) { command = new amd::Marker(*queue, kMarkerDisableFlush); } else if (command == nullptr) { static constexpr bool kRecordExplicitGpuTs = true; // Always submit a EventMarker. command = new hip::EventMarker(*queue, !kMarkerDisableFlush, kRecordExplicitGpuTs); } return hipSuccess; } hipError_t Event::enqueueRecordCommand(hipStream_t stream, amd::Command* command, bool record) { amd::HostQueue* queue = hip::getQueue(stream); bool recorded = isRecorded(); if ((flags & hipEventInterprocess) && !recorded) { amd::Event& tEvent = command->event(); createIpcEventShmemIfNeeded(ipc_evt_); int write_index = ipc_evt_.ipc_shmem_->write_index++; int offset = write_index % IPC_SIGNALS_PER_EVENT; while (ipc_evt_.ipc_shmem_->signal[offset] != 0) { amd::Os::sleep(1); } // Lock signal. ipc_evt_.ipc_shmem_->signal[offset] = 1; ipc_evt_.ipc_shmem_->owners_device_id = deviceId(); std::atomic* signal = &ipc_evt_.ipc_shmem_->signal[offset]; StreamCallback* cbo = new StreamCallback( stream, reinterpret_cast(ipcEventCallback), signal, command); if (!tEvent.setCallback(CL_COMPLETE, ihipStreamCallback, cbo)) { command->release(); return hipErrorInvalidHandle; } command->enqueue(); tEvent.notifyCmdQueue(); // Add the new barrier to stall the stream, until the callback is done amd::Command::EventWaitList eventWaitList; eventWaitList.push_back(command); amd::Command* block_command = new amd::Marker(*queue, !kMarkerDisableFlush, eventWaitList); if (block_command == nullptr) { return hipErrorInvalidValue; } block_command->enqueue(); block_command->release(); // Update read index to indicate new signal. int expected = write_index - 1; while (!ipc_evt_.ipc_shmem_->read_index.compare_exchange_weak(expected, write_index)) { amd::Os::sleep(1); } } else { command->enqueue(); if (event_ == &command->event()) return hipSuccess; if (event_ != nullptr) { event_->release(); } event_ = &command->event(); recorded_ = record; } return hipSuccess; } hipError_t Event::addMarker(hipStream_t stream, amd::Command* command, bool record) { amd::HostQueue* queue = hip::getQueue(stream); // Keep the lock always at the beginning of this to avoid a race. SWDEV-277847 amd::ScopedLock lock(lock_); hipError_t status = recordCommand(command, queue); if (status != hipSuccess) { return hipSuccess; } status = enqueueRecordCommand(stream, command, record); return status; } } // namespace hip hipError_t ihipEventCreateWithFlags(hipEvent_t* event, unsigned flags) { if (event == nullptr) { return hipErrorInvalidValue; } #if !defined(_MSC_VER) unsigned supportedFlags = hipEventDefault | hipEventBlockingSync | hipEventDisableTiming | hipEventReleaseToDevice | hipEventReleaseToSystem | hipEventInterprocess; #else unsigned supportedFlags = hipEventDefault | hipEventBlockingSync | hipEventDisableTiming | hipEventReleaseToDevice | hipEventReleaseToSystem; #endif const unsigned releaseFlags = (hipEventReleaseToDevice | hipEventReleaseToSystem); const bool illegalFlags = (flags & ~supportedFlags) || // can't set any unsupported flags. (flags & releaseFlags) == releaseFlags; // can't set both release flags if (!illegalFlags) { hip::Event* e = new hip::Event(flags); if (e == nullptr) { return hipErrorOutOfMemory; } *event = reinterpret_cast(e); } else { return hipErrorInvalidValue; } return hipSuccess; } hipError_t ihipEventQuery(hipEvent_t event) { if (event == nullptr) { return hipErrorInvalidHandle; } hip::Event* e = reinterpret_cast(event); if ((e->flags & hipEventInterprocess) && (e->ipc_evt_.ipc_shmem_)) { int prev_read_idx = e->ipc_evt_.ipc_shmem_->read_index; int offset = (prev_read_idx % IPC_SIGNALS_PER_EVENT); if (e->ipc_evt_.ipc_shmem_->read_index < prev_read_idx+IPC_SIGNALS_PER_EVENT && e->ipc_evt_.ipc_shmem_->signal[offset] != 0) { return hipErrorNotReady; } return hipSuccess; } else { return e->query(); } } hipError_t hipEventCreateWithFlags(hipEvent_t* event, unsigned flags) { HIP_INIT_API(hipEventCreateWithFlags, event, flags); HIP_RETURN(ihipEventCreateWithFlags(event, flags), *event); } hipError_t hipEventCreate(hipEvent_t* event) { HIP_INIT_API(hipEventCreate, event); HIP_RETURN(ihipEventCreateWithFlags(event, 0), *event); } hipError_t hipEventDestroy(hipEvent_t event) { HIP_INIT_API(hipEventDestroy, event); if (event == nullptr) { HIP_RETURN(hipErrorInvalidHandle); } hip::Event* e = reinterpret_cast(event); if ((e->flags & hipEventInterprocess) && (e->ipc_evt_.ipc_shmem_)) { int owners = -- e->ipc_evt_.ipc_shmem_->owners; // Make sure event is synchronized hipEventSynchronize(event); if (!amd::Os::MemoryUnmapFile(e->ipc_evt_.ipc_shmem_,sizeof(hip::ihipIpcEventShmem_t))) { HIP_RETURN(hipErrorInvalidHandle); } } delete e; HIP_RETURN(hipSuccess); } hipError_t hipEventElapsedTime(float *ms, hipEvent_t start, hipEvent_t stop) { HIP_INIT_API(hipEventElapsedTime, ms, start, stop); if (ms == nullptr) { HIP_RETURN(hipErrorInvalidValue); } if (start == nullptr || stop == nullptr) { HIP_RETURN(hipErrorInvalidHandle); } hip::Event* eStart = reinterpret_cast(start); hip::Event* eStop = reinterpret_cast(stop); if (eStart->deviceId() != eStop->deviceId()) { HIP_RETURN(hipErrorInvalidHandle); } HIP_RETURN(eStart->elapsedTime(*eStop, *ms), "Elapsed Time = ", *ms); } // ================================================================================================ hipError_t hipEventRecord(hipEvent_t event, hipStream_t stream) { HIP_INIT_API(hipEventRecord, event, stream); STREAM_CAPTURE(hipEventRecord, stream, event); if (event == nullptr) { HIP_RETURN(hipErrorInvalidHandle); } hip::Event* e = reinterpret_cast(event); amd::HostQueue* queue = hip::getQueue(stream); if (g_devices[e->deviceId()]->devices()[0] != &queue->device()) { HIP_RETURN(hipErrorInvalidHandle); } HIP_RETURN(e->addMarker(stream, nullptr, true)); } // ================================================================================================ hipError_t hipEventSynchronize(hipEvent_t event) { HIP_INIT_API(hipEventSynchronize, event); if (event == nullptr) { HIP_RETURN(hipErrorInvalidHandle); } hip::Event* e = reinterpret_cast(event); if ((e->flags & hipEventInterprocess) && (e->ipc_evt_.ipc_shmem_)) { int prev_read_idx = e->ipc_evt_.ipc_shmem_->read_index; if (prev_read_idx >= 0) { int offset = (prev_read_idx % IPC_SIGNALS_PER_EVENT); while ((e->ipc_evt_.ipc_shmem_->read_index < prev_read_idx + IPC_SIGNALS_PER_EVENT) && (e->ipc_evt_.ipc_shmem_->signal[offset] != 0)) { amd::Os::sleep(1); } } HIP_RETURN(hipSuccess); } else { HIP_RETURN(e->synchronize()); } } hipError_t hipEventQuery(hipEvent_t event) { HIP_INIT_API(hipEventQuery, event); HIP_RETURN(ihipEventQuery(event)); } hipError_t hipIpcGetEventHandle(hipIpcEventHandle_t* handle, hipEvent_t event) { HIP_INIT_API(hipIpcGetEventHandle, handle, event); #if !defined(_MSC_VER) if (handle == nullptr || event == nullptr) { HIP_RETURN(hipErrorInvalidValue); } hip::Event* e = reinterpret_cast(event); if (!(e->flags & hipEventInterprocess)) { HIP_RETURN(hipErrorInvalidConfiguration); } if (!createIpcEventShmemIfNeeded(e->ipc_evt_)) { HIP_RETURN(hipErrorInvalidConfiguration); } e->ipc_evt_.ipc_shmem_->owners_device_id = e->deviceId(); e->ipc_evt_.ipc_shmem_->owners_process_id = getpid(); ihipIpcEventHandle_t* iHandle = reinterpret_cast(handle); memset(iHandle->shmem_name, 0, HIP_IPC_HANDLE_SIZE); e->ipc_evt_.ipc_name_.copy(iHandle->shmem_name, std::string::npos); HIP_RETURN(hipSuccess); #else assert(0 && "Unimplemented"); HIP_RETURN(hipErrorNotSupported); #endif } hipError_t hipIpcOpenEventHandle(hipEvent_t* event, hipIpcEventHandle_t handle) { HIP_INIT_API(NONE, event, handle); #if !defined(_MSC_VER) hipError_t hip_err = hipSuccess; if (event == nullptr) { HIP_RETURN(hipErrorInvalidValue); } hip_err = ihipEventCreateWithFlags(event, hipEventDisableTiming | hipEventInterprocess); if (hip_err != hipSuccess) { HIP_RETURN(hip_err); } hip::Event* e = reinterpret_cast(*event); ihipIpcEventHandle_t* iHandle = reinterpret_cast(&handle); hip::Event::ihipIpcEvent_t& ipc_evt = e->ipc_evt_; ipc_evt.ipc_name_ = iHandle->shmem_name; if (!amd::Os::MemoryMapFileTruncated(ipc_evt.ipc_name_.c_str(), (const void**) &(ipc_evt.ipc_shmem_), sizeof(hip::ihipIpcEventShmem_t))) { HIP_RETURN(hipErrorInvalidValue); } if (getpid() == ipc_evt.ipc_shmem_->owners_process_id.load()) { // If this is in the same process, return error. HIP_RETURN(hipErrorInvalidContext); } ipc_evt.ipc_shmem_->owners += 1; e->setDeviceId(ipc_evt.ipc_shmem_->owners_device_id.load()); HIP_RETURN(hipSuccess); #else assert(0 && "Unimplemented"); HIP_RETURN(hipErrorNotSupported); #endif }