Michael Halkenhäuser 7c1d2467f1
Reland: [OpenMP] Add ompTest library to OpenMP (#154786)
Reland of https://github.com/llvm/llvm-project/pull/147381

Added changes to fix observed BuildBot failures:
 * CMake version (reduced minimum to `3.20`, was: `3.22`)
 * GoogleTest linking (missing `./build/lib/libllvm_gtest.a`)
* Related header issue (missing `#include
"llvm/Support/raw_os_ostream.h"`)

Original message

Description
===========
OpenMP Tooling Interface Testing Library (ompTest) ompTest is a unit testing framework for testing OpenMP implementations. It offers a simple-to-use framework that allows a tester to check for OMPT events in addition to regular unit testing code, supported by linking against GoogleTest by default. It also facilitates writing concise tests while bridging the semantic gap between the unit under test and the OMPT-event testing.

Background
==========
This library has been developed to provide the means of testing OMPT implementations with reasonable effort. Especially, asynchronous or unordered events are supported and can be verified with ease, which may prove to be challenging with LIT-based tests. Additionally, since the assertions are part of the code being tested, ompTest can reference all corresponding variables during assertion.

Basic Usage
===========
OMPT event assertions are placed before the code, which shall be tested. These assertion can either be provided as one block or interleaved with the test code. There are two types of asserters: (1) sequenced "order-sensitive" and (2) set "unordered" assserters. Once the test is being run, the corresponding events are triggered by the OpenMP runtime and can be observed. Each of these observed events notifies asserters, which then determine if the test should pass or fail.

Example (partial, interleaved)
==============================
```c++
  int N = 100000;
  int a[N];
  int b[N];

  OMPT_ASSERT_SEQUENCE(Target, TARGET, BEGIN, 0);
  OMPT_ASSERT_SEQUENCE(TargetDataOp, ALLOC, N * sizeof(int)); // a ?
  OMPT_ASSERT_SEQUENCE(TargetDataOp, H2D, N * sizeof(int), &a);
  OMPT_ASSERT_SEQUENCE(TargetDataOp, ALLOC, N * sizeof(int)); // b ?
  OMPT_ASSERT_SEQUENCE(TargetDataOp, H2D, N * sizeof(int), &b);
  OMPT_ASSERT_SEQUENCE(TargetSubmit, 1);
  OMPT_ASSERT_SEQUENCE(TargetDataOp, D2H, N * sizeof(int), nullptr, &b);
  OMPT_ASSERT_SEQUENCE(TargetDataOp, D2H, N * sizeof(int), nullptr, &a);
  OMPT_ASSERT_SEQUENCE(TargetDataOp, DELETE);
  OMPT_ASSERT_SEQUENCE(TargetDataOp, DELETE);
  OMPT_ASSERT_SEQUENCE(Target, TARGET, END, 0);

#pragma omp target parallel for
  {
    for (int j = 0; j < N; j++)
      a[j] = b[j];
  }
```

References
==========
This work has been presented at SC'24 workshops, see: https://ieeexplore.ieee.org/document/10820689

Current State and Future Work
=============================
ompTest's development was mostly device-centric and aimed at OMPT device callbacks and device-side tracing. Consequentially, a substantial part of host-related events or features may not be supported in its current state. However, we are confident that the related functionality can be added and ompTest provides a general foundation for future OpenMP and especially OMPT testing. This PR will allow us to upstream the corresponding features, like OMPT device-side tracing in the future with significantly reduced risk of introducing regressions in the process.

Build
=====
ompTest is linked against LLVM's GoogleTest by default, but can also be built 'standalone'. Additionally, it comes with a set of unit tests, which in turn require GoogleTest (overriding a standalone build). The unit tests are added to the `check-openmp` target.

Use the following parameters to perform the corresponding build: 
`LIBOMPTEST_BUILD_STANDALONE` (Default: ${OPENMP_STANDALONE_BUILD})
`LIBOMPTEST_BUILD_UNITTESTS` (Default: OFF)

---------

Co-authored-by: Jan-Patrick Lehr <JanPatrick.Lehr@amd.com>
Co-authored-by: Joachim <protze@rz.rwth-aachen.de>
Co-authored-by: Joachim Jenke <jenke@itc.rwth-aachen.de>
2025-08-22 13:56:12 +02:00

292 lines
10 KiB
C++

//===- OmptAsserter.h - Asserter-related classes, enums, etc. ---*- 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
/// Contains all asserter-related class declarations and important enums.
///
//===----------------------------------------------------------------------===//
#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTASSERTER_H
#define OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTASSERTER_H
#include "Logging.h"
#include "OmptAssertEvent.h"
#include <cassert>
#include <iostream>
#include <map>
#include <mutex>
#include <set>
#include <vector>
namespace omptest {
// Forward declaration.
class OmptEventGroupInterface;
enum class AssertMode { Strict, Relaxed };
enum class AssertState { Pass, Fail };
/// General base class for the subscriber/notification pattern in
/// OmptCallbackHandler. Derived classes need to implement the notify method.
class OmptListener {
public:
virtual ~OmptListener() = default;
/// Called for each registered OMPT event of the OmptCallbackHandler
virtual void notify(omptest::OmptAssertEvent &&AE) = 0;
/// Control whether this asserter should be considered 'active'.
void setActive(bool Enabled);
/// Check if this asserter is considered 'active'.
bool isActive();
/// Check if the given event type is from the set of suppressed event types.
bool isSuppressedEventType(omptest::internal::EventTy EvTy);
/// Remove the given event type to the set of suppressed events.
void permitEvent(omptest::internal::EventTy EvTy);
/// Add the given event type to the set of suppressed events.
void suppressEvent(omptest::internal::EventTy EvTy);
private:
bool Active{true};
// Add event types to the set of suppressed events by default.
std::set<omptest::internal::EventTy> SuppressedEvents{
omptest::internal::EventTy::ThreadBegin,
omptest::internal::EventTy::ThreadEnd,
omptest::internal::EventTy::ParallelBegin,
omptest::internal::EventTy::ParallelEnd,
omptest::internal::EventTy::Work,
omptest::internal::EventTy::Dispatch,
omptest::internal::EventTy::TaskCreate,
omptest::internal::EventTy::Dependences,
omptest::internal::EventTy::TaskDependence,
omptest::internal::EventTy::TaskSchedule,
omptest::internal::EventTy::ImplicitTask,
omptest::internal::EventTy::Masked,
omptest::internal::EventTy::SyncRegion,
omptest::internal::EventTy::MutexAcquire,
omptest::internal::EventTy::Mutex,
omptest::internal::EventTy::NestLock,
omptest::internal::EventTy::Flush,
omptest::internal::EventTy::Cancel};
};
/// Base class for asserting on OMPT events
class OmptAsserter : public OmptListener {
public:
OmptAsserter();
virtual ~OmptAsserter() = default;
/// Add an event to the asserter's internal data structure.
virtual void insert(omptest::OmptAssertEvent &&AE);
/// Called from the CallbackHandler with a corresponding AssertEvent to which
/// callback was handled.
void notify(omptest::OmptAssertEvent &&AE) override;
/// Implemented in subclasses to implement what should actually be done with
/// the notification.
virtual void notifyImpl(omptest::OmptAssertEvent &&AE) = 0;
/// Get the number of currently remaining events, with: ObserveState::Always.
virtual size_t getRemainingEventCount() = 0;
/// Get the total number of received, effective notifications.
int getNotificationCount() { return NumNotifications; }
/// Get the total number of successful assertion checks.
int getSuccessfulAssertionCount() { return NumSuccessfulAsserts; }
/// Get the asserter's current operationmode: e.g.: Strict or Relaxed.
AssertMode getOperationMode() { return OperationMode; }
/// Return the asserter's current state.
omptest::AssertState getState() { return State; }
/// Determine and return the asserter's state.
virtual omptest::AssertState checkState();
/// Accessor for the event group interface.
std::shared_ptr<OmptEventGroupInterface> getEventGroups() const {
return EventGroups;
}
/// Accessor for the event group interface.
std::shared_ptr<logging::Logger> getLog() const { return Log; }
/// Check the observed events' group association. If the event indicates the
/// begin/end of an OpenMP target region, we will create/deprecate the
/// expected event's group. Return true if the expected event group exists
/// (and is active), otherwise: false. Note: BufferRecords may also match with
/// deprecated groups as they may be delivered asynchronously.
bool verifyEventGroups(const omptest::OmptAssertEvent &ExpectedEvent,
const omptest::OmptAssertEvent &ObservedEvent);
/// Set the asserter's mode of operation w.r.t. assertion.
void setOperationMode(AssertMode Mode);
protected:
/// The asserter's current state.
omptest::AssertState State{omptest::AssertState::Pass};
/// Mutex to avoid data races w.r.t. event notifications and/or insertions.
std::mutex AssertMutex;
/// Pointer to the OmptEventGroupInterface.
std::shared_ptr<OmptEventGroupInterface> EventGroups{nullptr};
/// Pointer to the logging instance.
std::shared_ptr<logging::Logger> Log{nullptr};
/// Operation mode during assertion / notification.
AssertMode OperationMode{AssertMode::Strict};
/// The total number of effective notifications. For example, if specific
/// notifications are to be ignored, they will not count towards this total.
int NumNotifications{0};
/// The number of successful assertion checks.
int NumSuccessfulAsserts{0};
private:
/// Mutex for creating/accessing the singleton members
static std::mutex StaticMemberAccessMutex;
/// Static member to manage the singleton event group interface instance
static std::weak_ptr<OmptEventGroupInterface> EventGroupInterfaceInstance;
/// Static member to manage the singleton logging instance
static std::weak_ptr<logging::Logger> LoggingInstance;
};
/// Class that can assert in a sequenced fashion, i.e., events have to occur in
/// the order they were registered
class OmptSequencedAsserter : public OmptAsserter {
public:
OmptSequencedAsserter() : OmptAsserter(), NextEvent(0) {}
/// Add the event to the in-sequence set of events that the asserter should
/// check for.
void insert(omptest::OmptAssertEvent &&AE) override;
/// Implements the asserter's actual logic
virtual void notifyImpl(omptest::OmptAssertEvent &&AE) override;
size_t getRemainingEventCount() override;
omptest::AssertState checkState() override;
bool AssertionSuspended{false};
protected:
/// Notification helper function, implementing SyncPoint logic. Returns true
/// in case of consumed event, indicating early exit of notification.
bool consumeSyncPoint(const omptest::OmptAssertEvent &AE);
/// Notification helper function, implementing excess event notification
/// logic. Returns true when no more events were expected, indicating early
/// exit of notification.
bool checkExcessNotify(const omptest::OmptAssertEvent &AE);
/// Notification helper function, implementing Suspend logic. Returns true
/// in case of consumed event, indicating early exit of notification.
bool consumeSuspend();
/// Notification helper function, implementing regular event notification
/// logic. Returns true when a matching event was encountered, indicating
/// early exit of notification.
bool consumeRegularEvent(const omptest::OmptAssertEvent &AE);
public:
/// Index of the next, expected event.
size_t NextEvent{0};
std::vector<omptest::OmptAssertEvent> Events{};
};
/// Class that asserts with set semantics, i.e., unordered
struct OmptEventAsserter : public OmptAsserter {
OmptEventAsserter() : OmptAsserter(), NumEvents(0), Events() {}
/// Add the event to the set of events that the asserter should check for.
void insert(omptest::OmptAssertEvent &&AE) override;
/// Implements the asserter's logic
virtual void notifyImpl(omptest::OmptAssertEvent &&AE) override;
size_t getRemainingEventCount() override;
omptest::AssertState checkState() override;
size_t NumEvents{0};
/// For now use vector (but do set semantics)
// TODO std::unordered_set?
std::vector<omptest::OmptAssertEvent> Events{};
};
/// Class that reports the occurred events
class OmptEventReporter : public OmptListener {
public:
OmptEventReporter(std::ostream &OutStream = std::cout)
: OutStream(OutStream) {}
/// Called from the CallbackHandler with a corresponding AssertEvent to which
/// callback was handled.
void notify(omptest::OmptAssertEvent &&AE) override;
private:
std::ostream &OutStream;
};
/// This class provides the members and methods to manage event groups and
/// SyncPoints in conjunction with asserters. Most importantly it maintains a
/// coherent view of active and past events or SyncPoints.
class OmptEventGroupInterface {
public:
OmptEventGroupInterface() = default;
~OmptEventGroupInterface() = default;
/// Non-copyable and non-movable
OmptEventGroupInterface(const OmptEventGroupInterface &) = delete;
OmptEventGroupInterface &operator=(const OmptEventGroupInterface &) = delete;
OmptEventGroupInterface(OmptEventGroupInterface &&) = delete;
OmptEventGroupInterface &operator=(OmptEventGroupInterface &&) = delete;
/// Add given group to the set of active event groups. Effectively connecting
/// the given groupname (expected) with a target region id (observed).
bool addActiveEventGroup(const std::string &GroupName,
omptest::AssertEventGroup Group);
/// Move given group from the set of active event groups to the set of
/// previously active event groups.
bool deprecateActiveEventGroup(const std::string &GroupName);
/// Check if given group is currently part of the active event groups.
bool checkActiveEventGroups(const std::string &GroupName,
omptest::AssertEventGroup Group);
/// Check if given group is currently part of the deprecated event groups.
bool checkDeprecatedEventGroups(const std::string &GroupName,
omptest::AssertEventGroup Group);
private:
mutable std::mutex GroupMutex;
std::map<std::string, omptest::AssertEventGroup> ActiveEventGroups{};
std::map<std::string, omptest::AssertEventGroup> DeprecatedEventGroups{};
std::set<std::string> EncounteredSyncPoints{};
};
} // namespace omptest
#endif