//===- OmptAsserter.cpp - Asserter-related implementations ------*- 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 // //===----------------------------------------------------------------------===// /// /// \file /// Implements all asserter-related class methods, like: notifications, handling /// of groups or determination of the testcase state. /// //===----------------------------------------------------------------------===// #include "OmptAsserter.h" #include "Logging.h" #include using namespace omptest; using namespace internal; // Initialize static members std::mutex OmptAsserter::StaticMemberAccessMutex; std::weak_ptr OmptAsserter::EventGroupInterfaceInstance; std::weak_ptr OmptAsserter::LoggingInstance; OmptAsserter::OmptAsserter() { // Protect static members access std::lock_guard Lock(StaticMemberAccessMutex); // Upgrade OmptEventGroupInterface weak_ptr to shared_ptr { EventGroups = EventGroupInterfaceInstance.lock(); if (!EventGroups) { // Coordinator doesn't exist or was previously destroyed, create a new // one. EventGroups = std::make_shared(); // Store a weak reference to it EventGroupInterfaceInstance = EventGroups; } // EventGroups is now a valid shared_ptr, either to a new or existing // instance. } // Upgrade logging::Logger weak_ptr to shared_ptr { Log = LoggingInstance.lock(); if (!Log) { // Coordinator doesn't exist or was previously destroyed, create a new // one. Log = std::make_shared(); // Store a weak reference to it LoggingInstance = Log; } // Log is now a valid shared_ptr, either to a new or existing instance. } } void OmptListener::setActive(bool Enabled) { Active = Enabled; } bool OmptListener::isActive() { return Active; } bool OmptListener::isSuppressedEventType(EventTy EvTy) { return SuppressedEvents.find(EvTy) != SuppressedEvents.end(); } void OmptListener::permitEvent(EventTy EvTy) { SuppressedEvents.erase(EvTy); } void OmptListener::suppressEvent(EventTy EvTy) { SuppressedEvents.insert(EvTy); } void OmptAsserter::insert(OmptAssertEvent &&AE) { assert(false && "Base class 'insert' has undefined semantics."); } void OmptAsserter::notify(OmptAssertEvent &&AE) { // Ignore notifications while inactive if (!isActive() || isSuppressedEventType(AE.getEventType())) return; this->notifyImpl(std::move(AE)); } AssertState OmptAsserter::checkState() { return State; } bool OmptAsserter::verifyEventGroups(const OmptAssertEvent &ExpectedEvent, const OmptAssertEvent &ObservedEvent) { assert(ExpectedEvent.getEventType() == ObservedEvent.getEventType() && "Type mismatch: Expected != Observed event type"); assert(EventGroups && "Missing EventGroups interface"); // Ignore all events within "default" group auto GroupName = ExpectedEvent.getEventGroup(); if (GroupName == "default") return true; // Get a pointer to the observed internal event auto Event = ObservedEvent.getEvent(); switch (Event->Type) { case EventTy::Target: if (auto E = static_cast(Event)) { if (E->Endpoint == ompt_scope_begin) { // Add new group since we entered a Target Region EventGroups->addActiveEventGroup(GroupName, AssertEventGroup{E->TargetId}); } else if (E->Endpoint == ompt_scope_end) { // Deprecate group since we return from a Target Region EventGroups->deprecateActiveEventGroup(GroupName); } return true; } return false; case EventTy::TargetEmi: if (auto E = static_cast(Event)) { if (E->Endpoint == ompt_scope_begin) { // Add new group since we entered a Target Region EventGroups->addActiveEventGroup( GroupName, AssertEventGroup{E->TargetData->value}); } else if (E->Endpoint == ompt_scope_end) { // Deprecate group since we return from a Target Region EventGroups->deprecateActiveEventGroup(GroupName); } return true; } return false; case EventTy::TargetDataOp: if (auto E = static_cast(Event)) return EventGroups->checkActiveEventGroups(GroupName, AssertEventGroup{E->TargetId}); return false; case EventTy::TargetDataOpEmi: if (auto E = static_cast(Event)) return EventGroups->checkActiveEventGroups( GroupName, AssertEventGroup{E->TargetData->value}); return false; case EventTy::TargetSubmit: if (auto E = static_cast(Event)) return EventGroups->checkActiveEventGroups(GroupName, AssertEventGroup{E->TargetId}); return false; case EventTy::TargetSubmitEmi: if (auto E = static_cast(Event)) return EventGroups->checkActiveEventGroups( GroupName, AssertEventGroup{E->TargetData->value}); return false; case EventTy::BufferRecord: // BufferRecords are delivered asynchronously: also check deprecated groups. if (auto E = static_cast(Event)) return (EventGroups->checkActiveEventGroups( GroupName, AssertEventGroup{E->Record.target_id}) || EventGroups->checkDeprecatedEventGroups( GroupName, AssertEventGroup{E->Record.target_id})); return false; // Some event types do not need any handling case EventTy::ThreadBegin: case EventTy::ThreadEnd: case EventTy::ParallelBegin: case EventTy::ParallelEnd: case EventTy::Work: case EventTy::Dispatch: case EventTy::TaskCreate: case EventTy::Dependences: case EventTy::TaskDependence: case EventTy::TaskSchedule: case EventTy::ImplicitTask: case EventTy::Masked: case EventTy::SyncRegion: case EventTy::MutexAcquire: case EventTy::Mutex: case EventTy::NestLock: case EventTy::Flush: case EventTy::Cancel: case EventTy::DeviceInitialize: case EventTy::DeviceFinalize: case EventTy::DeviceLoad: case EventTy::DeviceUnload: case EventTy::BufferRequest: case EventTy::BufferComplete: case EventTy::BufferRecordDeallocation: return true; // Some event types must not be encountered case EventTy::None: case EventTy::AssertionSyncPoint: case EventTy::AssertionSuspend: default: Log->log("Observed invalid event type: " + Event->toString(), logging::Level::Critical); __builtin_unreachable(); } return true; } void OmptAsserter::setOperationMode(AssertMode Mode) { OperationMode = Mode; } void OmptSequencedAsserter::insert(OmptAssertEvent &&AE) { std::lock_guard Lock(AssertMutex); Events.emplace_back(std::move(AE)); } void OmptSequencedAsserter::notifyImpl(OmptAssertEvent &&AE) { std::lock_guard Lock(AssertMutex); // Ignore notifications while inactive, or for suppressed events if (Events.empty() || !isActive() || isSuppressedEventType(AE.getEventType())) return; ++NumNotifications; // Note: Order of these checks has semantic meaning. // (1) Synchronization points should fail if there are remaining events, // otherwise pass. (2) Regular notification while no further events are // expected: fail. (3) Assertion suspension relies on a next expected event // being available. (4) All other cases are considered 'regular' and match the // next expected against the observed event. (5+6) Depending on the state / // mode we signal failure if no other check has done already, or signaled pass // by early-exit. if (consumeSyncPoint(AE) || // Handle observed SyncPoint event checkExcessNotify(AE) || // Check for remaining expected consumeSuspend() || // Handle requested suspend consumeRegularEvent(AE) || // Handle regular event AssertionSuspended || // Ignore fail, if suspended OperationMode == AssertMode::Relaxed) // Ignore fail, if Relaxed op-mode return; Log->logEventMismatch("[OmptSequencedAsserter] The events are not equal", Events[NextEvent], AE); State = AssertState::Fail; } bool OmptSequencedAsserter::consumeSyncPoint( const omptest::OmptAssertEvent &AE) { if (AE.getEventType() == EventTy::AssertionSyncPoint) { auto NumRemainingEvents = getRemainingEventCount(); // Upon encountering a SyncPoint, all events should have been processed if (NumRemainingEvents == 0) return true; Log->logEventMismatch( "[OmptSequencedAsserter] Encountered SyncPoint while still awaiting " + std::to_string(NumRemainingEvents) + " events. Asserted " + std::to_string(NumSuccessfulAsserts) + "/" + std::to_string(Events.size()) + " events successfully.", AE); State = AssertState::Fail; return true; } // Nothing to process: continue. return false; } bool OmptSequencedAsserter::checkExcessNotify( const omptest::OmptAssertEvent &AE) { if (NextEvent >= Events.size()) { // If we are not expecting any more events and passively asserting: return if (AssertionSuspended) return true; Log->logEventMismatch( "[OmptSequencedAsserter] Too many events to check (" + std::to_string(NumNotifications) + "). Asserted " + std::to_string(NumSuccessfulAsserts) + "/" + std::to_string(Events.size()) + " events successfully.", AE); State = AssertState::Fail; return true; } // Remaining expected events present: continue. return false; } bool OmptSequencedAsserter::consumeSuspend() { // On AssertionSuspend -- enter 'passive' assertion. // Since we may encounter multiple, successive AssertionSuspend events, loop // until we hit the next non-AssertionSuspend event. while (Events[NextEvent].getEventType() == EventTy::AssertionSuspend) { AssertionSuspended = true; // We just hit the very last event: indicate early exit. if (++NextEvent >= Events.size()) return true; } // Continue with remaining notification logic. return false; } bool OmptSequencedAsserter::consumeRegularEvent( const omptest::OmptAssertEvent &AE) { // If we are actively asserting, increment the event counter. // Otherwise: If passively asserting, we will keep waiting for a match. auto &E = Events[NextEvent]; if (E == AE && verifyEventGroups(E, AE)) { if (E.getEventExpectedState() == ObserveState::Always) { ++NumSuccessfulAsserts; } else if (E.getEventExpectedState() == ObserveState::Never) { Log->logEventMismatch( "[OmptSequencedAsserter] Encountered forbidden event", E, AE); State = AssertState::Fail; } // Return to active assertion if (AssertionSuspended) AssertionSuspended = false; // Match found, increment index and indicate early exit (success). ++NextEvent; return true; } // Continue with remaining notification logic. return false; } size_t OmptSequencedAsserter::getRemainingEventCount() { return std::count_if(Events.begin(), Events.end(), [](const omptest::OmptAssertEvent &E) { return E.getEventExpectedState() == ObserveState::Always; }) - NumSuccessfulAsserts; } AssertState OmptSequencedAsserter::checkState() { // This is called after the testcase executed. // Once reached the number of successful notifications should be equal to the // number of expected events. However, there may still be excluded as well as // special asserter events remaining in the sequence. for (size_t i = NextEvent; i < Events.size(); ++i) { auto &E = Events[i]; if (E.getEventExpectedState() == ObserveState::Always) { State = AssertState::Fail; Log->logEventMismatch("[OmptSequencedAsserter] Expected event was not " "encountered (Remaining events: " + std::to_string(getRemainingEventCount()) + ")", E); break; } } return State; } void OmptEventAsserter::insert(OmptAssertEvent &&AE) { std::lock_guard Lock(AssertMutex); Events.emplace_back(std::move(AE)); } void OmptEventAsserter::notifyImpl(OmptAssertEvent &&AE) { std::lock_guard Lock(AssertMutex); if (Events.empty() || !isActive() || isSuppressedEventType(AE.getEventType())) return; if (NumEvents == 0) NumEvents = Events.size(); ++NumNotifications; if (AE.getEventType() == EventTy::AssertionSyncPoint) { auto NumRemainingEvents = getRemainingEventCount(); // Upon encountering a SyncPoint, all events should have been processed if (NumRemainingEvents == 0) return; Log->logEventMismatch( "[OmptEventAsserter] Encountered SyncPoint while still awaiting " + std::to_string(NumRemainingEvents) + " events. Asserted " + std::to_string(NumSuccessfulAsserts) + " events successfully.", AE); State = AssertState::Fail; return; } for (size_t i = 0; i < Events.size(); ++i) { auto &E = Events[i]; if (E == AE && verifyEventGroups(E, AE)) { if (E.getEventExpectedState() == ObserveState::Always) { Events.erase(Events.begin() + i); ++NumSuccessfulAsserts; } else if (E.getEventExpectedState() == ObserveState::Never) { Log->logEventMismatch("[OmptEventAsserter] Encountered forbidden event", E, AE); State = AssertState::Fail; } return; } } if (OperationMode == AssertMode::Strict) { Log->logEventMismatch("[OmptEventAsserter] Too many events to check (" + std::to_string(NumNotifications) + "). Asserted " + std::to_string(NumSuccessfulAsserts) + " events successfully. (Remaining events: " + std::to_string(getRemainingEventCount()) + ")", AE); State = AssertState::Fail; return; } } size_t OmptEventAsserter::getRemainingEventCount() { return std::count_if( Events.begin(), Events.end(), [](const omptest::OmptAssertEvent &E) { return E.getEventExpectedState() == ObserveState::Always; }); } AssertState OmptEventAsserter::checkState() { // This is called after the testcase executed. // Once reached no more expected events should be in the queue for (const auto &E : Events) { // Check if any of the remaining events were expected to be observed if (E.getEventExpectedState() == ObserveState::Always) { State = AssertState::Fail; Log->logEventMismatch("[OmptEventAsserter] Expected event was not " "encountered (Remaining events: " + std::to_string(getRemainingEventCount()) + ")", E); break; } } return State; } void OmptEventReporter::notify(OmptAssertEvent &&AE) { if (!isActive() || isSuppressedEventType(AE.getEventType())) return; // Prepare notification, containing the newline to avoid stream interleaving. auto Notification{AE.toString()}; Notification.push_back('\n'); OutStream << Notification; } bool OmptEventGroupInterface::addActiveEventGroup( const std::string &GroupName, omptest::AssertEventGroup Group) { std::lock_guard Lock(GroupMutex); auto EventGroup = ActiveEventGroups.find(GroupName); if (EventGroup != ActiveEventGroups.end() && EventGroup->second.TargetRegion == Group.TargetRegion) return false; ActiveEventGroups.emplace(GroupName, Group); return true; } bool OmptEventGroupInterface::deprecateActiveEventGroup( const std::string &GroupName) { std::lock_guard Lock(GroupMutex); auto EventGroup = ActiveEventGroups.find(GroupName); auto DeprecatedEventGroup = DeprecatedEventGroups.find(GroupName); if (EventGroup == ActiveEventGroups.end() && DeprecatedEventGroup != DeprecatedEventGroups.end()) return false; DeprecatedEventGroups.emplace(GroupName, EventGroup->second); ActiveEventGroups.erase(GroupName); return true; } bool OmptEventGroupInterface::checkActiveEventGroups( const std::string &GroupName, omptest::AssertEventGroup Group) { std::lock_guard Lock(GroupMutex); auto EventGroup = ActiveEventGroups.find(GroupName); return (EventGroup != ActiveEventGroups.end() && EventGroup->second.TargetRegion == Group.TargetRegion); } bool OmptEventGroupInterface::checkDeprecatedEventGroups( const std::string &GroupName, omptest::AssertEventGroup Group) { std::lock_guard Lock(GroupMutex); auto EventGroup = DeprecatedEventGroups.find(GroupName); return (EventGroup != DeprecatedEventGroups.end() && EventGroup->second.TargetRegion == Group.TargetRegion); }