From f024da45acd71e4dfe65d3d9cc6b86cf9cb7cc69 Mon Sep 17 00:00:00 2001 From: Michael Halkenhaeuser Date: Wed, 2 Jul 2025 05:32:04 -0500 Subject: [PATCH] [OpenMP] Add ompTest library to OpenMP 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) ============================== 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); { 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: OFF) `LIBOMPTEST_BUILD_UNITTESTS` (Default: OFF) --------- Co-authored-by: Jan-Patrick Lehr --- openmp/README.rst | 1 + openmp/tools/omptest/CMakeLists.txt | 116 ++++ openmp/tools/omptest/README.md | 279 +++++++++ .../omptest/cmake/omptest-config.cmake.in | 29 + openmp/tools/omptest/include/AssertMacros.h | 138 ++++ openmp/tools/omptest/include/InternalEvent.h | 331 ++++++++++ .../omptest/include/InternalEventCommon.h | 133 ++++ openmp/tools/omptest/include/Logging.h | 155 +++++ openmp/tools/omptest/include/OmptAliases.h | 85 +++ .../tools/omptest/include/OmptAssertEvent.h | 377 +++++++++++ openmp/tools/omptest/include/OmptAsserter.h | 291 +++++++++ .../omptest/include/OmptCallbackHandler.h | 165 +++++ openmp/tools/omptest/include/OmptTester.h | 60 ++ .../tools/omptest/include/OmptTesterGlobals.h | 36 ++ .../omptest/include/OmptTesterGoogleTest.h | 86 +++ .../omptest/include/OmptTesterStandalone.h | 123 ++++ openmp/tools/omptest/src/InternalEvent.cpp | 367 +++++++++++ .../omptest/src/InternalEventOperators.cpp | 366 +++++++++++ openmp/tools/omptest/src/Logging.cpp | 177 ++++++ openmp/tools/omptest/src/OmptAssertEvent.cpp | 587 ++++++++++++++++++ openmp/tools/omptest/src/OmptAsserter.cpp | 480 ++++++++++++++ .../tools/omptest/src/OmptCallbackHandler.cpp | 445 +++++++++++++ openmp/tools/omptest/src/OmptTester.cpp | 504 +++++++++++++++ .../omptest/src/OmptTesterStandalone.cpp | 147 +++++ openmp/tools/omptest/test/CMakeLists.txt | 28 + openmp/tools/omptest/test/lit.cfg | 26 + openmp/tools/omptest/test/lit.site.cfg.in | 9 + .../test/unittests/asserter-seq-test.cpp | 358 +++++++++++ .../test/unittests/internal-event-test.cpp | 530 ++++++++++++++++ .../test/unittests/internal-util-test.cpp | 95 +++ .../omptest/test/unittests/main-test.cpp | 141 +++++ 31 files changed, 6665 insertions(+) create mode 100644 openmp/tools/omptest/CMakeLists.txt create mode 100644 openmp/tools/omptest/README.md create mode 100644 openmp/tools/omptest/cmake/omptest-config.cmake.in create mode 100644 openmp/tools/omptest/include/AssertMacros.h create mode 100644 openmp/tools/omptest/include/InternalEvent.h create mode 100644 openmp/tools/omptest/include/InternalEventCommon.h create mode 100644 openmp/tools/omptest/include/Logging.h create mode 100644 openmp/tools/omptest/include/OmptAliases.h create mode 100644 openmp/tools/omptest/include/OmptAssertEvent.h create mode 100644 openmp/tools/omptest/include/OmptAsserter.h create mode 100644 openmp/tools/omptest/include/OmptCallbackHandler.h create mode 100644 openmp/tools/omptest/include/OmptTester.h create mode 100644 openmp/tools/omptest/include/OmptTesterGlobals.h create mode 100644 openmp/tools/omptest/include/OmptTesterGoogleTest.h create mode 100644 openmp/tools/omptest/include/OmptTesterStandalone.h create mode 100644 openmp/tools/omptest/src/InternalEvent.cpp create mode 100644 openmp/tools/omptest/src/InternalEventOperators.cpp create mode 100644 openmp/tools/omptest/src/Logging.cpp create mode 100644 openmp/tools/omptest/src/OmptAssertEvent.cpp create mode 100644 openmp/tools/omptest/src/OmptAsserter.cpp create mode 100644 openmp/tools/omptest/src/OmptCallbackHandler.cpp create mode 100644 openmp/tools/omptest/src/OmptTester.cpp create mode 100644 openmp/tools/omptest/src/OmptTesterStandalone.cpp create mode 100644 openmp/tools/omptest/test/CMakeLists.txt create mode 100644 openmp/tools/omptest/test/lit.cfg create mode 100644 openmp/tools/omptest/test/lit.site.cfg.in create mode 100644 openmp/tools/omptest/test/unittests/asserter-seq-test.cpp create mode 100644 openmp/tools/omptest/test/unittests/internal-event-test.cpp create mode 100644 openmp/tools/omptest/test/unittests/internal-util-test.cpp create mode 100644 openmp/tools/omptest/test/unittests/main-test.cpp diff --git a/openmp/README.rst b/openmp/README.rst index 2dfc8630858b8..c34d3e8a40d7d 100644 --- a/openmp/README.rst +++ b/openmp/README.rst @@ -369,6 +369,7 @@ There are following check-* make targets for tests. - ``check-ompt`` (ompt tests under runtime/test/ompt) - ``check-ompt-multiplex`` (ompt multiplex tests under tools/multiplex/tests) +- ``check-ompt-omptest`` (ompt omptest tests under tools/omptest/tests) - ``check-libarcher`` (libarcher tests under tools/archer/tests) - ``check-libomp`` (libomp tests under runtime/test. This includes check-ompt tests too) - ``check-libomptarget-*`` (libomptarget tests for specific target under libomptarget/test) diff --git a/openmp/tools/omptest/CMakeLists.txt b/openmp/tools/omptest/CMakeLists.txt new file mode 100644 index 0000000000000..19f9f898f4300 --- /dev/null +++ b/openmp/tools/omptest/CMakeLists.txt @@ -0,0 +1,116 @@ +##===----------------------------------------------------------------------===## +# +# Build OMPT unit testing library: ompTest +# +##===----------------------------------------------------------------------===## + +cmake_minimum_required(VERSION 3.22) +project(omptest LANGUAGES CXX) + +option(LIBOMPTEST_BUILD_STANDALONE + "Build ompTest 'standalone', i.e. w/o GoogleTest." OFF) +option(LIBOMPTEST_BUILD_UNITTESTS + "Build ompTest's unit tests , requires GoogleTest." OFF) + +# In absence of corresponding OMPT support: exit early +if(NOT ${LIBOMPTARGET_OMPT_SUPPORT}) + return() +endif() + +set(OMPTEST_HEADERS + ./include/AssertMacros.h + ./include/InternalEvent.h + ./include/InternalEventCommon.h + ./include/Logging.h + ./include/OmptAliases.h + ./include/OmptAsserter.h + ./include/OmptAssertEvent.h + ./include/OmptCallbackHandler.h + ./include/OmptTester.h + ./include/OmptTesterGlobals.h +) + +add_library(omptest + SHARED + + ${OMPTEST_HEADERS} + ./src/InternalEvent.cpp + ./src/InternalEventOperators.cpp + ./src/Logging.cpp + ./src/OmptAsserter.cpp + ./src/OmptAssertEvent.cpp + ./src/OmptCallbackHandler.cpp + ./src/OmptTester.cpp +) + +# Target: ompTest library +# On (implicit) request of GoogleTest, link against the one provided with LLVM. +if ((NOT LIBOMPTEST_BUILD_STANDALONE) OR LIBOMPTEST_BUILD_UNITTESTS) + # Check if standalone build was requested together with unittests + if (LIBOMPTEST_BUILD_STANDALONE) + # Emit warning: this build actually depends on LLVM's GoogleTest + message(WARNING "LIBOMPTEST_BUILD_STANDALONE and LIBOMPTEST_BUILD_UNITTESTS" + " requested simultaneously.\n" + "Linking against LLVM's GoogleTest library archives.\n" + "Disable LIBOMPTEST_BUILD_UNITTESTS to perform an actual" + " standalone build.") + # Explicitly disable LIBOMPTEST_BUILD_STANDALONE + set(LIBOMPTEST_BUILD_STANDALONE OFF) + endif() + + # Use LLVM's gtest library archive + set(GTEST_LIB "${LLVM_BINARY_DIR}/lib/libllvm_gtest.a") + # Link gtest as whole-archive to expose required symbols + set(GTEST_LINK_CMD "-Wl,--whole-archive" ${GTEST_LIB} + "-Wl,--no-whole-archive" LLVMSupport) + + # Add GoogleTest-based header + target_sources(omptest PRIVATE ./include/OmptTesterGoogleTest.h) + + # Add LLVM-provided GoogleTest include directories. + target_include_directories(omptest PRIVATE + ${LLVM_THIRD_PARTY_DIR}/unittest/googletest/include) + + # TODO: Re-visit ABI breaking checks, disable for now. + target_compile_definitions(omptest PUBLIC + -DLLVM_DISABLE_ABI_BREAKING_CHECKS_ENFORCING) + + # Link against gtest and gtest_main + target_link_libraries(omptest PRIVATE ${GTEST_LINK_CMD}) +else() + # Add 'standalone' compile definitions + target_compile_definitions(omptest PRIVATE + -DOPENMP_LIBOMPTEST_BUILD_STANDALONE) + + # Add 'standalone' source files + target_sources(omptest PRIVATE + ./include/OmptTesterStandalone.h + ./src/OmptTesterStandalone.cpp) +endif() + +# Add common include directories. +target_include_directories(omptest PRIVATE + ./include + ${LIBOMPTARGET_INCLUDE_DIR}) +target_compile_features(omptest PRIVATE cxx_std_17) + +# Create and install package configuration files. +configure_file( + ${omptest_SOURCE_DIR}/cmake/omptest-config.cmake.in + ${omptest_BINARY_DIR}/cmake/omptest-config.cmake @ONLY) + +install(FILES ${omptest_BINARY_DIR}/cmake/omptest-config.cmake + DESTINATION "${OPENMP_INSTALL_LIBDIR}/cmake/openmp/omptest") + +# Install libomptest header files: Copy header-files from include dir +install(DIRECTORY ./include + DESTINATION "${LIBOMP_HEADERS_INSTALL_PATH}/omptest" + FILES_MATCHING PATTERN "*.h") + +install(TARGETS omptest LIBRARY COMPONENT omptest + DESTINATION "${OPENMP_INSTALL_LIBDIR}") + +# Discover unit tests (added to check-openmp) +if(LIBOMPTEST_BUILD_UNITTESTS) + add_subdirectory(test) +endif() diff --git a/openmp/tools/omptest/README.md b/openmp/tools/omptest/README.md new file mode 100644 index 0000000000000..bfed871b59bdb --- /dev/null +++ b/openmp/tools/omptest/README.md @@ -0,0 +1,279 @@ + +README for the OpenMP Tooling Interface Testing Library (ompTest) +================================================================= + +# Introduction +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. + +# Testing macros + +Corresponding macro definitions are located in: `./include/AssertMacros.h` + +## OMPT_GENERATE_EVENTS(NumberOfCopies, EventMacro) +`TODO` + +## OMPT_ASSERT_SET_EVENT(Name, Group, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SET(EventTy, ...) +`TODO` + +## OMPT_ASSERT_SET_GROUPED(Group, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SET_NAMED(Name, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SET_EVENT_NOT(Name, Group, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SET_NOT(EventTy, ...) +`TODO` + +## OMPT_ASSERT_SET_GROUPED_NOT(Group, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SET_NAMED_NOT(Name, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SEQUENCE_EVENT(Name, Group, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SEQUENCE(EventTy, ...) +This macro checks for the occurrence of the provided event, which also +entails the exact sequence of events. When only using this assertion macro one +has to provide every single event in the exact order of occurrence. + +## OMPT_ASSERT_SEQUENCE_GROUPED(Group, EventTy, ...) +This macro acts like `OMPT_ASSERT_SEQUENCE` with the addition of grouping. + +## OMPT_ASSERT_SEQUENCE_NAMED(Name, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SEQUENCE_EVENT_NOT(Name, Group, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SEQUENCE_NOT(EventTy, ...) +`TODO` + +## OMPT_ASSERT_SEQUENCE_GROUPED_NOT(Group, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SEQUENCE_NAMED_NOT(Name, EventTy, ...) +`TODO` + +## OMPT_ASSERT_SEQUENCE_SUSPEND() +`TODO` + +## OMPT_ASSERT_SEQUENCE_ONLY(EventTy, ...) +This macro acts like `OMPT_ASSERT_SEQUENCE`, while actually being preceded +-AND- succeeded by commands to suspend sequenced assertion until the next match. +As a result, one may omit all other "unneccessary" events from the sequence. + +## OMPT_ASSERT_SEQUENCE_GROUPED_ONLY(Group, EventTy, ...) +This macro acts like `OMPT_ASSERT_SEQUENCE_ONLY`, plus grouping. + +## OMPT_ASSERT_SEQUENCE_NAMED_ONLY(Name, EventTy, ...) +`TODO` + +## OMPT_ASSERTER_MODE_STRICT(Asserter) +`TODO` + +## OMPT_ASSERTER_MODE_RELAXED(Asserter) +`TODO` + +## OMPT_ASSERT_SEQUENCE_MODE_STRICT() +`TODO` + +## OMPT_ASSERT_SEQUENCE_MODE_RELAXED() +`TODO` + +## OMPT_ASSERT_SET_MODE_STRICT() +`TODO` + +## OMPT_ASSERT_SET_MODE_RELAXED() +`TODO` + +## OMPT_ASSERTER_DISABLE(Asserter) +`TODO` + +## OMPT_ASSERTER_ENABLE(Asserter) +`TODO` + +## OMPT_ASSERT_SET_DISABLE() +`TODO` + +## OMPT_ASSERT_SET_ENABLE() +`TODO` + +## OMPT_ASSERT_SEQUENCE_DISABLE() +`TODO` + +## OMPT_ASSERT_SEQUENCE_ENABLE() +`TODO` + +## OMPT_REPORT_EVENT_DISABLE() +`TODO` + +## OMPT_REPORT_EVENT_ENABLE() +`TODO` + +## OMPT_ASSERTER_PERMIT_EVENT(Asserter, EventTy) +`TODO` + +## OMPT_ASSERTER_SUPPRESS_EVENT(Asserter, EventTy) +`TODO` + +## OMPT_PERMIT_EVENT(EventTy) +`TODO` + +## OMPT_SUPPRESS_EVENT(EventTy) +`TODO` + +## OMPT_ASSERTER_LOG_LEVEL(Asserter, LogLevel) +`TODO` + +## OMPT_ASSERTER_LOG_FORMATTED(Asserter, FormatLog) +`TODO` + +## OMPT_ASSERT_SYNC_POINT(SyncPointName) +`TODO` + +### Grouping Asserts + +This allows to generate and verify data during runtime of a test. +Currently, we only use target region information which manifests into groups. +This allows to correlate multiple events to a certain target region without +manual interaction just by specifying a groupname for these events. + +When a target region is encountered and we are about to enter it, we gather the +`target_id` (non-EMI) -OR- `target_data->value` (EMI). This value is stored +along the groupname for future reference. Upon target region end, the +corresponding group is erased. (Note: The groupname is available again.) + +Other asserted callbacks which may occur within target regions query their +groupname: retrieving and comparing the value of the group against the observed +event's value. + +### Suspending Sequenced Asserts + +When a sequence of events is not of interest while testing, these additional +events may be ignored by suspending the assertion until the next match. This +can be done by using `OMPT_ASSERT_SEQUENCE_SUSPEND` manually or the `_ONLY` +macro variants, like `OMPT_ASSERT_GROUPED_SEQUENCE_ONLY`. + +The former adds a special event to the queue of expected events and signal +that any non-matching event should be ignored rather than failing the test. +`_ONLY` macros embed their corresponding macro between two calls to +`OMPT_ASSERT_SEQUENCE_SUSPEND`. As a consequence, we enter passive assertion +until a match occurs, then enter passive assertion again. This enables us to +"only" assert a certain, single event in arbitrary circumstances. + +### Asserter Modes +`TODO` + +## Aliases (shorthands) +To allow for easier writing of tests and enhanced readability, the following set +of aliases is introduced. The left hand side represents the original value, +while the right hand side depicts the shorthand version. + +| Type | Enum Value | Shorthand | +|---------------------------|---------------------------------------------|---------------------------| +| **ompt_scope_endpoint_t** | | | +| | ompt_scope_begin | BEGIN | +| | ompt_scope_end | END | +| | ompt_scope_beginend | BEGINEND | +| **ompt_target_t** | | | +| | ompt_target | TARGET | +| | ompt_target_enter_data | ENTER_DATA | +| | ompt_target_exit_data | EXIT_DATA | +| | ompt_target_update | UPDATE | +| | ompt_target_nowait | TARGET_NOWAIT | +| | ompt_target_enter_data_nowait | ENTER_DATA_NOWAIT | +| | ompt_target_exit_data_nowait | EXIT_DATA_NOWAIT | +| | ompt_target_update_nowait | UPDATE_NOWAIT | +| **ompt_target_data_op_t** | | | +| | ompt_target_data_alloc | ALLOC | +| | ompt_target_data_transfer_to_device | H2D | +| | ompt_target_data_transfer_from_device | D2H | +| | ompt_target_data_delete | DELETE | +| | ompt_target_data_associate | ASSOCIATE | +| | ompt_target_data_disassociate | DISASSOCIATE | +| | ompt_target_data_alloc_async | ALLOC_ASYNC | +| | ompt_target_data_transfer_to_device_async | H2D_ASYNC | +| | ompt_target_data_transfer_from_device_async | D2H_ASYNC | +| | ompt_target_data_delete_async | DELETE_ASYNC | +| **ompt_callbacks_t** | | | +| | ompt_callback_target | CB_TARGET | +| | ompt_callback_target_data_op | CB_DATAOP | +| | ompt_callback_target_submit | CB_KERNEL | +| **ompt_work_t** | | | +| | ompt_work_loop | WORK_LOOP | +| | ompt_work_sections | WORK_SECT | +| | ompt_work_single_executor | WORK_EXEC | +| | ompt_work_single_other | WORK_SINGLE | +| | ompt_work_workshare | WORK_SHARE | +| | ompt_work_distribute | WORK_DIST | +| | ompt_work_taskloop | WORK_TASK | +| | ompt_work_scope | WORK_SCOPE | +| | ompt_work_loop_static | WORK_LOOP_STA | +| | ompt_work_loop_dynamic | WORK_LOOP_DYN | +| | ompt_work_loop_guided | WORK_LOOP_GUI | +| | ompt_work_loop_other | WORK_LOOP_OTH | +| **ompt_sync_region_t** | | | +| | ompt_sync_region_barrier | SR_BARRIER | +| | ompt_sync_region_barrier_implicit | SR_BARRIER_IMPL | +| | ompt_sync_region_barrier_explicit | SR_BARRIER_EXPL | +| | ompt_sync_region_barrier_implementation | SR_BARRIER_IMPLEMENTATION | +| | ompt_sync_region_taskwait | SR_TASKWAIT | +| | ompt_sync_region_taskgroup | SR_TASKGROUP | +| | ompt_sync_region_reduction | SR_REDUCTION | +| | ompt_sync_region_barrier_implicit_workshare | SR_BARRIER_IMPL_WORKSHARE | +| | ompt_sync_region_barrier_implicit_parallel | SR_BARRIER_IMPL_PARALLEL | +| | ompt_sync_region_barrier_teams | SR_BARRIER_TEAMS | + + +Limitations +=========== +Currently, there are some peculiarities which have to be kept in mind when using +this library: + +## Callbacks + * It is not possible to e.g. test non-EMI -AND- EMI callbacks within the same + test file. Reason: all testsuites share the initialization and therefore the + registered callbacks. + * It is not possible to check for device initialization and/or load callbacks + more than once per test file. The first testcase being run, triggers these + callbacks and is therefore the only testcase that is able to check for them. + This is because, after that, the device remains initialized. + * It is not possible to check for device finalization callbacks, as libomptest + is un-loaded before this callback occurs. Same holds true for the final + ThreadEnd event(s). + +Miscellaneous +============= + +## Default values + +To allow for easier writing of tests, many OMPT events may be created using less +parameters than actually requested by the spec -- by using default values. These +defaults are currently set to the corresponding data type's minimum as follows, +for example integers use: `std::numeric_limits::min()`. + +When an expected / user-specified event has certain values set to the +corresponding default, these values are ignored. That is, when compared to an +observed event, this property is considered as 'equal' regardless of their +actual equality relation. + +References +========== +[0]: ompTest – Unit Testing with OMPT + https://doi.org/10.1109/SCW63240.2024.00031 + +[1]: OMPTBench – OpenMP Tool Interface Conformance Testing + https://doi.org/10.1109/SCW63240.2024.00036 diff --git a/openmp/tools/omptest/cmake/omptest-config.cmake.in b/openmp/tools/omptest/cmake/omptest-config.cmake.in new file mode 100644 index 0000000000000..dca02505539b0 --- /dev/null +++ b/openmp/tools/omptest/cmake/omptest-config.cmake.in @@ -0,0 +1,29 @@ +################################################################################ +## +## omptest cmake configuration file. +## Enable support for find_package. +## +################################################################################ + +# Compute installation prefix relative to this file. +get_filename_component(LLVM_INSTALL_PREFIX "${CMAKE_CURRENT_LIST_FILE}" REALPATH) +get_filename_component(LLVM_INSTALL_PREFIX "${LLVM_INSTALL_PREFIX}" PATH) +get_filename_component(LLVM_INSTALL_PREFIX "${LLVM_INSTALL_PREFIX}" PATH) +get_filename_component(LLVM_INSTALL_PREFIX "${LLVM_INSTALL_PREFIX}" PATH) +get_filename_component(LLVM_INSTALL_PREFIX "${LLVM_INSTALL_PREFIX}" PATH) +get_filename_component(LLVM_INSTALL_PREFIX "${LLVM_INSTALL_PREFIX}" PATH) +get_filename_component(LLVM_INSTALL_PREFIX "${LLVM_INSTALL_PREFIX}" PATH) + +# Provide header and library paths. +set(LIBOMP_HEADERS_INSTALL_PATH "${LLVM_INSTALL_PREFIX}/@LIBOMP_HEADERS_INSTALL_PATH@") +set(LIBOMP_LIBRARY_INSTALL_PATH "${LLVM_INSTALL_PREFIX}/@OPENMP_INSTALL_LIBDIR@") +set(omptest_INCLUDE_DIR "${LIBOMP_HEADERS_INSTALL_PATH}/omptest/include") +set(omptest_LIBRARY_DIR "${LIBOMP_LIBRARY_INSTALL_PATH}") + +# Provide compiler default values. +set(LLVM_BIN_INSTALL_DIR "${LLVM_INSTALL_PREFIX}/bin") +set(omptest_C_COMPILER "${LLVM_BIN_INSTALL_DIR}/clang") +set(omptest_CXX_COMPILER "${LLVM_BIN_INSTALL_DIR}/clang++") + +# Provide information, if ompTest has been built 'standalone'. +set(LIBOMPTEST_BUILD_STANDALONE "@LIBOMPTEST_BUILD_STANDALONE@") diff --git a/openmp/tools/omptest/include/AssertMacros.h b/openmp/tools/omptest/include/AssertMacros.h new file mode 100644 index 0000000000000..d5d191c10dabb --- /dev/null +++ b/openmp/tools/omptest/include/AssertMacros.h @@ -0,0 +1,138 @@ +//===- AssertMacros.h - Macro aliases for ease-of-use -----------*- 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 +/// Provides macros to be used in unit tests for OMPT events. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_ASSERTMACROS_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_ASSERTMACROS_H + +#define OMPTEST_EXCLUDED_EVENT omptest::ObserveState::never +#define OMPTEST_REQUIRED_EVENT omptest::ObserveState::always + +/// ASSERT MACROS TO BE USED BY THE USER + +#define OMPT_GENERATE_EVENTS(NumberOfCopies, EventMacro) \ + for (size_t i = 0; i < NumberOfCopies; ++i) { \ + EventMacro \ + } + +// Handle a minimum unordered set of events +// Required events +#define OMPT_ASSERT_SET_EVENT(Name, Group, EventTy, ...) \ + SetAsserter->insert(OmptAssertEvent::EventTy( \ + Name, Group, OMPTEST_REQUIRED_EVENT, __VA_ARGS__)); +#define OMPT_ASSERT_SET(EventTy, ...) \ + OMPT_ASSERT_SET_EVENT("", "", EventTy, __VA_ARGS__) +#define OMPT_ASSERT_SET_GROUPED(Group, EventTy, ...) \ + OMPT_ASSERT_SET_EVENT("", Group, EventTy, __VA_ARGS__) +#define OMPT_ASSERT_SET_NAMED(Name, EventTy, ...) \ + OMPT_ASSERT_SET_EVENT(Name, "", EventTy, __VA_ARGS__) +// Excluded ("NOT") events +#define OMPT_ASSERT_SET_EVENT_NOT(Name, Group, EventTy, ...) \ + SetAsserter->insert(OmptAssertEvent::EventTy( \ + Name, Group, OMPTEST_EXCLUDED_EVENT, __VA_ARGS__)); +#define OMPT_ASSERT_SET_NOT(EventTy, ...) \ + OMPT_ASSERT_SET_EVENT_NOT("", "", EventTy, __VA_ARGS__) +#define OMPT_ASSERT_SET_GROUPED_NOT(Group, EventTy, ...) \ + OMPT_ASSERT_SET_EVENT_NOT("", Group, EventTy, __VA_ARGS__) +#define OMPT_ASSERT_SET_NAMED_NOT(Name, EventTy, ...) \ + OMPT_ASSERT_SET_EVENT_NOT(Name, "", EventTy, __VA_ARGS__) + +// Handle an exact sequence of events +// Required events +#define OMPT_ASSERT_SEQUENCE_EVENT(Name, Group, EventTy, ...) \ + SequenceAsserter->insert(OmptAssertEvent::EventTy( \ + Name, Group, OMPTEST_REQUIRED_EVENT, __VA_ARGS__)); +#define OMPT_ASSERT_SEQUENCE(EventTy, ...) \ + OMPT_ASSERT_SEQUENCE_EVENT("", "", EventTy, __VA_ARGS__) +#define OMPT_ASSERT_SEQUENCE_GROUPED(Group, EventTy, ...) \ + OMPT_ASSERT_SEQUENCE_EVENT("", Group, EventTy, __VA_ARGS__) +#define OMPT_ASSERT_SEQUENCE_NAMED(Name, EventTy, ...) \ + OMPT_ASSERT_SEQUENCE_EVENT(Name, "", EventTy, __VA_ARGS__) +// Excluded ("NOT") events +#define OMPT_ASSERT_SEQUENCE_EVENT_NOT(Name, Group, EventTy, ...) \ + SequenceAsserter->insert(OmptAssertEvent::EventTy( \ + Name, Group, OMPTEST_EXCLUDED_EVENT, __VA_ARGS__)); +#define OMPT_ASSERT_SEQUENCE_NOT(EventTy, ...) \ + OMPT_ASSERT_SEQUENCE_EVENT_NOT("", "", EventTy, __VA_ARGS__) +#define OMPT_ASSERT_SEQUENCE_GROUPED_NOT(Group, EventTy, ...) \ + OMPT_ASSERT_SEQUENCE_EVENT_NOT("", Group, EventTy, __VA_ARGS__) +#define OMPT_ASSERT_SEQUENCE_NAMED_NOT(Name, EventTy, ...) \ + OMPT_ASSERT_SEQUENCE_EVENT_NOT(Name, "", EventTy, __VA_ARGS__) +// Special command: suspend active assertion +// The created event is not correlated to any observed event +#define OMPT_ASSERT_SEQUENCE_SUSPEND() \ + SequenceAsserter->insert( \ + OmptAssertEvent::AssertionSuspend("", "", OMPTEST_EXCLUDED_EVENT)); +#define OMPT_ASSERT_SEQUENCE_ONLY(EventTy, ...) \ + OMPT_ASSERT_SEQUENCE_SUSPEND() \ + OMPT_ASSERT_SEQUENCE_EVENT("", "", EventTy, __VA_ARGS__) \ + OMPT_ASSERT_SEQUENCE_SUSPEND() +#define OMPT_ASSERT_SEQUENCE_GROUPED_ONLY(Group, EventTy, ...) \ + OMPT_ASSERT_SEQUENCE_SUSPEND() \ + OMPT_ASSERT_SEQUENCE_EVENT("", Group, EventTy, __VA_ARGS__) \ + OMPT_ASSERT_SEQUENCE_SUSPEND() +#define OMPT_ASSERT_SEQUENCE_NAMED_ONLY(Name, EventTy, ...) \ + OMPT_ASSERT_SEQUENCE_SUSPEND() \ + OMPT_ASSERT_SEQUENCE_EVENT(Name, "", EventTy, __VA_ARGS__) \ + OMPT_ASSERT_SEQUENCE_SUSPEND() + +#define OMPT_ASSERTER_MODE_STRICT(Asserter) \ + Asserter->setOperationMode(AssertMode::strict); +#define OMPT_ASSERTER_MODE_RELAXED(Asserter) \ + Asserter->setOperationMode(AssertMode::relaxed); +#define OMPT_ASSERT_SEQUENCE_MODE_STRICT() \ + OMPT_ASSERTER_MODE_STRICT(SequenceAsserter) +#define OMPT_ASSERT_SEQUENCE_MODE_RELAXED() \ + OMPT_ASSERTER_MODE_RELAXED(SequenceAsserter) +#define OMPT_ASSERT_SET_MODE_STRICT() OMPT_ASSERTER_MODE_STRICT(SetAsserter) +#define OMPT_ASSERT_SET_MODE_RELAXED() OMPT_ASSERTER_MODE_RELAXED(SetAsserter) + +// Enable / disable asserters entirely +#define OMPT_ASSERTER_DISABLE(Asserter) Asserter->setActive(false); +#define OMPT_ASSERTER_ENABLE(Asserter) Asserter->setActive(true); +#define OMPT_ASSERT_SET_DISABLE() OMPT_ASSERTER_DISABLE(SetAsserter) +#define OMPT_ASSERT_SET_ENABLE() OMPT_ASSERTER_ENABLE(SetAsserter) +#define OMPT_ASSERT_SEQUENCE_DISABLE() OMPT_ASSERTER_DISABLE(SequenceAsserter) +#define OMPT_ASSERT_SEQUENCE_ENABLE() OMPT_ASSERTER_ENABLE(SequenceAsserter) +#define OMPT_REPORT_EVENT_DISABLE() OMPT_ASSERTER_DISABLE(EventReporter) +#define OMPT_REPORT_EVENT_ENABLE() OMPT_ASSERTER_ENABLE(EventReporter) + +// Enable / disable certain event types for asserters +#define OMPT_ASSERTER_PERMIT_EVENT(Asserter, EventTy) \ + Asserter->permitEvent(EventTy); +#define OMPT_ASSERTER_SUPPRESS_EVENT(Asserter, EventTy) \ + Asserter->suppressEvent(EventTy); +#define OMPT_PERMIT_EVENT(EventTy) \ + OMPT_ASSERTER_PERMIT_EVENT(SetAsserter, EventTy); \ + OMPT_ASSERTER_PERMIT_EVENT(EventReporter, EventTy); \ + OMPT_ASSERTER_PERMIT_EVENT(SequenceAsserter, EventTy); +#define OMPT_SUPPRESS_EVENT(EventTy) \ + OMPT_ASSERTER_SUPPRESS_EVENT(SetAsserter, EventTy); \ + OMPT_ASSERTER_SUPPRESS_EVENT(EventReporter, EventTy); \ + OMPT_ASSERTER_SUPPRESS_EVENT(SequenceAsserter, EventTy); + +// Set logging level for asserters +// Note: Logger is a singleton, hence this will affect all asserter instances +#define OMPT_ASSERTER_LOG_LEVEL(Asserter, LogLevel) \ + Asserter->getLog()->setLoggingLevel(LogLevel); + +// Set log formatting (esp. coloring) for asserters +// Note: Logger is a singleton, hence this will affect all asserter instances +#define OMPT_ASSERTER_LOG_FORMATTED(Asserter, FormatLog) \ + Asserter->getLog()->setFormatOutput(FormatLog); + +// SyncPoint handling +#define OMPT_ASSERT_SYNC_POINT(SyncPointName) \ + flush_traced_devices(); \ + OmptCallbackHandler::get().handleAssertionSyncPoint(SyncPointName); + +#endif diff --git a/openmp/tools/omptest/include/InternalEvent.h b/openmp/tools/omptest/include/InternalEvent.h new file mode 100644 index 0000000000000..455d8d996e5f4 --- /dev/null +++ b/openmp/tools/omptest/include/InternalEvent.h @@ -0,0 +1,331 @@ +//===- InternalEvent.h - Internal event representation ----------*- 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 +/// Declares internal event representations along the default CTOR definition. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_INTERNALEVENT_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_INTERNALEVENT_H + +#include "InternalEventCommon.h" + +#include +#include +#include + +#define expectedDefault(TypeName) std::numeric_limits::min() + +namespace omptest { + +namespace util { + +/// String manipulation helper function. Takes up to 8 bytes of data and returns +/// their hexadecimal representation as string. The data can be expanded to the +/// given size in bytes and will by default be prefixed with '0x'. +std::string makeHexString(uint64_t Data, bool IsPointer = true, + size_t DataBytes = 0, bool ShowHexBase = true); + +} // namespace util + +namespace internal { +// clang-format off +event_class_w_custom_body(AssertionSyncPoint, \ + AssertionSyncPoint(const std::string &Name) \ + : InternalEvent(EventTy::AssertionSyncPoint), Name(Name) {} \ + \ + const std::string Name; \ +) +event_class_stub(AssertionSuspend) +event_class_w_custom_body(ThreadBegin, \ + ThreadBegin(ompt_thread_t ThreadType) \ + : InternalEvent(EventTy::ThreadBegin), ThreadType(ThreadType) {} \ + \ + ompt_thread_t ThreadType; \ +) +event_class_w_custom_body(ThreadEnd, \ + ThreadEnd() : InternalEvent(EventTy::ThreadEnd) {} \ +) +event_class_w_custom_body(ParallelBegin, \ + ParallelBegin(int NumThreads) \ + : InternalEvent(EventTy::ParallelBegin), NumThreads(NumThreads) {} \ + \ + unsigned int NumThreads; \ +) +event_class_w_custom_body(ParallelEnd, \ + ParallelEnd(ompt_data_t *ParallelData, ompt_data_t *EncounteringTaskData, \ + int Flags, const void *CodeptrRA) \ + : InternalEvent(EventTy::ParallelEnd), ParallelData(ParallelData), \ + EncounteringTaskData(EncounteringTaskData), Flags(Flags), \ + CodeptrRA(CodeptrRA) {} \ + \ +ompt_data_t *ParallelData; \ +ompt_data_t *EncounteringTaskData; \ +int Flags; \ +const void *CodeptrRA; \ +) +event_class_w_custom_body(Work, \ + Work(ompt_work_t WorkType, ompt_scope_endpoint_t Endpoint, \ + ompt_data_t *ParallelData, ompt_data_t *TaskData, uint64_t Count, \ + const void *CodeptrRA) \ + : InternalEvent(EventTy::Work), WorkType(WorkType), Endpoint(Endpoint), \ + ParallelData(ParallelData), TaskData(TaskData), Count(Count), \ + CodeptrRA(CodeptrRA) {} \ + \ +ompt_work_t WorkType; \ +ompt_scope_endpoint_t Endpoint; \ +ompt_data_t *ParallelData; \ +ompt_data_t *TaskData; \ +uint64_t Count; \ +const void *CodeptrRA; \ +) +event_class_w_custom_body(Dispatch, \ + Dispatch(ompt_data_t *ParallelData, ompt_data_t *TaskData, \ + ompt_dispatch_t Kind, ompt_data_t Instance) \ + : InternalEvent(EventTy::Dispatch), ParallelData(ParallelData), \ + TaskData(TaskData), Kind(Kind), Instance(Instance) {} \ + \ +ompt_data_t *ParallelData; \ +ompt_data_t *TaskData; \ +ompt_dispatch_t Kind; \ +ompt_data_t Instance; \ +) +event_class_w_custom_body(TaskCreate, \ + TaskCreate(ompt_data_t *EncounteringTaskData, \ + const ompt_frame_t *EncounteringTaskFrame, \ + ompt_data_t *NewTaskData, int Flags, int HasDependences, \ + const void *CodeptrRA) \ + : InternalEvent(EventTy::TaskCreate), \ + EncounteringTaskData(EncounteringTaskData), \ + EncounteringTaskFrame(EncounteringTaskFrame), NewTaskData(NewTaskData), \ + Flags(Flags), HasDependences(HasDependences), CodeptrRA(CodeptrRA) {} \ + \ +ompt_data_t *EncounteringTaskData; \ +const ompt_frame_t *EncounteringTaskFrame; \ +ompt_data_t *NewTaskData; \ +int Flags; \ +int HasDependences; \ +const void *CodeptrRA; \ +) +event_class_stub(Dependences) +event_class_stub(TaskDependence) +event_class_stub(TaskSchedule) +event_class_w_custom_body(ImplicitTask, \ + ImplicitTask(ompt_scope_endpoint_t Endpoint, ompt_data_t *ParallelData, \ + ompt_data_t *TaskData, unsigned int ActualParallelism, \ + unsigned int Index, int Flags) \ + : InternalEvent(EventTy::ImplicitTask), Endpoint(Endpoint), \ + ParallelData(ParallelData), TaskData(TaskData), \ + ActualParallelism(ActualParallelism), Index(Index), Flags(Flags) {} \ + \ +ompt_scope_endpoint_t Endpoint; \ +ompt_data_t *ParallelData; \ +ompt_data_t *TaskData; \ +unsigned int ActualParallelism; \ +unsigned int Index; \ +int Flags; \ +) +event_class_stub(Masked) +event_class_w_custom_body(SyncRegion, \ + SyncRegion(ompt_sync_region_t Kind, ompt_scope_endpoint_t Endpoint, \ + ompt_data_t *ParallelData, ompt_data_t *TaskData, \ + const void *CodeptrRA) \ + : InternalEvent(EventTy::SyncRegion), Kind(Kind), Endpoint(Endpoint), \ + ParallelData(ParallelData), TaskData(TaskData), CodeptrRA(CodeptrRA) {} \ + \ +ompt_sync_region_t Kind; \ +ompt_scope_endpoint_t Endpoint; \ +ompt_data_t *ParallelData; \ +ompt_data_t *TaskData; \ +const void *CodeptrRA; \ +) +event_class_stub(MutexAcquire) +event_class_stub(Mutex) +event_class_stub(NestLock) +event_class_stub(Flush) +event_class_stub(Cancel) +event_class_w_custom_body(Target, \ + Target(ompt_target_t Kind, ompt_scope_endpoint_t Endpoint, int DeviceNum, \ + ompt_data_t *TaskData, ompt_id_t TargetId, const void *CodeptrRA) \ + : InternalEvent(EventTy::Target), Kind(Kind), Endpoint(Endpoint), \ + DeviceNum(DeviceNum), TaskData(TaskData), TargetId(TargetId), \ + CodeptrRA(CodeptrRA) {} \ + \ + ompt_target_t Kind; \ + ompt_scope_endpoint_t Endpoint; \ + int DeviceNum; \ + ompt_data_t *TaskData; \ + ompt_id_t TargetId; \ + const void *CodeptrRA; \ +) +event_class_w_custom_body(TargetEmi, \ + TargetEmi(ompt_target_t Kind, ompt_scope_endpoint_t Endpoint, int DeviceNum, \ + ompt_data_t *TaskData, ompt_data_t *TargetTaskData, \ + ompt_data_t *TargetData, const void *CodeptrRA) \ + : InternalEvent(EventTy::TargetEmi), Kind(Kind), Endpoint(Endpoint), \ + DeviceNum(DeviceNum), TaskData(TaskData), \ + TargetTaskData(TargetTaskData), TargetData(TargetData), \ + CodeptrRA(CodeptrRA) {} \ + \ + ompt_target_t Kind; \ + ompt_scope_endpoint_t Endpoint; \ + int DeviceNum; \ + ompt_data_t *TaskData; \ + ompt_data_t *TargetTaskData; \ + ompt_data_t *TargetData; \ + const void *CodeptrRA; \ +) +event_class_w_custom_body(TargetDataOp, \ + TargetDataOp(ompt_id_t TargetId, ompt_id_t HostOpId, \ + ompt_target_data_op_t OpType, void *SrcAddr, int SrcDeviceNum, \ + void *DstAddr, int DstDeviceNum, size_t Bytes, \ + const void *CodeptrRA) \ + : InternalEvent(EventTy::TargetDataOp), TargetId(TargetId), \ + HostOpId(HostOpId), OpType(OpType), SrcAddr(SrcAddr), \ + SrcDeviceNum(SrcDeviceNum), DstAddr(DstAddr), \ + DstDeviceNum(DstDeviceNum), Bytes(Bytes), CodeptrRA(CodeptrRA) {} \ + \ + ompt_id_t TargetId; \ + ompt_id_t HostOpId; \ + ompt_target_data_op_t OpType; \ + void *SrcAddr; \ + int SrcDeviceNum; \ + void *DstAddr; \ + int DstDeviceNum; \ + size_t Bytes; \ + const void *CodeptrRA; \ +) +event_class_w_custom_body(TargetDataOpEmi, \ + TargetDataOpEmi(ompt_scope_endpoint_t Endpoint, ompt_data_t *TargetTaskData, \ + ompt_data_t *TargetData, ompt_id_t *HostOpId, \ + ompt_target_data_op_t OpType, void *SrcAddr, \ + int SrcDeviceNum, void *DstAddr, int DstDeviceNum, \ + size_t Bytes, const void *CodeptrRA) \ + : InternalEvent(EventTy::TargetDataOpEmi), Endpoint(Endpoint), \ + TargetTaskData(TargetTaskData), TargetData(TargetData), \ + HostOpId(HostOpId), OpType(OpType), SrcAddr(SrcAddr), \ + SrcDeviceNum(SrcDeviceNum), DstAddr(DstAddr), \ + DstDeviceNum(DstDeviceNum), Bytes(Bytes), CodeptrRA(CodeptrRA) {} \ + \ + ompt_scope_endpoint_t Endpoint; \ + ompt_data_t *TargetTaskData; \ + ompt_data_t *TargetData; \ + ompt_id_t *HostOpId; \ + ompt_target_data_op_t OpType; \ + void *SrcAddr; \ + int SrcDeviceNum; \ + void *DstAddr; \ + int DstDeviceNum; \ + size_t Bytes; \ + const void *CodeptrRA; \ +) +event_class_w_custom_body(TargetSubmit, \ + TargetSubmit(ompt_id_t TargetId, ompt_id_t HostOpId, \ + unsigned int RequestedNumTeams) \ + : InternalEvent(EventTy::TargetSubmit), TargetId(TargetId), \ + HostOpId(HostOpId), RequestedNumTeams(RequestedNumTeams) {} \ + \ + ompt_id_t TargetId; \ + ompt_id_t HostOpId; \ + unsigned int RequestedNumTeams; \ +) +event_class_w_custom_body(TargetSubmitEmi, \ + TargetSubmitEmi(ompt_scope_endpoint_t Endpoint, ompt_data_t *TargetData, \ + ompt_id_t *HostOpId, unsigned int RequestedNumTeams) \ + : InternalEvent(EventTy::TargetSubmitEmi), Endpoint(Endpoint), \ + TargetData(TargetData), HostOpId(HostOpId), \ + RequestedNumTeams(RequestedNumTeams) {} \ + \ + ompt_scope_endpoint_t Endpoint; \ + ompt_data_t *TargetData; \ + ompt_id_t *HostOpId; \ + unsigned int RequestedNumTeams; \ +) +event_class_stub(ControlTool) +event_class_w_custom_body(DeviceInitialize, \ + DeviceInitialize(int DeviceNum, const char *Type, ompt_device_t *Device, \ + ompt_function_lookup_t LookupFn, const char *DocStr) \ + : InternalEvent(EventTy::DeviceInitialize), DeviceNum(DeviceNum), \ + Type(Type), Device(Device), LookupFn(LookupFn), DocStr(DocStr) {} \ + \ + int DeviceNum; \ + const char *Type; \ + ompt_device_t *Device; \ + ompt_function_lookup_t LookupFn; \ + const char *DocStr; \ +) +event_class_w_custom_body(DeviceFinalize, \ + DeviceFinalize(int DeviceNum) \ + : InternalEvent(EventTy::DeviceFinalize), DeviceNum(DeviceNum) {} \ + \ + int DeviceNum; \ +) +event_class_w_custom_body(DeviceLoad, \ + DeviceLoad(int DeviceNum, const char *Filename, int64_t OffsetInFile, \ + void *VmaInFile, size_t Bytes, void *HostAddr, void *DeviceAddr, \ + uint64_t ModuleId) \ + : InternalEvent(EventTy::DeviceLoad), DeviceNum(DeviceNum), \ + Filename(Filename), OffsetInFile(OffsetInFile), VmaInFile(VmaInFile), \ + Bytes(Bytes), HostAddr(HostAddr), DeviceAddr(DeviceAddr), \ + ModuleId(ModuleId) {} \ + \ + int DeviceNum; \ + const char *Filename; \ + int64_t OffsetInFile; \ + void *VmaInFile; \ + size_t Bytes; \ + void *HostAddr; \ + void *DeviceAddr; \ + uint64_t ModuleId; \ +) +event_class_stub(DeviceUnload) +event_class_w_custom_body(BufferRequest, \ + BufferRequest(int DeviceNum, ompt_buffer_t **Buffer, size_t *Bytes) \ + : InternalEvent(EventTy::BufferRequest), DeviceNum(DeviceNum), \ + Buffer(Buffer), Bytes(Bytes) {} \ + \ + int DeviceNum; \ + ompt_buffer_t **Buffer; \ + size_t *Bytes; \ +) +event_class_w_custom_body(BufferComplete, \ + BufferComplete(int DeviceNum, ompt_buffer_t *Buffer, size_t Bytes, \ + ompt_buffer_cursor_t Begin, int BufferOwned) \ + : InternalEvent(EventTy::BufferComplete), DeviceNum(DeviceNum), \ + Buffer(Buffer), Bytes(Bytes), Begin(Begin), BufferOwned(BufferOwned) {} \ + \ + int DeviceNum; \ + ompt_buffer_t *Buffer; \ + size_t Bytes; \ + ompt_buffer_cursor_t Begin; \ + int BufferOwned; \ +) +event_class_w_custom_body(BufferRecord, \ + BufferRecord(ompt_record_ompt_t *RecordPtr) \ + : InternalEvent(EventTy::BufferRecord), RecordPtr(RecordPtr) { \ + if (RecordPtr != nullptr) Record = *RecordPtr; \ + else memset(&Record, 0, sizeof(ompt_record_ompt_t)); \ + } \ + \ + ompt_record_ompt_t Record; \ + ompt_record_ompt_t *RecordPtr; \ +) +event_class_w_custom_body(BufferRecordDeallocation, \ + BufferRecordDeallocation(ompt_buffer_t *Buffer) \ + : InternalEvent(EventTy::BufferRecordDeallocation), Buffer(Buffer) {} \ + \ + ompt_buffer_t *Buffer; \ +) +// clang-format on + +} // namespace internal + +} // namespace omptest + +#endif diff --git a/openmp/tools/omptest/include/InternalEventCommon.h b/openmp/tools/omptest/include/InternalEventCommon.h new file mode 100644 index 0000000000000..e48eeddd975ed --- /dev/null +++ b/openmp/tools/omptest/include/InternalEventCommon.h @@ -0,0 +1,133 @@ +//===- InternalEventCommon.h - Common internal event basics -----*- 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 +/// Provides event types, and class/operator declaration macros. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_INTERNALEVENTCOMMON_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_INTERNALEVENTCOMMON_H + +#include "omp-tools.h" + +#include +#include + +namespace omptest { + +namespace internal { +/// Enum values are used for comparison of observed and asserted events +/// List is based on OpenMP 5.2 specification, table 19.2 (page 447) +enum class EventTy { + None, // not part of OpenMP spec, used for implementation + AssertionSyncPoint, // not part of OpenMP spec, used for implementation + AssertionSuspend, // not part of OpenMP spec, used for implementation + BufferRecord, // not part of OpenMP spec, used for implementation + BufferRecordDeallocation, // not part of OpenMP spec, used for implementation + ThreadBegin, + ThreadEnd, + ParallelBegin, + ParallelEnd, + Work, + Dispatch, + TaskCreate, // TODO: Implement + Dependences, // TODO: Implement + TaskDependence, // TODO: Implement + TaskSchedule, // TODO: Implement + ImplicitTask, // TODO: Implement + Masked, // TODO: Implement + SyncRegion, + MutexAcquire, // TODO: Implement + Mutex, // TODO: Implement + NestLock, // TODO: Implement + Flush, // TODO: Implement + Cancel, // TODO: Implement + DeviceInitialize, + DeviceFinalize, + DeviceLoad, + DeviceUnload, + BufferRequest, + BufferComplete, + TargetDataOp, + TargetDataOpEmi, + Target, + TargetEmi, + TargetSubmit, + TargetSubmitEmi, + ControlTool +}; + +struct InternalEvent { + EventTy Type; + EventTy getType() const { return Type; } + + InternalEvent() : Type(EventTy::None) {} + InternalEvent(EventTy T) : Type(T) {} + virtual ~InternalEvent() = default; + + virtual bool equals(const InternalEvent *o) const { + assert(false && "Base class implementation"); + return false; + }; + + virtual std::string toString() const { + std::string S{"InternalEvent: Type="}; + S.append(std::to_string((uint32_t)Type)); + return S; + } +}; + +#define event_class_stub(EvTy) \ + struct EvTy : public InternalEvent { \ + virtual bool equals(const InternalEvent *o) const override; \ + EvTy() : InternalEvent(EventTy::EvTy) {} \ + }; + +#define event_class_w_custom_body(EvTy, ...) \ + struct EvTy : public InternalEvent { \ + virtual bool equals(const InternalEvent *o) const override; \ + std::string toString() const override; \ + __VA_ARGS__ \ + }; + +#define event_class_operator_stub(EvTy) \ + bool operator==(const EvTy &Expected, const EvTy &Observed) { return true; } + +#define event_class_operator_w_body(EvTy, ...) \ + bool operator==(const EvTy &Expected, const EvTy &Observed) { __VA_ARGS__ } + +/// Template "base" for the cast functions generated in the define_cast_func +/// macro +template const To *cast(const InternalEvent *From) { + return nullptr; +} + +/// Generates template specialization of the cast operation for the specified +/// EvTy as the template parameter +#define define_cast_func(EvTy) \ + template <> const EvTy *cast(const InternalEvent *From) { \ + if (From->getType() == EventTy::EvTy) \ + return static_cast(From); \ + return nullptr; \ + } + +/// Auto generate the equals override to cast and dispatch to the specific class +/// operator== +#define class_equals_op(EvTy) \ + bool EvTy::equals(const InternalEvent *o) const { \ + if (const auto O = cast(o)) \ + return *this == *O; \ + return false; \ + } + +} // namespace internal + +} // namespace omptest + +#endif diff --git a/openmp/tools/omptest/include/Logging.h b/openmp/tools/omptest/include/Logging.h new file mode 100644 index 0000000000000..0104191b1d15f --- /dev/null +++ b/openmp/tools/omptest/include/Logging.h @@ -0,0 +1,155 @@ +//===- Logging.h - General logging class ------------------------*- 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 +/// Provides ompTest-tailored logging, with log-levels and formatting/coloring. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_LOGGING_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_LOGGING_H + +#include "OmptAssertEvent.h" + +#include +#include +#include +#include +#include +#include + +namespace omptest { +namespace logging { + +enum class Level : uint32_t { + // Levels (Note: DEBUG may already be reserved) + DIAGNOSTIC = 10, + INFO = 20, + WARNING = 30, + ERROR = 40, + CRITICAL = 50, + + // Types used for formatting options + Default, + ExpectedEvent, + ObservedEvent, + OffendingEvent, + + // Suppress all prints + SILENT = 0xFFFFFFFF +}; + +enum class FormatOption : uint32_t { + // General options + // Note: BOLD is actually "BRIGHT" -- But it will be perceived as 'bold' font + // It is implicitly switching colors to the 'Light' variant + // Thus, it has -NO EFFECT- when already using a Light* color + NONE = 0, + BOLD = 1, + DIM = 2, + UNDERLINED = 4, + BLINK = 5, + INVERTED = 7, + HIDDEN = 8, + // Foreground colors + COLOR_Default = 39, + COLOR_Black = 30, + COLOR_Red = 31, + COLOR_Green = 32, + COLOR_Yellow = 33, + COLOR_Blue = 34, + COLOR_Magenta = 35, + COLOR_Cyan = 36, + COLOR_LightGray = 37, + COLOR_DarkGray = 90, + COLOR_LightRed = 91, + COLOR_LightGreen = 92, + COLOR_LightYellow = 93, + COLOR_LightBlue = 94, + COLOR_LightMagenta = 95, + COLOR_LightCyan = 96, + COLOR_White = 97, + // Background colors + COLOR_BG_Default = 49, + COLOR_BG_Black = 40, + COLOR_BG_Red = 41, + COLOR_BG_Green = 42, + COLOR_BG_Yellow = 43, + COLOR_BG_Blue = 44, + COLOR_BG_Magenta = 45, + COLOR_BG_Cyan = 46, + COLOR_BG_LightGray = 47, + COLOR_BG_DarkGray = 100, + COLOR_BG_LightRed = 101, + COLOR_BG_LightGreen = 102, + COLOR_BG_LightYellow = 103, + COLOR_BG_LightBlue = 104, + COLOR_BG_LightMagenta = 105, + COLOR_BG_LightCyan = 106, + COLOR_BG_White = 107 +}; + +/// Returns a string representation of the given logging level. +const char *to_string(Level LogLevel); + +/// Returns the format options as escaped sequence, for the given logging level +std::string getFormatSequence(Level LogLevel = Level::Default); + +/// Format the given message with the provided option(s) and return it. +/// Here formatting is only concerning control sequences using character +/// which can be obtained using '\e' (on console), '\033' or '\x1B'. +std::string format(const std::string &Message, FormatOption Option); +std::string format(const std::string &Message, std::set Options); + +class Logger { +public: + Logger(Level LogLevel = Level::WARNING, std::ostream &OutStream = std::cerr, + bool FormatOutput = true); + ~Logger(); + + /// Log the given message to the output. + void log(Level LogLevel, const std::string &Message) const; + + /// Log a single event mismatch. + void eventMismatch(const omptest::OmptAssertEvent &OffendingEvent, + const std::string &Message, + Level LogLevel = Level::ERROR) const; + + /// Log an event-pair mismatch. + void eventMismatch(const omptest::OmptAssertEvent &ExpectedEvent, + const omptest::OmptAssertEvent &ObservedEvent, + const std::string &Message, + Level LogLevel = Level::ERROR) const; + + /// Set if output is being formatted (e.g. colored). + void setFormatOutput(bool Enabled); + + /// Return the current (minimum) Logging Level. + Level getLoggingLevel() const; + + /// Set the (minimum) Logging Level. + void setLoggingLevel(Level LogLevel); + +private: + /// The minimum logging level that is considered by the logger instance. + Level LoggingLevel; + + /// The output stream used by the logger instance. + std::ostream &OutStream; + + /// Determine if log messages are formatted using control sequences. + bool FormatOutput; + + /// Mutex to ensure serialized logging + mutable std::mutex LogMutex; +}; + +} // namespace logging +} // namespace omptest + +#endif \ No newline at end of file diff --git a/openmp/tools/omptest/include/OmptAliases.h b/openmp/tools/omptest/include/OmptAliases.h new file mode 100644 index 0000000000000..500be5ef9f749 --- /dev/null +++ b/openmp/tools/omptest/include/OmptAliases.h @@ -0,0 +1,85 @@ +//===- OmptAliases.h - Shorthand aliases for OMPT enum values ---*- 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 +/// Defines shorthand aliases for OMPT enum values, providing improved +/// ease-of-use and readability. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTALIASES_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTALIASES_H + +#include + +/// Aliases for enum: ompt_scope_endpoint_t +constexpr ompt_scope_endpoint_t BEGIN = ompt_scope_begin; +constexpr ompt_scope_endpoint_t END = ompt_scope_end; +constexpr ompt_scope_endpoint_t BEGINEND = ompt_scope_beginend; + +/// Aliases for enum: ompt_target_t +constexpr ompt_target_t TARGET = ompt_target; +constexpr ompt_target_t ENTER_DATA = ompt_target_enter_data; +constexpr ompt_target_t EXIT_DATA = ompt_target_exit_data; +constexpr ompt_target_t UPDATE = ompt_target_update; +constexpr ompt_target_t TARGET_NOWAIT = ompt_target_nowait; +constexpr ompt_target_t ENTER_DATA_NOWAIT = ompt_target_enter_data_nowait; +constexpr ompt_target_t EXIT_DATA_NOWAIT = ompt_target_exit_data_nowait; +constexpr ompt_target_t UPDATE_NOWAIT = ompt_target_update_nowait; + +/// Aliases for enum: ompt_target_data_op_t +constexpr ompt_target_data_op_t ALLOC = ompt_target_data_alloc; +constexpr ompt_target_data_op_t H2D = ompt_target_data_transfer_to_device; +constexpr ompt_target_data_op_t D2H = ompt_target_data_transfer_from_device; +constexpr ompt_target_data_op_t DELETE = ompt_target_data_delete; +constexpr ompt_target_data_op_t ASSOCIATE = ompt_target_data_associate; +constexpr ompt_target_data_op_t DISASSOCIATE = ompt_target_data_disassociate; +constexpr ompt_target_data_op_t ALLOC_ASYNC = ompt_target_data_alloc_async; +constexpr ompt_target_data_op_t H2D_ASYNC = + ompt_target_data_transfer_to_device_async; +constexpr ompt_target_data_op_t D2H_ASYNC = + ompt_target_data_transfer_from_device_async; +constexpr ompt_target_data_op_t DELETE_ASYNC = ompt_target_data_delete_async; + +/// Aliases for enum: ompt_callbacks_t (partial) +constexpr ompt_callbacks_t CB_TARGET = ompt_callback_target; +constexpr ompt_callbacks_t CB_DATAOP = ompt_callback_target_data_op; +constexpr ompt_callbacks_t CB_KERNEL = ompt_callback_target_submit; + +/// Aliases for enum: ompt_work_t +constexpr ompt_work_t WORK_LOOP = ompt_work_loop; +constexpr ompt_work_t WORK_SECT = ompt_work_sections; +constexpr ompt_work_t WORK_EXEC = ompt_work_single_executor; +constexpr ompt_work_t WORK_SINGLE = ompt_work_single_other; +constexpr ompt_work_t WORK_SHARE = ompt_work_workshare; +constexpr ompt_work_t WORK_DIST = ompt_work_distribute; +constexpr ompt_work_t WORK_TASK = ompt_work_taskloop; +constexpr ompt_work_t WORK_SCOPE = ompt_work_scope; +constexpr ompt_work_t WORK_LOOP_STA = ompt_work_loop_static; +constexpr ompt_work_t WORK_LOOP_DYN = ompt_work_loop_dynamic; +constexpr ompt_work_t WORK_LOOP_GUI = ompt_work_loop_guided; +constexpr ompt_work_t WORK_LOOP_OTH = ompt_work_loop_other; + +/// Aliases for enum: ompt_sync_region_t +constexpr ompt_sync_region_t SR_BARRIER = ompt_sync_region_barrier; +constexpr ompt_sync_region_t SR_BARRIER_IMPL = + ompt_sync_region_barrier_implicit; +constexpr ompt_sync_region_t SR_BARRIER_EXPL = + ompt_sync_region_barrier_explicit; +constexpr ompt_sync_region_t SR_BARRIER_IMPLEMENTATION = + ompt_sync_region_barrier_implementation; +constexpr ompt_sync_region_t SR_TASKWAIT = ompt_sync_region_taskwait; +constexpr ompt_sync_region_t SR_TASKGROUP = ompt_sync_region_taskgroup; +constexpr ompt_sync_region_t SR_REDUCTION = ompt_sync_region_reduction; +constexpr ompt_sync_region_t SR_BARRIER_IMPL_WORKSHARE = + ompt_sync_region_barrier_implicit_workshare; +constexpr ompt_sync_region_t SR_BARRIER_IMPL_PARALLEL = + ompt_sync_region_barrier_implicit_parallel; +constexpr ompt_sync_region_t SR_BARRIER_TEAMS = ompt_sync_region_barrier_teams; + +#endif diff --git a/openmp/tools/omptest/include/OmptAssertEvent.h b/openmp/tools/omptest/include/OmptAssertEvent.h new file mode 100644 index 0000000000000..87d187c823796 --- /dev/null +++ b/openmp/tools/omptest/include/OmptAssertEvent.h @@ -0,0 +1,377 @@ +//===- OmptAssertEvent.h - Assertion event declarations ---------*- 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 assertion event constructors, for generally all observable events. +/// This includes user-generated events, like synchronization. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTASSERTEVENT_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTASSERTEVENT_H + +#include "InternalEvent.h" +#include "omp-tools.h" + +#include +#include +#include +#include + +namespace omptest { + +enum class ObserveState { generated, always, never }; + +/// Helper function, returning an ObserveState string representation +const char *to_string(ObserveState State); + +/// Assertion event struct, provides statically callable CTORs. +struct OmptAssertEvent { + static OmptAssertEvent AssertionSyncPoint(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + const std::string &SyncPointName); + + static OmptAssertEvent AssertionSuspend(const std::string &Name, + const std::string &Group, + const ObserveState &Expected); + + static OmptAssertEvent ThreadBegin(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + ompt_thread_t ThreadType); + + static OmptAssertEvent ThreadEnd(const std::string &Name, + const std::string &Group, + const ObserveState &Expected); + + static OmptAssertEvent ParallelBegin(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + int NumThreads); + + static OmptAssertEvent ParallelEnd( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, + ompt_data_t *ParallelData = expectedDefault(ompt_data_t *), + ompt_data_t *EncounteringTaskData = expectedDefault(ompt_data_t *), + int Flags = expectedDefault(int), + const void *CodeptrRA = expectedDefault(const void *)); + + static OmptAssertEvent + Work(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_work_t WorkType, + ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData = expectedDefault(ompt_data_t *), + ompt_data_t *TaskData = expectedDefault(ompt_data_t *), + uint64_t Count = expectedDefault(uint64_t), + const void *CodeptrRA = expectedDefault(const void *)); + + static OmptAssertEvent + Dispatch(const std::string &Name, const std::string &Group, + const ObserveState &Expected, + ompt_data_t *ParallelData = expectedDefault(ompt_data_t *), + ompt_data_t *TaskData = expectedDefault(ompt_data_t *), + ompt_dispatch_t Kind = expectedDefault(ompt_dispatch_t), + ompt_data_t Instance = expectedDefault(ompt_data_t)); + + static OmptAssertEvent + TaskCreate(const std::string &Name, const std::string &Group, + const ObserveState &Expected, + ompt_data_t *EncounteringTaskData = expectedDefault(ompt_data_t *), + const ompt_frame_t *EncounteringTaskFrame = + expectedDefault(ompt_frame_t *), + ompt_data_t *NewTaskData = expectedDefault(ompt_data_t *), + int Flags = expectedDefault(int), + int HasDependences = expectedDefault(int), + const void *CodeptrRA = expectedDefault(const void *)); + + static OmptAssertEvent TaskSchedule(const std::string &Name, + const std::string &Group, + const ObserveState &Expected); + + static OmptAssertEvent + ImplicitTask(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData = expectedDefault(ompt_data_t *), + ompt_data_t *TaskData = expectedDefault(ompt_data_t *), + unsigned int ActualParallelism = expectedDefault(unsigned int), + unsigned int Index = expectedDefault(unsigned int), + int Flags = expectedDefault(int)); + + static OmptAssertEvent + SyncRegion(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_sync_region_t Kind, + ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData = expectedDefault(ompt_data_t *), + ompt_data_t *TaskData = expectedDefault(ompt_data_t *), + const void *CodeptrRA = expectedDefault(const void *)); + + static OmptAssertEvent + Target(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_target_t Kind, + ompt_scope_endpoint_t Endpoint, int DeviceNum = expectedDefault(int), + ompt_data_t *TaskData = expectedDefault(ompt_data_t *), + ompt_id_t TargetId = expectedDefault(ompt_id_t), + const void *CodeptrRA = expectedDefault(void *)); + + static OmptAssertEvent + TargetEmi(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_target_t Kind, + ompt_scope_endpoint_t Endpoint, + int DeviceNum = expectedDefault(int), + ompt_data_t *TaskData = expectedDefault(ompt_data_t *), + ompt_data_t *TargetTaskData = expectedDefault(ompt_data_t *), + ompt_data_t *TargetData = expectedDefault(ompt_data_t *), + const void *CodeptrRA = expectedDefault(void *)); + + static OmptAssertEvent + TargetDataOp(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_id_t TargetId, + ompt_id_t HostOpId, ompt_target_data_op_t OpType, void *SrcAddr, + int SrcDeviceNum, void *DstAddr, int DstDeviceNum, size_t Bytes, + const void *CodeptrRA); + + static OmptAssertEvent + TargetDataOp(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_target_data_op_t OpType, + size_t Bytes = expectedDefault(size_t), + void *SrcAddr = expectedDefault(void *), + void *DstAddr = expectedDefault(void *), + int SrcDeviceNum = expectedDefault(int), + int DstDeviceNum = expectedDefault(int), + ompt_id_t TargetId = expectedDefault(ompt_id_t), + ompt_id_t HostOpId = expectedDefault(ompt_id_t), + const void *CodeptrRA = expectedDefault(void *)); + + static OmptAssertEvent + TargetDataOpEmi(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_scope_endpoint_t Endpoint, + ompt_data_t *TargetTaskData, ompt_data_t *TargetData, + ompt_id_t *HostOpId, ompt_target_data_op_t OpType, + void *SrcAddr, int SrcDeviceNum, void *DstAddr, + int DstDeviceNum, size_t Bytes, const void *CodeptrRA); + + static OmptAssertEvent + TargetDataOpEmi(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_target_data_op_t OpType, + ompt_scope_endpoint_t Endpoint, + size_t Bytes = expectedDefault(size_t), + void *SrcAddr = expectedDefault(void *), + void *DstAddr = expectedDefault(void *), + int SrcDeviceNum = expectedDefault(int), + int DstDeviceNum = expectedDefault(int), + ompt_data_t *TargetTaskData = expectedDefault(ompt_data_t *), + ompt_data_t *TargetData = expectedDefault(ompt_data_t *), + ompt_id_t *HostOpId = expectedDefault(ompt_id_t *), + const void *CodeptrRA = expectedDefault(void *)); + + static OmptAssertEvent TargetSubmit(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + ompt_id_t TargetId, ompt_id_t HostOpId, + unsigned int RequestedNumTeams); + + static OmptAssertEvent + TargetSubmit(const std::string &Name, const std::string &Group, + const ObserveState &Expected, unsigned int RequestedNumTeams, + ompt_id_t TargetId = expectedDefault(ompt_id_t), + ompt_id_t HostOpId = expectedDefault(ompt_id_t)); + + static OmptAssertEvent + TargetSubmitEmi(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_scope_endpoint_t Endpoint, + ompt_data_t *TargetData, ompt_id_t *HostOpId, + unsigned int RequestedNumTeams); + + static OmptAssertEvent + TargetSubmitEmi(const std::string &Name, const std::string &Group, + const ObserveState &Expected, unsigned int RequestedNumTeams, + ompt_scope_endpoint_t Endpoint, + ompt_data_t *TargetData = expectedDefault(ompt_data_t *), + ompt_id_t *HostOpId = expectedDefault(ompt_id_t *)); + + static OmptAssertEvent ControlTool(const std::string &Name, + const std::string &Group, + const ObserveState &Expected); + + static OmptAssertEvent DeviceInitialize( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, int DeviceNum, + const char *Type = expectedDefault(const char *), + ompt_device_t *Device = expectedDefault(ompt_device_t *), + ompt_function_lookup_t LookupFn = expectedDefault(ompt_function_lookup_t), + const char *DocumentationStr = expectedDefault(const char *)); + + static OmptAssertEvent DeviceFinalize(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + int DeviceNum); + + static OmptAssertEvent + DeviceLoad(const std::string &Name, const std::string &Group, + const ObserveState &Expected, int DeviceNum, + const char *Filename = expectedDefault(const char *), + int64_t OffsetInFile = expectedDefault(int64_t), + void *VmaInFile = expectedDefault(void *), + size_t Bytes = expectedDefault(size_t), + void *HostAddr = expectedDefault(void *), + void *DeviceAddr = expectedDefault(void *), + uint64_t ModuleId = expectedDefault(int64_t)); + + static OmptAssertEvent DeviceUnload(const std::string &Name, + const std::string &Group, + const ObserveState &Expected); + + static OmptAssertEvent BufferRequest(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + int DeviceNum, ompt_buffer_t **Buffer, + size_t *Bytes); + + static OmptAssertEvent + BufferComplete(const std::string &Name, const std::string &Group, + const ObserveState &Expected, int DeviceNum, + ompt_buffer_t *Buffer, size_t Bytes, + ompt_buffer_cursor_t Begin, int BufferOwned); + + static OmptAssertEvent BufferRecord(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + ompt_record_ompt_t *Record); + + /// Handle type = ompt_record_target_t + static OmptAssertEvent + BufferRecord(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, + ompt_target_t Kind, ompt_scope_endpoint_t Endpoint, + int DeviceNum = expectedDefault(int), + ompt_id_t TaskId = expectedDefault(ompt_id_t), + ompt_id_t TargetId = expectedDefault(ompt_id_t), + const void *CodeptrRA = expectedDefault(void *)); + + /// Handle type = ompt_callback_target_data_op + static OmptAssertEvent + BufferRecord(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, + ompt_target_data_op_t OpType, size_t Bytes, + std::pair Timeframe, + void *SrcAddr = expectedDefault(void *), + void *DstAddr = expectedDefault(void *), + int SrcDeviceNum = expectedDefault(int), + int DstDeviceNum = expectedDefault(int), + ompt_id_t TargetId = expectedDefault(ompt_id_t), + ompt_id_t HostOpId = expectedDefault(ompt_id_t), + const void *CodeptrRA = expectedDefault(void *)); + + /// Handle type = ompt_callback_target_data_op + static OmptAssertEvent BufferRecord( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, + ompt_target_data_op_t OpType, size_t Bytes = expectedDefault(size_t), + ompt_device_time_t MinimumTimeDelta = expectedDefault(ompt_device_time_t), + void *SrcAddr = expectedDefault(void *), + void *DstAddr = expectedDefault(void *), + int SrcDeviceNum = expectedDefault(int), + int DstDeviceNum = expectedDefault(int), + ompt_id_t TargetId = expectedDefault(ompt_id_t), + ompt_id_t HostOpId = expectedDefault(ompt_id_t), + const void *CodeptrRA = expectedDefault(void *)); + + /// Handle type = ompt_callback_target_submit + static OmptAssertEvent + BufferRecord(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, + std::pair Timeframe, + unsigned int RequestedNumTeams = expectedDefault(unsigned int), + unsigned int GrantedNumTeams = expectedDefault(unsigned int), + ompt_id_t TargetId = expectedDefault(ompt_id_t), + ompt_id_t HostOpId = expectedDefault(ompt_id_t)); + + /// Handle type = ompt_callback_target_submit + /// Note: This will also act as the simplest default CTOR + static OmptAssertEvent BufferRecord( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, + ompt_device_time_t MinimumTimeDelta = expectedDefault(ompt_device_time_t), + unsigned int RequestedNumTeams = expectedDefault(unsigned int), + unsigned int GrantedNumTeams = expectedDefault(unsigned int), + ompt_id_t TargetId = expectedDefault(ompt_id_t), + ompt_id_t HostOpId = expectedDefault(ompt_id_t)); + + static OmptAssertEvent BufferRecordDeallocation(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + ompt_buffer_t *Buffer); + + /// Allow move construction (due to std::unique_ptr) + OmptAssertEvent(OmptAssertEvent &&o) = default; + OmptAssertEvent &operator=(OmptAssertEvent &&o) = default; + + /// Get the event's name + std::string getEventName() const; + + /// Get the event's group name + std::string getEventGroup() const; + + /// Get the event's expected observation state + ObserveState getEventExpectedState() const; + + /// Return the actual event type enum value + internal::EventTy getEventType() const; + + /// Get a pointer to the internal event + internal::InternalEvent *getEvent() const; + + /// Make events comparable + friend bool operator==(const OmptAssertEvent &A, const OmptAssertEvent &B); + + /// Returns the string representation of the event + std::string toString(bool PrefixEventName = false) const; + +private: + OmptAssertEvent(const std::string &Name, const std::string &Group, + const ObserveState &Expected, internal::InternalEvent *IE); + OmptAssertEvent(const OmptAssertEvent &o) = delete; + + /// Determine the event name. Either it is provided directly or determined + /// from the calling function's name. + static std::string getName(const std::string &Name, + const char *Caller = __builtin_FUNCTION()) { + std::string EName = Name; + if (EName.empty()) + EName.append(Caller).append(" (auto generated)"); + + return EName; + } + + /// Determine the event name. Either it is provided directly or "default". + static std::string getGroup(const std::string &Group) { + if (Group.empty()) + return "default"; + + return Group; + } + + std::string Name; + std::string Group; + ObserveState ExpectedState; + std::unique_ptr TheEvent; +}; + +/// POD type, which holds the target region id, corresponding to an event group. +struct AssertEventGroup { + AssertEventGroup(uint64_t TargetRegion) : TargetRegion(TargetRegion) {} + uint64_t TargetRegion; +}; + +bool operator==(const OmptAssertEvent &A, const OmptAssertEvent &B); + +} // namespace omptest + +#endif diff --git a/openmp/tools/omptest/include/OmptAsserter.h b/openmp/tools/omptest/include/OmptAsserter.h new file mode 100644 index 0000000000000..64cbb5f3642f9 --- /dev/null +++ b/openmp/tools/omptest/include/OmptAsserter.h @@ -0,0 +1,291 @@ +//===- 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 +#include +#include +#include +#include +#include + +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 +/// OmptCallbachHandler. 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 in 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 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 getEventGroups() const { + return EventGroups; + } + + /// Accessor for the event group interface. + std::shared_ptr 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); + + /// 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}; + +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 EventGroups{nullptr}; + + /// Pointer to the logging instance. + std::shared_ptr Log{nullptr}; + + /// Operation mode during assertion / notification. + AssertMode OperationMode{AssertMode::strict}; + +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 EventGroupInterfaceInstance; + + /// Static member to manage the singleton logging instance + static std::weak_ptr 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 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 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 ActiveEventGroups{}; + std::map DeprecatedEventGroups{}; + std::set EncounteredSyncPoints{}; +}; + +} // namespace omptest + +#endif diff --git a/openmp/tools/omptest/include/OmptCallbackHandler.h b/openmp/tools/omptest/include/OmptCallbackHandler.h new file mode 100644 index 0000000000000..40076c386107e --- /dev/null +++ b/openmp/tools/omptest/include/OmptCallbackHandler.h @@ -0,0 +1,165 @@ +//===- OmptCallbackHandler.h - Callback reception and handling --*- 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 +/// This file provides the OMPT callback handling declarations. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTCALLBACKHANDLER_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTCALLBACKHANDLER_H + +#include "OmptAssertEvent.h" +#include "OmptAsserter.h" + +#include "omp-tools.h" + +#include + +namespace omptest { + +/// Handler class to do whatever is needed to be done when a callback is invoked +/// by the OMP runtime +/// Supports a RecordAndReplay mechanism in which all OMPT events are recorded +/// and then replayed. This is so that a test can assert on, e.g., a device +/// initialize event, even though this would occur before a unit test is +/// actually executed. +class OmptCallbackHandler { +public: + ~OmptCallbackHandler() = default; + + /// Singleton handler + static OmptCallbackHandler &get(); + + /// Subscribe a listener to be notified for OMPT events + void subscribe(OmptListener *Listener); + + /// Remove all subscribers + void clearSubscribers(); + + /// When the record and replay mechanism is enabled this replays all OMPT + /// events + void replay(); + + /// Special asserter callback which checks that upon encountering the + /// synchronization point, all expected events have been processed. That is: + /// there are currently no remaining expected events for any asserter. + void handleAssertionSyncPoint(const std::string &SyncPointName); + + void handleThreadBegin(ompt_thread_t ThreadType, ompt_data_t *ThreadData); + + void handleThreadEnd(ompt_data_t *ThreadData); + + void handleTaskCreate(ompt_data_t *EncounteringTaskData, + const ompt_frame_t *EncounteringTaskFrame, + ompt_data_t *NewTaskData, int Flags, int HasDependences, + const void *CodeptrRA); + + void handleTaskSchedule(ompt_data_t *PriorTaskData, + ompt_task_status_t PriorTaskStatus, + ompt_data_t *NextTaskData); + + void handleImplicitTask(ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData, ompt_data_t *TaskData, + unsigned int ActualParallelism, unsigned int Index, + int Flags); + + void handleParallelBegin(ompt_data_t *EncounteringTaskData, + const ompt_frame_t *EncounteringTaskFrame, + ompt_data_t *ParallelData, + unsigned int RequestedParallelism, int Flags, + const void *CodeptrRA); + + void handleParallelEnd(ompt_data_t *ParallelData, + ompt_data_t *EncounteringTaskData, int Flags, + const void *CodeptrRA); + + void handleDeviceInitialize(int DeviceNum, const char *Type, + ompt_device_t *Device, + ompt_function_lookup_t LookupFn, + const char *DocumentationStr); + + void handleDeviceFinalize(int DeviceNum); + + void handleTarget(ompt_target_t Kind, ompt_scope_endpoint_t Endpoint, + int DeviceNum, ompt_data_t *TaskData, ompt_id_t TargetId, + const void *CodeptrRA); + + void handleTargetEmi(ompt_target_t Kind, ompt_scope_endpoint_t Endpoint, + int DeviceNum, ompt_data_t *TaskData, + ompt_data_t *TargetTaskData, ompt_data_t *TargetData, + const void *CodeptrRA); + + void handleTargetSubmit(ompt_id_t TargetId, ompt_id_t HostOpId, + unsigned int RequestedNumTeams); + + void handleTargetSubmitEmi(ompt_scope_endpoint_t Endpoint, + ompt_data_t *TargetData, ompt_id_t *HostOpId, + unsigned int RequestedNumTeams); + + void handleTargetDataOp(ompt_id_t TargetId, ompt_id_t HostOpId, + ompt_target_data_op_t OpType, void *SrcAddr, + int SrcDeviceNum, void *DstAddr, int DstDeviceNum, + size_t Bytes, const void *CodeptrRA); + + void handleTargetDataOpEmi(ompt_scope_endpoint_t Endpoint, + ompt_data_t *TargetTaskData, + ompt_data_t *TargetData, ompt_id_t *HostOpId, + ompt_target_data_op_t OpType, void *SrcAddr, + int SrcDeviceNum, void *DstAddr, int DstDeviceNum, + size_t Bytes, const void *CodeptrRA); + + void handleDeviceLoad(int DeviceNum, const char *Filename, + int64_t OffsetInFile, void *VmaInFile, size_t Bytes, + void *HostAddr, void *DeviceAddr, uint64_t ModuleId); + + void handleDeviceUnload(int DeviceNum, uint64_t ModuleId); + + void handleBufferRequest(int DeviceNum, ompt_buffer_t **Buffer, + size_t *Bytes); + + void handleBufferComplete(int DeviceNum, ompt_buffer_t *Buffer, size_t Bytes, + ompt_buffer_cursor_t Begin, int BufferOwned); + + void handleBufferRecord(ompt_record_ompt_t *Record); + + void handleBufferRecordDeallocation(ompt_buffer_t *Buffer); + + /// Not needed for a conforming minimal OMPT implementation + void handleWork(ompt_work_t WorkType, ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData, ompt_data_t *TaskData, + uint64_t Count, const void *CodeptrRA); + + void handleDispatch(ompt_data_t *ParallelData, ompt_data_t *TaskData, + ompt_dispatch_t Kind, ompt_data_t Instance); + + void handleSyncRegion(ompt_sync_region_t Kind, ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData, ompt_data_t *TaskData, + const void *CodeptrRA); + +private: + /// Wrapper around emplace_back for potential additional logging / checking or + /// so + void recordEvent(OmptAssertEvent &&Event); + + /// Listeners to be notified + std::vector Subscribers; + + /// Toggle if OMPT events should notify subscribers immediately or not + bool RecordAndReplay{false}; + + /// Recorded events in Record and Replay mode + std::vector RecordedEvents; +}; + +} // namespace omptest + +// Pointer to global callback handler +extern omptest::OmptCallbackHandler *Handler; + +#endif diff --git a/openmp/tools/omptest/include/OmptTester.h b/openmp/tools/omptest/include/OmptTester.h new file mode 100644 index 0000000000000..155e61d5f7482 --- /dev/null +++ b/openmp/tools/omptest/include/OmptTester.h @@ -0,0 +1,60 @@ +//===- OmptTester.h - Main header for ompTest usage -------------*- 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 +/// This file represents the main header file for usage of the ompTest library. +/// Depending on the build either 'standalone' or GoogleTest headers are +/// included and corresponding main-function macros are defined. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTTESTER_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTTESTER_H + +#include "AssertMacros.h" +#include "Logging.h" +#include "OmptAliases.h" +#include "OmptAssertEvent.h" +#include "OmptAsserter.h" +#include "OmptCallbackHandler.h" + +#include +#include +#include +#include +#include +#include +#include + +// Standalone header section +#ifdef OPENMP_LIBOMPTEST_BUILD_STANDALONE + +#include "OmptTesterStandalone.h" + +// Define standalone main function (place once at the bottom of a testsuite) +#define OMPTEST_TESTSUITE_MAIN() \ + int main(int argc, char **argv) { \ + Runner R; \ + return R.run(); \ + } + +// GoogleTest header section +#else + +#include "OmptTesterGoogleTest.h" + +// Define GoogleTest main function (place once at the bottom of a testsuite) +#define OMPTEST_TESTSUITE_MAIN() \ + int main(int argc, char **argv) { \ + testing::InitGoogleTest(&argc, argv); \ + return RUN_ALL_TESTS(); \ + } + +#endif + +#endif diff --git a/openmp/tools/omptest/include/OmptTesterGlobals.h b/openmp/tools/omptest/include/OmptTesterGlobals.h new file mode 100644 index 0000000000000..62f443aed80e0 --- /dev/null +++ b/openmp/tools/omptest/include/OmptTesterGlobals.h @@ -0,0 +1,36 @@ +//===- OmptTesterGlobals.h - Global function declarations -------*- 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 global function declarations, esp. for OMPT symbols. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTTESTERGLOBALS_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTTESTERGLOBALS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif +ompt_start_tool_result_t *ompt_start_tool(unsigned int omp_version, + const char *runtime_version); +int start_trace(ompt_device_t *Device); +int flush_trace(ompt_device_t *Device); +// Function which calls flush_trace(ompt_device_t *) on all traced devices. +int flush_traced_devices(); +int stop_trace(ompt_device_t *Device); +// Function which calls stop_trace(ompt_device_t *) on all traced devices. +int stop_trace_devices(); +void libomptest_global_eventreporter_set_active(bool State); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/openmp/tools/omptest/include/OmptTesterGoogleTest.h b/openmp/tools/omptest/include/OmptTesterGoogleTest.h new file mode 100644 index 0000000000000..51b94bc678f50 --- /dev/null +++ b/openmp/tools/omptest/include/OmptTesterGoogleTest.h @@ -0,0 +1,86 @@ +//===- OmptTesterGoogleTest.h - GoogleTest header variant -------*- 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 +/// This file represents the GoogleTest-based header variant, defining the +/// actual test classes and their behavior. +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTTESTERGOOGLETEST_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTTESTERGOOGLETEST_H + +#include "AssertMacros.h" +#include "OmptAliases.h" +#include "OmptAssertEvent.h" +#include "OmptAsserter.h" +#include "OmptCallbackHandler.h" +#include "OmptTesterGlobals.h" + +// This will allow us to override the "TEST" macro of gtest +#define GTEST_DONT_DEFINE_TEST 1 +#include "gtest/gtest.h" + +namespace testing { +class GTEST_API_ OmptTestCase : public testing::Test, + public omptest::OmptEventGroupInterface { +public: + std::unique_ptr SequenceAsserter = + std::make_unique(); + std::unique_ptr SetAsserter = + std::make_unique(); + std::unique_ptr EventReporter = + std::make_unique(); + +protected: + void SetUp() override { + omptest::OmptCallbackHandler::get().subscribe(SequenceAsserter.get()); + omptest::OmptCallbackHandler::get().subscribe(SetAsserter.get()); + omptest::OmptCallbackHandler::get().subscribe(EventReporter.get()); + } + + void TearDown() override { + // Actively flush potential in-flight trace records + flush_traced_devices(); + + // Remove subscribers to not be notified of events after test execution. + omptest::OmptCallbackHandler::get().clearSubscribers(); + + // This common testcase must not encounter any failures. + if (SequenceAsserter->checkState() == omptest::AssertState::fail || + SetAsserter->checkState() == omptest::AssertState::fail) + ADD_FAILURE(); + } +}; + +class GTEST_API_ OmptTestCaseXFail : public testing::OmptTestCase { +protected: + void TearDown() override { + // Actively flush potential in-flight trace records + flush_traced_devices(); + + // Remove subscribers to not be notified of events after test execution. + omptest::OmptCallbackHandler::get().clearSubscribers(); + + // This eXpectedly failing testcase has to encounter at least one failure. + if (SequenceAsserter->checkState() == omptest::AssertState::pass && + SetAsserter->checkState() == omptest::AssertState::pass) + ADD_FAILURE(); + } +}; +} // namespace testing + +#define TEST(test_suite_name, test_name) \ + GTEST_TEST_(test_suite_name, test_name, ::testing::OmptTestCase, \ + ::testing::internal::GetTypeId<::testing::OmptTestCase>()) + +#define TEST_XFAIL(test_suite_name, test_name) \ + GTEST_TEST_(test_suite_name, test_name, ::testing::OmptTestCaseXFail, \ + ::testing::internal::GetTypeId<::testing::OmptTestCaseXFail>()) + +#endif // include guard diff --git a/openmp/tools/omptest/include/OmptTesterStandalone.h b/openmp/tools/omptest/include/OmptTesterStandalone.h new file mode 100644 index 0000000000000..06649031c5d1c --- /dev/null +++ b/openmp/tools/omptest/include/OmptTesterStandalone.h @@ -0,0 +1,123 @@ +//===- OmptTesterStandalone.h - Standalone header variant -------*- 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 +/// This file represents the 'standalone' header variant, defining the actual +/// test classes and their behavior (it does not have external dependencies). +/// +//===----------------------------------------------------------------------===// + +#ifndef OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTTESTERSTANDALONE_H +#define OPENMP_TOOLS_OMPTEST_INCLUDE_OMPTTESTERSTANDALONE_H + +#include "OmptAssertEvent.h" +#include "OmptAsserter.h" +#include "OmptTesterGlobals.h" + +#include +#include + +// Forward declarations. +namespace omptest { +struct OmptEventAsserter; +class OmptEventReporter; +class OmptSequencedAsserter; +} // namespace omptest + +struct Error { + operator bool() { return Fail; } + bool Fail; +}; + +/// A pretty crude test case abstraction +struct TestCase { + TestCase(const std::string &name) + : IsDisabled(name.rfind("DISABLED_", 0) == 0), Name(name) {} + TestCase(const std::string &name, const omptest::AssertState &expected) + : IsDisabled(name.rfind("DISABLED_", 0) == 0), Name(name), + ExpectedState(expected) {} + virtual ~TestCase() = default; + Error exec(); + virtual void execImpl() { assert(false && "Allocating base class"); } + + bool IsDisabled{false}; + std::string Name; + omptest::AssertState ExpectedState{omptest::AssertState::pass}; + omptest::AssertState ResultState{omptest::AssertState::pass}; + + std::unique_ptr SequenceAsserter = + std::make_unique(); + std::unique_ptr SetAsserter = + std::make_unique(); + std::unique_ptr EventReporter = + std::make_unique(); +}; +/// A pretty crude test suite abstraction +struct TestSuite { + using TestCaseVec = std::vector>; + std::string Name; + TestSuite() = default; + TestSuite(const TestSuite &O) = delete; + TestSuite(TestSuite &&O); + void setup(); + void teardown(); + TestCaseVec::iterator begin(); + TestCaseVec::iterator end(); + TestCaseVec TestCases; +}; +/// Static class used to register all test cases and provide them to the driver +class TestRegistrar { +public: + static TestRegistrar &get(); + static std::vector getTestSuites(); + static void addCaseToSuite(TestCase *TC, std::string TSName); + +private: + TestRegistrar() = default; + TestRegistrar(const TestRegistrar &o) = delete; + TestRegistrar operator=(const TestRegistrar &o) = delete; + // Keep tests in order 'of appearance' (top -> bottom), avoid unordered_map + static std::map Tests; +}; +/// Hack to register test cases +struct Registerer { + Registerer(TestCase *TC, const std::string SuiteName); +}; +/// Eventually executes all test suites and cases, should contain logic to skip +/// stuff if needed +struct Runner { + Runner() : TestSuites(TestRegistrar::get().getTestSuites()) {} + int run(); + void reportError(const Error &Err); + void abortOrKeepGoing(); + // Print an execution summary of all testsuites and their corresponding + // testcases. + void printSummary(); + std::vector TestSuites; +}; + +/// MACROS TO DEFINE A TESTSUITE + TESTCASE (like GoogleTest does) +#define XQUOTE(str) QUOTE(str) +#define QUOTE(str) #str + +#define TEST_TEMPLATE(SuiteName, CaseName, ExpectedState) \ + struct SuiteName##_##CaseName : public TestCase { \ + SuiteName##_##CaseName() \ + : TestCase(XQUOTE(CaseName), omptest::AssertState::ExpectedState) {} \ + virtual void execImpl() override; \ + }; \ + static Registerer R_##SuiteName##CaseName(new SuiteName##_##CaseName(), \ + #SuiteName); \ + void SuiteName##_##CaseName::execImpl() + +#define TEST(SuiteName, CaseName) \ + TEST_TEMPLATE(SuiteName, CaseName, /*ExpectedState=*/pass) +#define TEST_XFAIL(SuiteName, CaseName) \ + TEST_TEMPLATE(SuiteName, CaseName, /*ExpectedState=*/fail) + +#endif diff --git a/openmp/tools/omptest/src/InternalEvent.cpp b/openmp/tools/omptest/src/InternalEvent.cpp new file mode 100644 index 0000000000000..87daf5a6a31ba --- /dev/null +++ b/openmp/tools/omptest/src/InternalEvent.cpp @@ -0,0 +1,367 @@ +//===- InternalEvent.cpp - Internal event implementation --------*- 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 internal event representation methods and helper functions. +/// +//===----------------------------------------------------------------------===// + +#include "InternalEvent.h" + +#include +#include + +using namespace omptest; +using namespace util; + +std::string util::makeHexString(uint64_t Data, bool IsPointer, size_t MinBytes, + bool ShowHexBase) { + if (Data == 0 && IsPointer) + return "(nil)"; + + thread_local std::ostringstream os; + // Clear the content of the stream + os.str(std::string()); + + // Manually prefixing "0x" will make the use of std::setfill more easy + if (ShowHexBase) + os << "0x"; + + // Default to 32bit (8 hex digits) width, if exceeding 64bit or zero value + size_t NumDigits = (MinBytes > 0 && MinBytes < 9) ? (MinBytes << 1) : 8; + + if (MinBytes > 0) + os << std::setfill('0') << std::setw(NumDigits); + + os << std::hex << Data; + return os.str(); +} + +std::string internal::AssertionSyncPoint::toString() const { + std::string S{"Assertion SyncPoint: '"}; + S.append(Name).append(1, '\''); + return S; +} + +std::string internal::ThreadBegin::toString() const { + std::string S{"OMPT Callback ThreadBegin: "}; + S.append("ThreadType=").append(std::to_string(ThreadType)); + return S; +} + +std::string internal::ThreadEnd::toString() const { + std::string S{"OMPT Callback ThreadEnd"}; + return S; +} + +std::string internal::ParallelBegin::toString() const { + std::string S{"OMPT Callback ParallelBegin: "}; + S.append("NumThreads=").append(std::to_string(NumThreads)); + return S; +} + +std::string internal::ParallelEnd::toString() const { + // TODO: Should we expose more detailed info here? + std::string S{"OMPT Callback ParallelEnd"}; + return S; +} + +std::string internal::Work::toString() const { + std::string S{"OMPT Callback Work: "}; + S.append("work_type=").append(std::to_string(WorkType)); + S.append(" endpoint=").append(std::to_string(Endpoint)); + S.append(" parallel_data=").append(makeHexString((uint64_t)ParallelData)); + S.append(" task_data=").append(makeHexString((uint64_t)TaskData)); + S.append(" count=").append(std::to_string(Count)); + S.append(" codeptr=").append(makeHexString((uint64_t)CodeptrRA)); + return S; +} + +std::string internal::Dispatch::toString() const { + std::string S{"OMPT Callback Dispatch: "}; + S.append("parallel_data=").append(makeHexString((uint64_t)ParallelData)); + S.append(" task_data=").append(makeHexString((uint64_t)TaskData)); + S.append(" kind=").append(std::to_string(Kind)); + // TODO Check what to print for instance in all different cases + if (Kind == ompt_dispatch_iteration) { + S.append(" instance=[it=") + .append(std::to_string(Instance.value)) + .append(1, ']'); + } else if (Kind == ompt_dispatch_section) { + S.append(" instance=[ptr=") + .append(makeHexString((uint64_t)Instance.ptr)) + .append(1, ']'); + } else if ((Kind == ompt_dispatch_ws_loop_chunk || + Kind == ompt_dispatch_taskloop_chunk || + Kind == ompt_dispatch_distribute_chunk) && + Instance.ptr != nullptr) { + auto Chunk = static_cast(Instance.ptr); + S.append(" instance=[chunk=(start=") + .append(std::to_string(Chunk->start)) + .append(", iterations=") + .append(std::to_string(Chunk->iterations)) + .append(")]"); + } + return S; +} + +std::string internal::TaskCreate::toString() const { + std::string S{"OMPT Callback TaskCreate: "}; + S.append("encountering_task_data=") + .append(makeHexString((uint64_t)EncounteringTaskData)); + S.append(" encountering_task_frame=") + .append(makeHexString((uint64_t)EncounteringTaskFrame)); + S.append(" new_task_data=").append(makeHexString((uint64_t)NewTaskData)); + S.append(" flags=").append(std::to_string(Flags)); + S.append(" has_dependences=").append(std::to_string(HasDependences)); + S.append(" codeptr=").append(makeHexString((uint64_t)CodeptrRA)); + return S; +} + +std::string internal::ImplicitTask::toString() const { + std::string S{"OMPT Callback ImplicitTask: "}; + S.append("endpoint=").append(std::to_string(Endpoint)); + S.append(" parallel_data=").append(makeHexString((uint64_t)ParallelData)); + S.append(" task_data=").append(makeHexString((uint64_t)TaskData)); + S.append(" actual_parallelism=").append(std::to_string(ActualParallelism)); + S.append(" index=").append(std::to_string(Index)); + S.append(" flags=").append(std::to_string(Flags)); + return S; +} + +std::string internal::SyncRegion::toString() const { + std::string S{"OMPT Callback SyncRegion: "}; + S.append("kind=").append(std::to_string(Kind)); + S.append(" endpoint=").append(std::to_string(Endpoint)); + S.append(" parallel_data=").append(makeHexString((uint64_t)ParallelData)); + S.append(" task_data=").append(makeHexString((uint64_t)TaskData)); + S.append(" codeptr=").append(makeHexString((uint64_t)CodeptrRA)); + return S; +} + +std::string internal::Target::toString() const { + // TODO Should we canonicalize the string prefix (use "OMPT ..." everywhere)? + std::string S{"Callback Target: target_id="}; + S.append(std::to_string(TargetId)); + S.append(" kind=").append(std::to_string(Kind)); + S.append(" endpoint=").append(std::to_string(Endpoint)); + S.append(" device_num=").append(std::to_string(DeviceNum)); + S.append(" code=").append(makeHexString((uint64_t)CodeptrRA)); + return S; +} + +std::string internal::TargetEmi::toString() const { + // TODO Should we canonicalize the string prefix (use "OMPT ..." everywhere)? + std::string S{"Callback Target EMI: kind="}; + S.append(std::to_string(Kind)); + S.append(" endpoint=").append(std::to_string(Endpoint)); + S.append(" device_num=").append(std::to_string(DeviceNum)); + S.append(" task_data=").append(makeHexString((uint64_t)TaskData)); + S.append(" (") + .append(makeHexString((uint64_t)(TaskData) ? TaskData->value : 0, + /*IsPointer=*/false)) + .append(1, ')'); + S.append(" target_task_data=") + .append(makeHexString((uint64_t)TargetTaskData)); + S.append(" (") + .append( + makeHexString((uint64_t)(TargetTaskData) ? TargetTaskData->value : 0, + /*IsPointer=*/false)) + .append(1, ')'); + S.append(" target_data=").append(makeHexString((uint64_t)TargetData)); + S.append(" (") + .append(makeHexString((uint64_t)(TargetData) ? TargetData->value : 0, + /*IsPointer=*/false)) + .append(1, ')'); + S.append(" code=").append(makeHexString((uint64_t)CodeptrRA)); + return S; +} + +std::string internal::TargetDataOp::toString() const { + std::string S{" Callback DataOp: target_id="}; + S.append(std::to_string(TargetId)); + S.append(" host_op_id=").append(std::to_string(HostOpId)); + S.append(" optype=").append(std::to_string(OpType)); + S.append(" src=").append(makeHexString((uint64_t)SrcAddr)); + S.append(" src_device_num=").append(std::to_string(SrcDeviceNum)); + S.append(" dest=").append(makeHexString((uint64_t)DstAddr)); + S.append(" dest_device_num=").append(std::to_string(DstDeviceNum)); + S.append(" bytes=").append(std::to_string(Bytes)); + S.append(" code=").append(makeHexString((uint64_t)CodeptrRA)); + return S; +} + +std::string internal::TargetDataOpEmi::toString() const { + std::string S{" Callback DataOp EMI: endpoint="}; + S.append(std::to_string(Endpoint)); + S.append(" optype=").append(std::to_string(OpType)); + S.append(" target_task_data=") + .append(makeHexString((uint64_t)TargetTaskData)); + S.append(" (") + .append( + makeHexString((uint64_t)(TargetTaskData) ? TargetTaskData->value : 0, + /*IsPointer=*/false)) + .append(1, ')'); + S.append(" target_data=").append(makeHexString((uint64_t)TargetData)); + S.append(" (") + .append(makeHexString((uint64_t)(TargetData) ? TargetData->value : 0, + /*IsPointer=*/false)) + .append(1, ')'); + S.append(" host_op_id=").append(makeHexString((uint64_t)HostOpId)); + S.append(" (") + .append(makeHexString((uint64_t)(HostOpId) ? (*HostOpId) : 0, + /*IsPointer=*/false)) + .append(1, ')'); + S.append(" src=").append(makeHexString((uint64_t)SrcAddr)); + S.append(" src_device_num=").append(std::to_string(SrcDeviceNum)); + S.append(" dest=").append(makeHexString((uint64_t)DstAddr)); + S.append(" dest_device_num=").append(std::to_string(DstDeviceNum)); + S.append(" bytes=").append(std::to_string(Bytes)); + S.append(" code=").append(makeHexString((uint64_t)CodeptrRA)); + return S; +} + +std::string internal::TargetSubmit::toString() const { + std::string S{" Callback Submit: target_id="}; + S.append(std::to_string(TargetId)); + S.append(" host_op_id=").append(std::to_string(HostOpId)); + S.append(" req_num_teams=").append(std::to_string(RequestedNumTeams)); + return S; +} + +std::string internal::TargetSubmitEmi::toString() const { + std::string S{" Callback Submit EMI: endpoint="}; + S.append(std::to_string(Endpoint)); + S.append(" req_num_teams=").append(std::to_string(RequestedNumTeams)); + S.append(" target_data=").append(makeHexString((uint64_t)TargetData)); + S.append(" (") + .append(makeHexString((uint64_t)(TargetData) ? TargetData->value : 0, + /*IsPointer=*/false)) + .append(1, ')'); + S.append(" host_op_id=").append(makeHexString((uint64_t)HostOpId)); + S.append(" (") + .append(makeHexString((uint64_t)(HostOpId) ? (*HostOpId) : 0, + /*IsPointer=*/false)) + .append(1, ')'); + return S; +} + +std::string internal::DeviceInitialize::toString() const { + std::string S{"Callback Init: device_num="}; + S.append(std::to_string(DeviceNum)); + S.append(" type=").append((Type) ? Type : "(null)"); + S.append(" device=").append(makeHexString((uint64_t)Device)); + S.append(" lookup=").append(makeHexString((uint64_t)LookupFn)); + S.append(" doc=").append(makeHexString((uint64_t)DocStr)); + return S; +} + +std::string internal::DeviceFinalize::toString() const { + std::string S{"Callback Fini: device_num="}; + S.append(std::to_string(DeviceNum)); + return S; +} + +std::string internal::DeviceLoad::toString() const { + std::string S{"Callback Load: device_num:"}; + S.append(std::to_string(DeviceNum)); + S.append(" module_id:").append(std::to_string(ModuleId)); + S.append(" filename:").append((Filename == nullptr) ? "(null)" : Filename); + S.append(" host_adddr:").append(makeHexString((uint64_t)HostAddr)); + S.append(" device_addr:").append(makeHexString((uint64_t)DeviceAddr)); + S.append(" bytes:").append(std::to_string(Bytes)); + return S; +} + +std::string internal::BufferRequest::toString() const { + std::string S{"Allocated "}; + S.append(std::to_string((Bytes != nullptr) ? *Bytes : 0)) + .append(" bytes at "); + S.append(makeHexString((Buffer != nullptr) ? (uint64_t)*Buffer : 0)); + S.append(" in buffer request callback"); + return S; +} + +std::string internal::BufferComplete::toString() const { + std::string S{"Executing buffer complete callback: "}; + S.append(std::to_string(DeviceNum)).append(1, ' '); + S.append(makeHexString((uint64_t)Buffer)).append(1, ' '); + S.append(std::to_string(Bytes)).append(1, ' '); + S.append(makeHexString((uint64_t)Begin)).append(1, ' '); + S.append(std::to_string(BufferOwned)); + return S; +} + +std::string internal::BufferRecord::toString() const { + std::string S{""}; + std::string T{""}; + S.append("rec=").append(makeHexString((uint64_t)RecordPtr)); + S.append(" type=").append(std::to_string(Record.type)); + + T.append("time=").append(std::to_string(Record.time)); + T.append(" thread_id=").append(std::to_string(Record.thread_id)); + T.append(" target_id=").append(std::to_string(Record.target_id)); + + switch (Record.type) { + case ompt_callback_target: + case ompt_callback_target_emi: { + // Handle Target Record + ompt_record_target_t TR = Record.record.target; + S.append(" (Target task) ").append(T); + S.append(" kind=").append(std::to_string(TR.kind)); + S.append(" endpoint=").append(std::to_string(TR.endpoint)); + S.append(" device=").append(std::to_string(TR.device_num)); + S.append(" task_id=").append(std::to_string(TR.task_id)); + S.append(" codeptr=").append(makeHexString((uint64_t)TR.codeptr_ra)); + break; + } + case ompt_callback_target_data_op: + case ompt_callback_target_data_op_emi: { + // Handle Target DataOp Record + ompt_record_target_data_op_t TDR = Record.record.target_data_op; + S.append(" (Target data op) ").append(T); + S.append(" host_op_id=").append(std::to_string(TDR.host_op_id)); + S.append(" optype=").append(std::to_string(TDR.optype)); + S.append(" src_addr=").append(makeHexString((uint64_t)TDR.src_addr)); + S.append(" src_device=").append(std::to_string(TDR.src_device_num)); + S.append(" dest_addr=").append(makeHexString((uint64_t)TDR.dest_addr)); + S.append(" dest_device=").append(std::to_string(TDR.dest_device_num)); + S.append(" bytes=").append(std::to_string(TDR.bytes)); + S.append(" end_time=").append(std::to_string(TDR.end_time)); + S.append(" duration=").append(std::to_string(TDR.end_time - Record.time)); + S.append(" ns codeptr=").append(makeHexString((uint64_t)TDR.codeptr_ra)); + break; + } + case ompt_callback_target_submit: + case ompt_callback_target_submit_emi: { + // Handle Target Kernel Record + ompt_record_target_kernel_t TKR = Record.record.target_kernel; + S.append(" (Target kernel) ").append(T); + S.append(" host_op_id=").append(std::to_string(TKR.host_op_id)); + S.append(" requested_num_teams=") + .append(std::to_string(TKR.requested_num_teams)); + S.append(" granted_num_teams=") + .append(std::to_string(TKR.granted_num_teams)); + S.append(" end_time=").append(std::to_string(TKR.end_time)); + S.append(" duration=").append(std::to_string(TKR.end_time - Record.time)); + S.append(" ns"); + break; + } + default: + S.append(" (unsupported record type)"); + break; + } + + return S; +} + +std::string internal::BufferRecordDeallocation::toString() const { + std::string S{"Deallocated "}; + S.append(makeHexString((uint64_t)Buffer)); + return S; +} diff --git a/openmp/tools/omptest/src/InternalEventOperators.cpp b/openmp/tools/omptest/src/InternalEventOperators.cpp new file mode 100644 index 0000000000000..49c61a44a7aba --- /dev/null +++ b/openmp/tools/omptest/src/InternalEventOperators.cpp @@ -0,0 +1,366 @@ +//===- InternalEventOperators.cpp - Operator 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 +/// Defines the internal event operators, like comparators. +/// +//===----------------------------------------------------------------------===// + +#include "InternalEvent.h" + +namespace omptest { + +namespace internal { +// clang-format off +event_class_operator_stub(AssertionSyncPoint) +event_class_operator_stub(AssertionSuspend) +event_class_operator_stub(ThreadBegin) +event_class_operator_stub(ThreadEnd) +event_class_operator_w_body(ParallelBegin, \ + return Expected.NumThreads == Observed.NumThreads; \ +) +event_class_operator_stub(ParallelEnd) +event_class_operator_w_body(Work, \ + bool isSameWorkType = (Expected.WorkType == Observed.WorkType); \ + bool isSameEndpoint = (Expected.Endpoint == Observed.Endpoint); \ + bool isSameParallelData = \ + (Expected.ParallelData == expectedDefault(ompt_data_t *)) ? \ + true : (Expected.ParallelData == Observed.ParallelData); \ + bool isSameTaskData = \ + (Expected.TaskData == expectedDefault(ompt_data_t *)) ? \ + true : (Expected.TaskData == Observed.TaskData); \ + bool isSameCount = (Expected.Count == expectedDefault(uint64_t)) ? \ + true : (Expected.Count == Observed.Count); \ + return isSameWorkType && isSameEndpoint && isSameParallelData && \ + isSameTaskData && isSameCount; \ +) +event_class_operator_stub(Dispatch) +event_class_operator_stub(TaskCreate) +event_class_operator_stub(Dependences) +event_class_operator_stub(TaskDependence) +event_class_operator_stub(TaskSchedule) +event_class_operator_w_body(ImplicitTask, \ + bool isSameEndpoint = (Expected.Endpoint == Observed.Endpoint); \ + bool isSameActualParallelism = \ + (Expected.ActualParallelism == expectedDefault(unsigned int)) ? \ + true : (Expected.ActualParallelism == Observed.ActualParallelism); \ + bool isSameIndex = (Expected.Index == expectedDefault(unsigned int)) ? \ + true : ( Expected.Index == Observed.Index); \ + return isSameEndpoint && isSameActualParallelism && isSameIndex; \ +) +event_class_operator_stub(Masked) +event_class_operator_w_body(SyncRegion, \ + bool isSameKind = (Expected.Kind == Observed.Kind); \ + bool isSameEndpoint = (Expected.Endpoint == Observed.Endpoint); \ + bool isSameParallelData = \ + (Expected.ParallelData == expectedDefault(ompt_data_t *)) ? \ + true : (Expected.ParallelData == Observed.ParallelData); \ + bool isSameTaskData = \ + (Expected.TaskData == expectedDefault(ompt_data_t *)) ? \ + true : (Expected.TaskData == Observed.TaskData); \ + return isSameKind && isSameEndpoint && isSameParallelData && isSameTaskData; \ +) +event_class_operator_stub(MutexAcquire) +event_class_operator_stub(Mutex) +event_class_operator_stub(NestLock) +event_class_operator_stub(Flush) +event_class_operator_stub(Cancel) +event_class_operator_w_body(Target, \ + bool isSameKind = (Expected.Kind == Observed.Kind); \ + bool isSameEndpoint = (Expected.Endpoint == Observed.Endpoint); \ + bool isSameDeviceNum = (Expected.DeviceNum == expectedDefault(int)) ? \ + true : (Expected.DeviceNum == Observed.DeviceNum); \ + return isSameKind && isSameEndpoint && isSameDeviceNum; \ +) +event_class_operator_w_body(TargetEmi, \ + bool isSameKind = (Expected.Kind == Observed.Kind); \ + bool isSameEndpoint = (Expected.Endpoint == Observed.Endpoint); \ + bool isSameDeviceNum = (Expected.DeviceNum == expectedDefault(int)) ? \ + true : (Expected.DeviceNum == Observed.DeviceNum); \ + return isSameKind && isSameEndpoint && isSameDeviceNum; \ +) +event_class_operator_w_body(TargetDataOp, \ + bool isSameOpType = (Expected.OpType == Observed.OpType); \ + bool isSameSize = (Expected.Bytes == expectedDefault(size_t)) ? \ + true : (Expected.Bytes == Observed.Bytes); \ + bool isSameSrcAddr = (Expected.SrcAddr == expectedDefault(void *)) ? \ + true : (Expected.SrcAddr == Observed.SrcAddr); \ + bool isSameDstAddr = (Expected.DstAddr == expectedDefault(void *)) ? \ + true : (Expected.DstAddr == Observed.DstAddr); \ + bool isSameSrcDeviceNum = \ + (Expected.SrcDeviceNum == expectedDefault(int)) ? \ + true : (Expected.SrcDeviceNum == Observed.SrcDeviceNum); \ + bool isSameDstDeviceNum = \ + (Expected.DstDeviceNum == expectedDefault(int)) ? \ + true : (Expected.DstDeviceNum == Observed.DstDeviceNum); \ + return isSameOpType && isSameSize && isSameSrcAddr && isSameDstAddr && \ + isSameSrcDeviceNum && isSameDstDeviceNum; \ +) +event_class_operator_w_body(TargetDataOpEmi, \ + bool isSameOpType = (Expected.OpType == Observed.OpType); \ + bool isSameEndpoint = (Expected.Endpoint == Observed.Endpoint); \ + bool isSameSize = (Expected.Bytes == expectedDefault(size_t)) ? \ + true : (Expected.Bytes == Observed.Bytes); \ + bool isSameSrcAddr = (Expected.SrcAddr == expectedDefault(void *)) ? \ + true : (Expected.SrcAddr == Observed.SrcAddr); \ + bool isSameDstAddr = (Expected.DstAddr == expectedDefault(void *)) ? \ + true : (Expected.DstAddr == Observed.DstAddr); \ + bool isSameSrcDeviceNum = \ + (Expected.SrcDeviceNum == expectedDefault(int)) ? \ + true : (Expected.SrcDeviceNum == Observed.SrcDeviceNum); \ + bool isSameDstDeviceNum = \ + (Expected.DstDeviceNum == expectedDefault(int)) ? \ + true : (Expected.DstDeviceNum == Observed.DstDeviceNum); \ + return isSameOpType && isSameEndpoint && isSameSize && isSameSrcAddr && \ + isSameDstAddr && isSameSrcDeviceNum && isSameDstDeviceNum; \ +) +event_class_operator_w_body(TargetSubmit, \ + bool isSameReqNumTeams = \ + (Expected.RequestedNumTeams == Observed.RequestedNumTeams); \ + return isSameReqNumTeams; \ +) +event_class_operator_w_body(TargetSubmitEmi, \ + bool isSameReqNumTeams = \ + (Expected.RequestedNumTeams == Observed.RequestedNumTeams); \ + bool isSameEndpoint = (Expected.Endpoint == Observed.Endpoint); \ + return isSameReqNumTeams && isSameEndpoint; \ +) +event_class_operator_stub(ControlTool) +event_class_operator_w_body(DeviceInitialize, \ + bool isSameDeviceNum = (Expected.DeviceNum == Observed.DeviceNum); \ + bool isSameType = (Expected.Type == expectedDefault(const char *)) ? \ + true : \ + ((Expected.Type == Observed.Type) || \ + (strcmp(Expected.Type, Observed.Type) == 0)); \ + bool isSameDevice = \ + (Expected.Device == expectedDefault(ompt_device_t *)) ? \ + true : (Expected.Device == Observed.Device); \ + return isSameDeviceNum && isSameType && isSameDevice; \ +) +event_class_operator_w_body(DeviceFinalize, \ + bool isSameDeviceNum = (Expected.DeviceNum == expectedDefault(int)) ? \ + true : (Expected.DeviceNum == Observed.DeviceNum); \ + return isSameDeviceNum; +) +event_class_operator_w_body(DeviceLoad, \ + bool isSameDeviceNum = (Expected.DeviceNum == expectedDefault(int)) ? \ + true : (Expected.DeviceNum == Observed.DeviceNum); \ + bool isSameSize = (Expected.Bytes == expectedDefault(size_t)) ? \ + true : (Expected.Bytes == Observed.Bytes); \ + return isSameDeviceNum && isSameSize; \ +) +event_class_operator_stub(DeviceUnload) +event_class_operator_w_body(BufferRequest, \ + bool isSameDeviceNum = (Expected.DeviceNum == expectedDefault(int)) ? \ + true : (Expected.DeviceNum == Observed.DeviceNum); \ + bool isSameSize = (Expected.Bytes == expectedDefault(size_t *)) ? \ + true : (Expected.Bytes == Observed.Bytes); \ + return isSameDeviceNum && isSameSize; \ +) +event_class_operator_w_body(BufferComplete, \ + bool isSameDeviceNum = (Expected.DeviceNum == expectedDefault(int)) ? \ + true : (Expected.DeviceNum == Observed.DeviceNum); \ + bool isSameSize = (Expected.Bytes == expectedDefault(size_t)) ? \ + true : (Expected.Bytes == Observed.Bytes); \ + return isSameDeviceNum && isSameSize; \ +) +event_class_operator_w_body(BufferRecord, \ + bool isSameType = (Expected.Record.type == Observed.Record.type); \ + bool isSameTargetId = \ + (Expected.Record.target_id == expectedDefault(ompt_id_t)) \ + ? true \ + : (Expected.Record.target_id == Observed.Record.target_id); \ + if (!(isSameType && isSameTargetId)) return false; \ + bool isEqual = true; \ + ompt_device_time_t ObservedDurationNs = \ + Observed.Record.record.target_data_op.end_time - Observed.Record.time; \ + switch(Expected.Record.type) { \ + case ompt_callback_target: \ + isEqual &= \ + (Expected.Record.record.target.kind == expectedDefault(ompt_target_t)) \ + ? true \ + : (Expected.Record.record.target.kind == \ + Observed.Record.record.target.kind); \ + isEqual &= \ + (Expected.Record.record.target.endpoint == \ + expectedDefault(ompt_scope_endpoint_t)) \ + ? true \ + : (Expected.Record.record.target.endpoint == \ + Observed.Record.record.target.endpoint); \ + isEqual &= \ + (Expected.Record.record.target.device_num == expectedDefault(int)) \ + ? true \ + : (Expected.Record.record.target.device_num == \ + Observed.Record.record.target.device_num); \ + break; \ + case ompt_callback_target_data_op: \ + isEqual &= \ + (Expected.Record.record.target_data_op.optype == \ + expectedDefault(ompt_target_data_op_t)) \ + ? true \ + : (Expected.Record.record.target_data_op.optype == \ + Observed.Record.record.target_data_op.optype); \ + isEqual &= \ + (Expected.Record.record.target_data_op.bytes == expectedDefault(size_t)) \ + ? true \ + : (Expected.Record.record.target_data_op.bytes == \ + Observed.Record.record.target_data_op.bytes); \ + isEqual &= \ + (Expected.Record.record.target_data_op.src_addr == \ + expectedDefault(void *)) \ + ? true \ + : (Expected.Record.record.target_data_op.src_addr == \ + Observed.Record.record.target_data_op.src_addr); \ + isEqual &= \ + (Expected.Record.record.target_data_op.dest_addr == \ + expectedDefault(void *)) \ + ? true \ + : (Expected.Record.record.target_data_op.dest_addr == \ + Observed.Record.record.target_data_op.dest_addr); \ + isEqual &= \ + (Expected.Record.record.target_data_op.src_device_num == \ + expectedDefault(int)) \ + ? true \ + : (Expected.Record.record.target_data_op.src_device_num == \ + Observed.Record.record.target_data_op.src_device_num); \ + isEqual &= \ + (Expected.Record.record.target_data_op.dest_device_num == \ + expectedDefault(int)) \ + ? true \ + : (Expected.Record.record.target_data_op.dest_device_num == \ + Observed.Record.record.target_data_op.dest_device_num); \ + isEqual &= \ + (Expected.Record.record.target_data_op.host_op_id == \ + expectedDefault(ompt_id_t)) \ + ? true \ + : (Expected.Record.record.target_data_op.host_op_id == \ + Observed.Record.record.target_data_op.host_op_id); \ + isEqual &= \ + (Expected.Record.record.target_data_op.codeptr_ra == \ + expectedDefault(void *)) \ + ? true \ + : (Expected.Record.record.target_data_op.codeptr_ra == \ + Observed.Record.record.target_data_op.codeptr_ra); \ + if (Expected.Record.record.target_data_op.end_time != \ + expectedDefault(ompt_device_time_t)) { \ + isEqual &= \ + ObservedDurationNs <= Expected.Record.record.target_data_op.end_time; \ + } \ + isEqual &= ObservedDurationNs >= Expected.Record.time; \ + break; \ + case ompt_callback_target_submit: \ + ObservedDurationNs = \ + Observed.Record.record.target_kernel.end_time - Observed.Record.time; \ + isEqual &= \ + (Expected.Record.record.target_kernel.requested_num_teams == \ + expectedDefault(unsigned int)) \ + ? true \ + : (Expected.Record.record.target_kernel.requested_num_teams == \ + Observed.Record.record.target_kernel.requested_num_teams); \ + isEqual &= \ + (Expected.Record.record.target_kernel.granted_num_teams == \ + expectedDefault(unsigned int)) \ + ? true \ + : (Expected.Record.record.target_kernel.granted_num_teams == \ + Observed.Record.record.target_kernel.granted_num_teams); \ + isEqual &= \ + (Expected.Record.record.target_kernel.host_op_id == \ + expectedDefault(ompt_id_t)) \ + ? true \ + : (Expected.Record.record.target_kernel.host_op_id == \ + Observed.Record.record.target_kernel.host_op_id); \ + if (Expected.Record.record.target_kernel.end_time != \ + expectedDefault(ompt_device_time_t)) { \ + isEqual &= \ + ObservedDurationNs <= Expected.Record.record.target_kernel.end_time; \ + } \ + isEqual &= ObservedDurationNs >= Expected.Record.time; \ + break; \ + default: \ + assert(false && "Encountered invalid record type"); \ + } \ + return isEqual; \ +) +event_class_operator_stub(BufferRecordDeallocation) + +define_cast_func(AssertionSyncPoint) +define_cast_func(AssertionSuspend) +define_cast_func(ThreadBegin) +define_cast_func(ThreadEnd) +define_cast_func(ParallelBegin) +define_cast_func(ParallelEnd) +define_cast_func(Work) +define_cast_func(Dispatch) +define_cast_func(TaskCreate) +define_cast_func(Dependences) +define_cast_func(TaskDependence) +define_cast_func(TaskSchedule) +define_cast_func(ImplicitTask) +define_cast_func(Masked) +define_cast_func(SyncRegion) +define_cast_func(MutexAcquire) +define_cast_func(Mutex) +define_cast_func(NestLock) +define_cast_func(Flush) +define_cast_func(Cancel) +define_cast_func(Target) +define_cast_func(TargetEmi) +define_cast_func(TargetDataOp) +define_cast_func(TargetDataOpEmi) +define_cast_func(TargetSubmit) +define_cast_func(TargetSubmitEmi) +define_cast_func(ControlTool) +define_cast_func(DeviceInitialize) +define_cast_func(DeviceFinalize) +define_cast_func(DeviceLoad) +define_cast_func(DeviceUnload) +define_cast_func(BufferRequest) +define_cast_func(BufferComplete) +define_cast_func(BufferRecord) +define_cast_func(BufferRecordDeallocation) + +class_equals_op(AssertionSyncPoint) +class_equals_op(AssertionSuspend) +class_equals_op(ThreadBegin) +class_equals_op(ThreadEnd) +class_equals_op(ParallelBegin) +class_equals_op(ParallelEnd) +class_equals_op(Work) +class_equals_op(Dispatch) +class_equals_op(TaskCreate) +class_equals_op(Dependences) +class_equals_op(TaskDependence) +class_equals_op(TaskSchedule) +class_equals_op(ImplicitTask) +class_equals_op(Masked) +class_equals_op(SyncRegion) +class_equals_op(MutexAcquire) +class_equals_op(Mutex) +class_equals_op(NestLock) +class_equals_op(Flush) +class_equals_op(Cancel) +class_equals_op(Target) +class_equals_op(TargetEmi) +class_equals_op(TargetDataOp) +class_equals_op(TargetDataOpEmi) +class_equals_op(TargetSubmit) +class_equals_op(TargetSubmitEmi) +class_equals_op(ControlTool) +class_equals_op(DeviceInitialize) +class_equals_op(DeviceFinalize) +class_equals_op(DeviceLoad) +class_equals_op(DeviceUnload) +class_equals_op(BufferRequest) +class_equals_op(BufferComplete) +class_equals_op(BufferRecord) +class_equals_op(BufferRecordDeallocation) +// clang-format on + +} // namespace internal + +} // namespace omptest diff --git a/openmp/tools/omptest/src/Logging.cpp b/openmp/tools/omptest/src/Logging.cpp new file mode 100644 index 0000000000000..28329c74d188d --- /dev/null +++ b/openmp/tools/omptest/src/Logging.cpp @@ -0,0 +1,177 @@ +//===- Logging.cpp - General logging class implementation -------*- 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 ompTest-tailored logging. +/// +//===----------------------------------------------------------------------===// + +#include "Logging.h" + +using namespace omptest; +using namespace logging; + +Logger::Logger(Level LogLevel, std::ostream &OutStream, bool FormatOutput) + : LoggingLevel(LogLevel), OutStream(OutStream), FormatOutput(FormatOutput) { + // Flush any buffered output + OutStream << std::flush; +} + +Logger::~Logger() { + // Flush any buffered output + OutStream << std::flush; +} + +std::map> AggregatedFormatOptions{ + {Level::DIAGNOSTIC, {FormatOption::COLOR_LightBlue}}, + {Level::INFO, {FormatOption::COLOR_LightGray}}, + {Level::WARNING, {FormatOption::COLOR_LightYellow}}, + {Level::ERROR, {FormatOption::COLOR_Red}}, + {Level::CRITICAL, {FormatOption::COLOR_LightRed}}, + {Level::Default, {FormatOption::NONE}}, + {Level::ExpectedEvent, {FormatOption::BOLD, FormatOption::COLOR_Cyan}}, + {Level::ObservedEvent, {FormatOption::COLOR_Cyan}}, + {Level::OffendingEvent, {FormatOption::COLOR_Yellow}}}; + +const char *logging::to_string(Level LogLevel) { + switch (LogLevel) { + case Level::DIAGNOSTIC: + return "DIAGNOSTIC"; + case Level::INFO: + return "INFO"; + case Level::WARNING: + return "WARNING"; + case Level::ERROR: + return "ERROR"; + case Level::CRITICAL: + return "CRITICAL"; + default: + assert(false && "Requested string representation for unknown LogLevel"); + return "UNKNOWN"; + } +} + +std::string logging::getFormatSequence(Level LogLevel) { + auto Options = AggregatedFormatOptions[LogLevel]; + std::stringstream SS{"\033["}; + SS << "\033["; + if (!Options.empty()) { + for (auto &Option : AggregatedFormatOptions[LogLevel]) + SS << int(Option) << ';'; + SS.seekp(-1, SS.cur); + SS << 'm'; + } else { + // Fallback to NONE / reset formatting + SS << "0m"; + } + return SS.str(); +} + +std::string logging::format(const std::string &Message, FormatOption Option) { + std::stringstream SS{"\033["}; + SS << "\033["; + SS << int(Option) << 'm' << Message << "\033[0m"; + return SS.str(); +} + +std::string logging::format(const std::string &Message, + std::set Options) { + std::stringstream SS{"\033["}; + SS << "\033["; + for (auto &Option : Options) + SS << int(Option) << ';'; + SS.seekp(-1, SS.cur); + SS << 'm' << Message << "\033[0m"; + return SS.str(); +} + +void Logger::log(Level LogLevel, const std::string &Message) const { + // Serialize logging + std::lock_guard Lock(LogMutex); + + if (LoggingLevel > LogLevel) + return; + + if (FormatOutput) { + OutStream << getFormatSequence(LogLevel) << '[' << to_string(LogLevel) + << "] " << Message << getFormatSequence() << std::endl; + } else { + OutStream << '[' << to_string(LogLevel) << "] " << Message << std::endl; + } +} + +void Logger::eventMismatch(const omptest::OmptAssertEvent &OffendingEvent, + const std::string &Message, Level LogLevel) const { + // Serialize logging + std::lock_guard Lock(LogMutex); + if (LoggingLevel > LogLevel) + return; + + if (FormatOutput) { + OutStream << getFormatSequence(LogLevel) << '[' << to_string(LogLevel) + << "] " << getFormatSequence() + << format(Message, AggregatedFormatOptions[LogLevel]) + << "\n\tOffending event name='" + << format(OffendingEvent.getEventName(), + AggregatedFormatOptions[Level::OffendingEvent]) + << "'\n\tOffending='" + << format(OffendingEvent.toString(), + AggregatedFormatOptions[Level::OffendingEvent]) + << '\'' << std::endl; + } else { + OutStream << '[' << to_string(LogLevel) << "] " << Message + << "\n\tOffending event name='" << OffendingEvent.getEventName() + << "'\n\tOffending='" << OffendingEvent.toString() << '\'' + << std::endl; + } +} + +void Logger::eventMismatch(const omptest::OmptAssertEvent &ExpectedEvent, + const omptest::OmptAssertEvent &ObservedEvent, + const std::string &Message, Level LogLevel) const { + // Serialize logging + std::lock_guard Lock(LogMutex); + if (LoggingLevel > LogLevel) + return; + + if (FormatOutput) { + OutStream << getFormatSequence(LogLevel) << '[' << to_string(LogLevel) + << "] " << Message << getFormatSequence() + << "\n\tExpected event name='" + << format(ExpectedEvent.getEventName(), + AggregatedFormatOptions[Level::ExpectedEvent]) + << "' observe='" + << format(to_string(ExpectedEvent.getEventExpectedState()), + AggregatedFormatOptions[Level::ExpectedEvent]) + << "'\n\tObserved event name='" + << format(ObservedEvent.getEventName(), + AggregatedFormatOptions[Level::ObservedEvent]) + << "'\n\tExpected='" + << format(ExpectedEvent.toString(), + AggregatedFormatOptions[Level::ExpectedEvent]) + << "'\n\tObserved='" + << format(ObservedEvent.toString(), + AggregatedFormatOptions[Level::ObservedEvent]) + << '\'' << std::endl; + } else { + OutStream << '[' << to_string(LogLevel) << "] " << Message + << "\n\tExpected event name='" << ExpectedEvent.getEventName() + << "' observe='" + << to_string(ExpectedEvent.getEventExpectedState()) + << "'\n\tObserved event name='" << ObservedEvent.getEventName() + << "'\n\tExpected='" << ExpectedEvent.toString() + << "'\n\tObserved='" << ObservedEvent.toString() << '\'' + << std::endl; + } +} + +void Logger::setFormatOutput(bool Enabled) { FormatOutput = Enabled; } + +Level Logger::getLoggingLevel() const { return LoggingLevel; } + +void Logger::setLoggingLevel(Level LogLevel) { LoggingLevel = LogLevel; } diff --git a/openmp/tools/omptest/src/OmptAssertEvent.cpp b/openmp/tools/omptest/src/OmptAssertEvent.cpp new file mode 100644 index 0000000000000..b03f267a8c397 --- /dev/null +++ b/openmp/tools/omptest/src/OmptAssertEvent.cpp @@ -0,0 +1,587 @@ +//===- OmptAssertEvent.cpp - Assertion event 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 assertion event CTORs, for generally all observable events. +/// +//===----------------------------------------------------------------------===// + +#include "OmptAssertEvent.h" +#include + +using namespace omptest; + +const char *omptest::to_string(ObserveState State) { + switch (State) { + case ObserveState::generated: + return "generated"; + case ObserveState::always: + return "always"; + case ObserveState::never: + return "never"; + default: + assert(false && "Requested string representation for unknown ObserveState"); + return "UNKNOWN"; + } +} + +OmptAssertEvent::OmptAssertEvent(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + internal::InternalEvent *IE) + : Name(Name), Group(Group), ExpectedState(Expected), TheEvent(IE) {} + +OmptAssertEvent OmptAssertEvent::AssertionSyncPoint( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, const std::string &SyncPointName) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::AssertionSyncPoint(SyncPointName)); +} + +OmptAssertEvent +OmptAssertEvent::AssertionSuspend(const std::string &Name, + const std::string &Group, + const ObserveState &Expected) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::AssertionSuspend()); +} + +OmptAssertEvent OmptAssertEvent::ThreadBegin(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + ompt_thread_t ThreadType) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::ThreadBegin(ThreadType)); +} + +OmptAssertEvent OmptAssertEvent::ThreadEnd(const std::string &Name, + const std::string &Group, + const ObserveState &Expected) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, new internal::ThreadEnd()); +} + +OmptAssertEvent OmptAssertEvent::ParallelBegin(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + int NumThreads) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::ParallelBegin(NumThreads)); +} + +OmptAssertEvent OmptAssertEvent::ParallelEnd(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + ompt_data_t *ParallelData, + ompt_data_t *EncounteringTaskData, + int Flags, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::ParallelEnd(ParallelData, + EncounteringTaskData, Flags, + CodeptrRA)); +} + +OmptAssertEvent +OmptAssertEvent::Work(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_work_t WorkType, + ompt_scope_endpoint_t Endpoint, ompt_data_t *ParallelData, + ompt_data_t *TaskData, uint64_t Count, + const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::Work(WorkType, Endpoint, ParallelData, + TaskData, Count, CodeptrRA)); +} + +OmptAssertEvent +OmptAssertEvent::Dispatch(const std::string &Name, const std::string &Group, + const ObserveState &Expected, + ompt_data_t *ParallelData, ompt_data_t *TaskData, + ompt_dispatch_t Kind, ompt_data_t Instance) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent( + EName, EGroup, Expected, + new internal::Dispatch(ParallelData, TaskData, Kind, Instance)); +} + +OmptAssertEvent OmptAssertEvent::TaskCreate( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_data_t *EncounteringTaskData, + const ompt_frame_t *EncounteringTaskFrame, ompt_data_t *NewTaskData, + int Flags, int HasDependences, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent( + EName, EGroup, Expected, + new internal::TaskCreate(EncounteringTaskData, EncounteringTaskFrame, + NewTaskData, Flags, HasDependences, CodeptrRA)); +} + +OmptAssertEvent OmptAssertEvent::TaskSchedule(const std::string &Name, + const std::string &Group, + const ObserveState &Expected) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, new internal::TaskSchedule()); +} + +OmptAssertEvent OmptAssertEvent::ImplicitTask( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData, ompt_data_t *TaskData, + unsigned int ActualParallelism, unsigned int Index, int Flags) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::ImplicitTask(Endpoint, ParallelData, + TaskData, ActualParallelism, + Index, Flags)); +} + +OmptAssertEvent OmptAssertEvent::SyncRegion( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_sync_region_t Kind, + ompt_scope_endpoint_t Endpoint, ompt_data_t *ParallelData, + ompt_data_t *TaskData, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::SyncRegion(Kind, Endpoint, ParallelData, + TaskData, CodeptrRA)); +} + +OmptAssertEvent +OmptAssertEvent::Target(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_target_t Kind, + ompt_scope_endpoint_t Endpoint, int DeviceNum, + ompt_data_t *TaskData, ompt_id_t TargetId, + const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::Target(Kind, Endpoint, DeviceNum, + TaskData, TargetId, CodeptrRA)); +} + +OmptAssertEvent +OmptAssertEvent::TargetEmi(const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_target_t Kind, + ompt_scope_endpoint_t Endpoint, int DeviceNum, + ompt_data_t *TaskData, ompt_data_t *TargetTaskData, + ompt_data_t *TargetData, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::TargetEmi(Kind, Endpoint, DeviceNum, + TaskData, TargetTaskData, + TargetData, CodeptrRA)); +} + +OmptAssertEvent OmptAssertEvent::TargetDataOp( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_id_t TargetId, ompt_id_t HostOpId, + ompt_target_data_op_t OpType, void *SrcAddr, int SrcDeviceNum, + void *DstAddr, int DstDeviceNum, size_t Bytes, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::TargetDataOp( + TargetId, HostOpId, OpType, SrcAddr, SrcDeviceNum, + DstAddr, DstDeviceNum, Bytes, CodeptrRA)); +} + +OmptAssertEvent OmptAssertEvent::TargetDataOp( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_target_data_op_t OpType, size_t Bytes, + void *SrcAddr, void *DstAddr, int SrcDeviceNum, int DstDeviceNum, + ompt_id_t TargetId, ompt_id_t HostOpId, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::TargetDataOp( + TargetId, HostOpId, OpType, SrcAddr, SrcDeviceNum, + DstAddr, DstDeviceNum, Bytes, CodeptrRA)); +} + +OmptAssertEvent OmptAssertEvent::TargetDataOpEmi( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_scope_endpoint_t Endpoint, + ompt_data_t *TargetTaskData, ompt_data_t *TargetData, ompt_id_t *HostOpId, + ompt_target_data_op_t OpType, void *SrcAddr, int SrcDeviceNum, + void *DstAddr, int DstDeviceNum, size_t Bytes, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent( + EName, EGroup, Expected, + new internal::TargetDataOpEmi(Endpoint, TargetTaskData, TargetData, + HostOpId, OpType, SrcAddr, SrcDeviceNum, + DstAddr, DstDeviceNum, Bytes, CodeptrRA)); +} + +OmptAssertEvent OmptAssertEvent::TargetDataOpEmi( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_target_data_op_t OpType, + ompt_scope_endpoint_t Endpoint, size_t Bytes, void *SrcAddr, void *DstAddr, + int SrcDeviceNum, int DstDeviceNum, ompt_data_t *TargetTaskData, + ompt_data_t *TargetData, ompt_id_t *HostOpId, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent( + EName, EGroup, Expected, + new internal::TargetDataOpEmi(Endpoint, TargetTaskData, TargetData, + HostOpId, OpType, SrcAddr, SrcDeviceNum, + DstAddr, DstDeviceNum, Bytes, CodeptrRA)); +} + +OmptAssertEvent OmptAssertEvent::TargetSubmit(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + ompt_id_t TargetId, + ompt_id_t HostOpId, + unsigned int RequestedNumTeams) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent( + EName, EGroup, Expected, + new internal::TargetSubmit(TargetId, HostOpId, RequestedNumTeams)); +} + +OmptAssertEvent OmptAssertEvent::TargetSubmit(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + unsigned int RequestedNumTeams, + ompt_id_t TargetId, + ompt_id_t HostOpId) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent( + EName, EGroup, Expected, + new internal::TargetSubmit(TargetId, HostOpId, RequestedNumTeams)); +} + +OmptAssertEvent OmptAssertEvent::TargetSubmitEmi( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_scope_endpoint_t Endpoint, + ompt_data_t *TargetData, ompt_id_t *HostOpId, + unsigned int RequestedNumTeams) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::TargetSubmitEmi(Endpoint, TargetData, + HostOpId, + RequestedNumTeams)); +} + +OmptAssertEvent OmptAssertEvent::TargetSubmitEmi(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + unsigned int RequestedNumTeams, + ompt_scope_endpoint_t Endpoint, + ompt_data_t *TargetData, + ompt_id_t *HostOpId) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::TargetSubmitEmi(Endpoint, TargetData, + HostOpId, + RequestedNumTeams)); +} + +OmptAssertEvent OmptAssertEvent::ControlTool(const std::string &Name, + const std::string &Group, + const ObserveState &Expected) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, new internal::ControlTool()); +} + +OmptAssertEvent OmptAssertEvent::DeviceInitialize( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, int DeviceNum, const char *Type, + ompt_device_t *Device, ompt_function_lookup_t LookupFn, + const char *DocumentationStr) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::DeviceInitialize(DeviceNum, Type, Device, + LookupFn, + DocumentationStr)); +} + +OmptAssertEvent OmptAssertEvent::DeviceFinalize(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + int DeviceNum) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::DeviceFinalize(DeviceNum)); +} + +OmptAssertEvent +OmptAssertEvent::DeviceLoad(const std::string &Name, const std::string &Group, + const ObserveState &Expected, int DeviceNum, + const char *Filename, int64_t OffsetInFile, + void *VmaInFile, size_t Bytes, void *HostAddr, + void *DeviceAddr, uint64_t ModuleId) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent( + EName, EGroup, Expected, + new internal::DeviceLoad(DeviceNum, Filename, OffsetInFile, VmaInFile, + Bytes, HostAddr, DeviceAddr, ModuleId)); +} + +OmptAssertEvent OmptAssertEvent::DeviceUnload(const std::string &Name, + const std::string &Group, + const ObserveState &Expected) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, new internal::DeviceUnload()); +} + +OmptAssertEvent OmptAssertEvent::BufferRequest(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + int DeviceNum, + ompt_buffer_t **Buffer, + size_t *Bytes) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::BufferRequest(DeviceNum, Buffer, Bytes)); +} + +OmptAssertEvent OmptAssertEvent::BufferComplete( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, int DeviceNum, ompt_buffer_t *Buffer, + size_t Bytes, ompt_buffer_cursor_t Begin, int BufferOwned) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::BufferComplete(DeviceNum, Buffer, Bytes, + Begin, BufferOwned)); +} + +OmptAssertEvent OmptAssertEvent::BufferRecord(const std::string &Name, + const std::string &Group, + const ObserveState &Expected, + ompt_record_ompt_t *Record) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::BufferRecord(Record)); +} + +OmptAssertEvent OmptAssertEvent::BufferRecord( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, ompt_target_t Kind, + ompt_scope_endpoint_t Endpoint, int DeviceNum, ompt_id_t TaskId, + ompt_id_t TargetId, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + + if (Type != ompt_callback_target) + assert(false && "CTOR only suited for type: 'ompt_callback_target'"); + + ompt_record_target_t Subrecord{Kind, Endpoint, DeviceNum, + TaskId, TargetId, CodeptrRA}; + + ompt_record_ompt_t *RecordPtr = + (ompt_record_ompt_t *)malloc(sizeof(ompt_record_ompt_t)); + memset(RecordPtr, 0, sizeof(ompt_record_ompt_t)); + RecordPtr->type = Type; + RecordPtr->time = expectedDefault(ompt_device_time_t); + RecordPtr->thread_id = expectedDefault(ompt_id_t); + RecordPtr->target_id = TargetId; + RecordPtr->record.target = Subrecord; + + return OmptAssertEvent(EName, EGroup, Expected, + new internal::BufferRecord(RecordPtr)); +} + +OmptAssertEvent OmptAssertEvent::BufferRecord( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, + ompt_target_data_op_t OpType, size_t Bytes, + std::pair Timeframe, void *SrcAddr, + void *DstAddr, int SrcDeviceNum, int DstDeviceNum, ompt_id_t TargetId, + ompt_id_t HostOpId, const void *CodeptrRA) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + + if (Type != ompt_callback_target_data_op) + assert(false && + "CTOR only suited for type: 'ompt_callback_target_data_op'"); + + ompt_record_target_data_op_t Subrecord{ + HostOpId, OpType, SrcAddr, SrcDeviceNum, DstAddr, + DstDeviceNum, Bytes, Timeframe.second, CodeptrRA}; + + ompt_record_ompt_t *RecordPtr = + (ompt_record_ompt_t *)malloc(sizeof(ompt_record_ompt_t)); + memset(RecordPtr, 0, sizeof(ompt_record_ompt_t)); + RecordPtr->type = Type; + RecordPtr->time = Timeframe.first; + RecordPtr->thread_id = expectedDefault(ompt_id_t); + RecordPtr->target_id = TargetId; + RecordPtr->record.target_data_op = Subrecord; + + return OmptAssertEvent(EName, EGroup, Expected, + new internal::BufferRecord(RecordPtr)); +} + +OmptAssertEvent OmptAssertEvent::BufferRecord( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, + ompt_target_data_op_t OpType, size_t Bytes, + ompt_device_time_t MinimumTimeDelta, void *SrcAddr, void *DstAddr, + int SrcDeviceNum, int DstDeviceNum, ompt_id_t TargetId, ompt_id_t HostOpId, + const void *CodeptrRA) { + return BufferRecord(Name, Group, Expected, Type, OpType, Bytes, + {MinimumTimeDelta, expectedDefault(ompt_device_time_t)}, + SrcAddr, DstAddr, SrcDeviceNum, DstDeviceNum, TargetId, + HostOpId, CodeptrRA); +} + +OmptAssertEvent OmptAssertEvent::BufferRecord( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, + std::pair Timeframe, + unsigned int RequestedNumTeams, unsigned int GrantedNumTeams, + ompt_id_t TargetId, ompt_id_t HostOpId) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + + bool isDefault = (Timeframe.first == expectedDefault(ompt_device_time_t)); + isDefault &= (Timeframe.second == expectedDefault(ompt_device_time_t)); + isDefault &= (RequestedNumTeams == expectedDefault(unsigned int)); + isDefault &= (GrantedNumTeams == expectedDefault(unsigned int)); + isDefault &= (TargetId == expectedDefault(ompt_id_t)); + isDefault &= (HostOpId == expectedDefault(ompt_id_t)); + + ompt_record_ompt_t *RecordPtr = + (ompt_record_ompt_t *)malloc(sizeof(ompt_record_ompt_t)); + memset(RecordPtr, 0, sizeof(ompt_record_ompt_t)); + RecordPtr->type = Type; + + // This handles the simplest occurrence of a device tracing record + // We can only check for Type -- since all other properties are set to default + if (isDefault) { + RecordPtr->time = expectedDefault(ompt_device_time_t); + RecordPtr->thread_id = expectedDefault(ompt_id_t); + RecordPtr->target_id = expectedDefault(ompt_id_t); + if (Type == ompt_callback_target) { + ompt_record_target_t Subrecord{expectedDefault(ompt_target_t), + expectedDefault(ompt_scope_endpoint_t), + expectedDefault(int), + expectedDefault(ompt_id_t), + expectedDefault(ompt_id_t), + expectedDefault(void *)}; + RecordPtr->record.target = Subrecord; + } + + if (Type == ompt_callback_target_data_op) { + ompt_record_target_data_op_t Subrecord{ + expectedDefault(ompt_id_t), expectedDefault(ompt_target_data_op_t), + expectedDefault(void *), expectedDefault(int), + expectedDefault(void *), expectedDefault(int), + expectedDefault(size_t), expectedDefault(ompt_device_time_t), + expectedDefault(void *)}; + RecordPtr->record.target_data_op = Subrecord; + } + + if (Type == ompt_callback_target_submit) { + ompt_record_target_kernel_t Subrecord{ + expectedDefault(ompt_id_t), expectedDefault(unsigned int), + expectedDefault(unsigned int), expectedDefault(ompt_device_time_t)}; + RecordPtr->record.target_kernel = Subrecord; + } + + return OmptAssertEvent(EName, EGroup, Expected, + new internal::BufferRecord(RecordPtr)); + } + + if (Type != ompt_callback_target_submit) + assert(false && "CTOR only suited for type: 'ompt_callback_target_submit'"); + + ompt_record_target_kernel_t Subrecord{HostOpId, RequestedNumTeams, + GrantedNumTeams, Timeframe.second}; + + RecordPtr->time = Timeframe.first; + RecordPtr->thread_id = expectedDefault(ompt_id_t); + RecordPtr->target_id = TargetId; + RecordPtr->record.target_kernel = Subrecord; + + return OmptAssertEvent(EName, EGroup, Expected, + new internal::BufferRecord(RecordPtr)); +} + +OmptAssertEvent OmptAssertEvent::BufferRecord( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_callbacks_t Type, + ompt_device_time_t MinimumTimeDelta, unsigned int RequestedNumTeams, + unsigned int GrantedNumTeams, ompt_id_t TargetId, ompt_id_t HostOpId) { + return BufferRecord(Name, Group, Expected, Type, + {MinimumTimeDelta, expectedDefault(ompt_device_time_t)}, + RequestedNumTeams, GrantedNumTeams, TargetId, HostOpId); +} + +OmptAssertEvent OmptAssertEvent::BufferRecordDeallocation( + const std::string &Name, const std::string &Group, + const ObserveState &Expected, ompt_buffer_t *Buffer) { + auto EName = getName(Name); + auto EGroup = getGroup(Group); + return OmptAssertEvent(EName, EGroup, Expected, + new internal::BufferRecordDeallocation(Buffer)); +} + +std::string OmptAssertEvent::getEventName() const { return Name; } + +std::string OmptAssertEvent::getEventGroup() const { return Group; } + +ObserveState OmptAssertEvent::getEventExpectedState() const { + return ExpectedState; +} + +internal::EventTy OmptAssertEvent::getEventType() const { + return TheEvent->getType(); +} + +internal::InternalEvent *OmptAssertEvent::getEvent() const { + return TheEvent.get(); +} + +std::string OmptAssertEvent::toString(bool PrefixEventName) const { + std::string S; + if (PrefixEventName) + S.append(getEventName()).append(": "); + S.append((TheEvent == nullptr) ? "OmptAssertEvent" : TheEvent->toString()); + return S; +} + +bool omptest::operator==(const OmptAssertEvent &A, const OmptAssertEvent &B) { + assert(A.TheEvent.get() != nullptr && "A is valid"); + assert(B.TheEvent.get() != nullptr && "B is valid"); + + return A.TheEvent->getType() == B.TheEvent->getType() && + A.TheEvent->equals(B.TheEvent.get()); +} diff --git a/openmp/tools/omptest/src/OmptAsserter.cpp b/openmp/tools/omptest/src/OmptAsserter.cpp new file mode 100644 index 0000000000000..1c2f2dee69e16 --- /dev/null +++ b/openmp/tools/omptest/src/OmptAsserter.cpp @@ -0,0 +1,480 @@ +//===- 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->getType()) { + 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: + assert(false && "Encountered invalid event type"); + } + + 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->eventMismatch(Events[NextEvent], AE, + "[OmptSequencedAsserter] The events are not equal"); + 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->eventMismatch( + AE, + "[OmptSequencedAsserter] Encountered SyncPoint while still awaiting " + + std::to_string(NumRemainingEvents) + " events. Asserted " + + std::to_string(NumSuccessfulAsserts) + "/" + + std::to_string(Events.size()) + " events successfully."); + 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->eventMismatch( + AE, "[OmptSequencedAsserter] Too many events to check (" + + std::to_string(NumNotifications) + "). Asserted " + + std::to_string(NumSuccessfulAsserts) + "/" + + std::to_string(Events.size()) + " events successfully."); + 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->eventMismatch(E, AE, + "[OmptSequencedAsserter] Encountered forbidden event"); + 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->eventMismatch(E, "[OmptSequencedAsserter] Expected event was not " + "encountered (Remaining events: " + + std::to_string(getRemainingEventCount()) + ")"); + 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->eventMismatch( + AE, "[OmptEventAsserter] Encountered SyncPoint while still awaiting " + + std::to_string(NumRemainingEvents) + " events. Asserted " + + std::to_string(NumSuccessfulAsserts) + " events successfully."); + 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->eventMismatch(E, AE, + "[OmptEventAsserter] Encountered forbidden event"); + State = AssertState::fail; + } + return; + } + } + + if (OperationMode == AssertMode::strict) { + Log->eventMismatch(AE, "[OmptEventAsserter] Too many events to check (" + + std::to_string(NumNotifications) + + "). Asserted " + + std::to_string(NumSuccessfulAsserts) + + " events successfully. (Remaining events: " + + std::to_string(getRemainingEventCount()) + ")"); + State = AssertState::fail; + return; + } +} + +size_t OmptEventAsserter::getRemainingEventCount() { + // size_t EventCount = std::count_if(Events.begin(), Events.end(), [](const + // omptest::OmptAssertEvent &E) { return E.getEventExpectedState() == + // ObserveState::always; }); + 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->eventMismatch(E, "[OmptEventAsserter] Expected event was not " + "encountered (Remaining events: " + + std::to_string(getRemainingEventCount()) + ")"); + 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); +} diff --git a/openmp/tools/omptest/src/OmptCallbackHandler.cpp b/openmp/tools/omptest/src/OmptCallbackHandler.cpp new file mode 100644 index 0000000000000..0794a1c27a902 --- /dev/null +++ b/openmp/tools/omptest/src/OmptCallbackHandler.cpp @@ -0,0 +1,445 @@ +//===- OmptCallbackHandler.cpp - OMPT Callback handling impl. ---*- 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 +/// This file contains the OMPT callback handling implementations. +/// +//===----------------------------------------------------------------------===// + +#include "OmptCallbackHandler.h" + +using namespace omptest; + +OmptCallbackHandler *Handler = nullptr; + +OmptCallbackHandler &OmptCallbackHandler::get() { + if (Handler == nullptr) + Handler = new OmptCallbackHandler(); + + return *Handler; +} + +void OmptCallbackHandler::subscribe(OmptListener *Listener) { + Subscribers.push_back(Listener); +} + +void OmptCallbackHandler::clearSubscribers() { + replay(); + + Subscribers.clear(); +} + +void OmptCallbackHandler::replay() { + if (!RecordAndReplay) + return; + + for (auto &E : RecordedEvents) + for (const auto &S : Subscribers) + S->notify(std::move(E)); +} + +void OmptCallbackHandler::handleThreadBegin(ompt_thread_t ThreadType, + ompt_data_t *ThreadData) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::ThreadBegin( + "Thread Begin", "", ObserveState::generated, ThreadType)); + return; + } + + // Initial thread event likely to preceed assertion registration, so skip + if (ThreadType == ompt_thread_initial) + return; + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::ThreadBegin( + "Thread Begin", "", ObserveState::generated, ThreadType)); +} + +void OmptCallbackHandler::handleThreadEnd(ompt_data_t *ThreadData) { + if (RecordAndReplay) { + recordEvent( + OmptAssertEvent::ThreadEnd("Thread End", "", ObserveState::generated)); + return; + } + + for (const auto &S : Subscribers) + S->notify( + OmptAssertEvent::ThreadEnd("Thread End", "", ObserveState::generated)); +} + +void OmptCallbackHandler::handleTaskCreate( + ompt_data_t *EncounteringTaskData, + const ompt_frame_t *EncounteringTaskFrame, ompt_data_t *NewTaskData, + int Flags, int HasDependences, const void *CodeptrRA) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::TaskCreate( + "Task Create", "", ObserveState::generated, EncounteringTaskData, + EncounteringTaskFrame, NewTaskData, Flags, HasDependences, CodeptrRA)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::TaskCreate( + "Task Create", "", ObserveState::generated, EncounteringTaskData, + EncounteringTaskFrame, NewTaskData, Flags, HasDependences, CodeptrRA)); +} + +void OmptCallbackHandler::handleTaskSchedule(ompt_data_t *PriorTaskData, + ompt_task_status_t PriorTaskStatus, + ompt_data_t *NextTaskData) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::TaskSchedule("Task Schedule", "", + ObserveState::generated)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::TaskSchedule("Task Schedule", "", + ObserveState::generated)); +} + +void OmptCallbackHandler::handleImplicitTask(ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData, + ompt_data_t *TaskData, + unsigned int ActualParallelism, + unsigned int Index, int Flags) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::ImplicitTask( + "Implicit Task", "", ObserveState::generated, Endpoint, ParallelData, + TaskData, ActualParallelism, Index, Flags)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::ImplicitTask( + "Implicit Task", "", ObserveState::generated, Endpoint, ParallelData, + TaskData, ActualParallelism, Index, Flags)); +} + +void OmptCallbackHandler::handleParallelBegin( + ompt_data_t *EncounteringTaskData, + const ompt_frame_t *EncounteringTaskFrame, ompt_data_t *ParallelData, + unsigned int RequestedParallelism, int Flags, const void *CodeptrRA) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::ParallelBegin( + "Parallel Begin", "", ObserveState::generated, RequestedParallelism)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::ParallelBegin( + "Parallel Begin", "", ObserveState::generated, RequestedParallelism)); +} + +void OmptCallbackHandler::handleParallelEnd(ompt_data_t *ParallelData, + ompt_data_t *EncounteringTaskData, + int Flags, const void *CodeptrRA) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::ParallelEnd("Parallel End", "", + ObserveState::generated)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::ParallelEnd("Parallel End", "", + ObserveState::generated)); +} + +void OmptCallbackHandler::handleDeviceInitialize( + int DeviceNum, const char *Type, ompt_device_t *Device, + ompt_function_lookup_t LookupFn, const char *DocumentationStr) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::DeviceInitialize( + "Device Inititalize", "", ObserveState::generated, DeviceNum, Type, + Device, LookupFn, DocumentationStr)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::DeviceInitialize( + "Device Inititalize", "", ObserveState::generated, DeviceNum, Type, + Device, LookupFn, DocumentationStr)); +} + +void OmptCallbackHandler::handleDeviceFinalize(int DeviceNum) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::DeviceFinalize( + "Device Finalize", "", ObserveState::generated, DeviceNum)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::DeviceFinalize( + "Device Finalize", "", ObserveState::generated, DeviceNum)); +} + +void OmptCallbackHandler::handleTarget(ompt_target_t Kind, + ompt_scope_endpoint_t Endpoint, + int DeviceNum, ompt_data_t *TaskData, + ompt_id_t TargetId, + const void *CodeptrRA) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::Target("Target", "", ObserveState::generated, + Kind, Endpoint, DeviceNum, TaskData, + TargetId, CodeptrRA)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::Target("Target", "", ObserveState::generated, + Kind, Endpoint, DeviceNum, TaskData, + TargetId, CodeptrRA)); +} + +void OmptCallbackHandler::handleTargetEmi(ompt_target_t Kind, + ompt_scope_endpoint_t Endpoint, + int DeviceNum, ompt_data_t *TaskData, + ompt_data_t *TargetTaskData, + ompt_data_t *TargetData, + const void *CodeptrRA) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::TargetEmi( + "Target EMI", "", ObserveState::generated, Kind, Endpoint, DeviceNum, + TaskData, TargetTaskData, TargetData, CodeptrRA)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::TargetEmi( + "Target EMI", "", ObserveState::generated, Kind, Endpoint, DeviceNum, + TaskData, TargetTaskData, TargetData, CodeptrRA)); +} + +void OmptCallbackHandler::handleTargetSubmit(ompt_id_t TargetId, + ompt_id_t HostOpId, + unsigned int RequestedNumTeams) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::TargetSubmit("Target Submit", "", + ObserveState::generated, TargetId, + HostOpId, RequestedNumTeams)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::TargetSubmit("Target Submit", "", + ObserveState::generated, TargetId, + HostOpId, RequestedNumTeams)); +} + +void OmptCallbackHandler::handleTargetSubmitEmi( + ompt_scope_endpoint_t Endpoint, ompt_data_t *TargetData, + ompt_id_t *HostOpId, unsigned int RequestedNumTeams) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::TargetSubmitEmi( + "Target Submit EMI", "", ObserveState::generated, Endpoint, TargetData, + HostOpId, RequestedNumTeams)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::TargetSubmitEmi( + "Target Submit EMI", "", ObserveState::generated, Endpoint, TargetData, + HostOpId, RequestedNumTeams)); +} + +void OmptCallbackHandler::handleTargetDataOp( + ompt_id_t TargetId, ompt_id_t HostOpId, ompt_target_data_op_t OpType, + void *SrcAddr, int SrcDeviceNum, void *DstAddr, int DstDeviceNum, + size_t Bytes, const void *CodeptrRA) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::TargetDataOp( + "Target Data Op", "", ObserveState::generated, TargetId, HostOpId, + OpType, SrcAddr, SrcDeviceNum, DstAddr, DstDeviceNum, Bytes, + CodeptrRA)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::TargetDataOp( + "Target Data Op", "", ObserveState::generated, TargetId, HostOpId, + OpType, SrcAddr, SrcDeviceNum, DstAddr, DstDeviceNum, Bytes, + CodeptrRA)); +} + +void OmptCallbackHandler::handleTargetDataOpEmi( + ompt_scope_endpoint_t Endpoint, ompt_data_t *TargetTaskData, + ompt_data_t *TargetData, ompt_id_t *HostOpId, ompt_target_data_op_t OpType, + void *SrcAddr, int SrcDeviceNum, void *DstAddr, int DstDeviceNum, + size_t Bytes, const void *CodeptrRA) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::TargetDataOpEmi( + "Target Data Op EMI", "", ObserveState::generated, Endpoint, + TargetTaskData, TargetData, HostOpId, OpType, SrcAddr, SrcDeviceNum, + DstAddr, DstDeviceNum, Bytes, CodeptrRA)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::TargetDataOpEmi( + "Target Data Op EMI", "", ObserveState::generated, Endpoint, + TargetTaskData, TargetData, HostOpId, OpType, SrcAddr, SrcDeviceNum, + DstAddr, DstDeviceNum, Bytes, CodeptrRA)); +} + +void OmptCallbackHandler::handleDeviceLoad(int DeviceNum, const char *Filename, + int64_t OffsetInFile, + void *VmaInFile, size_t Bytes, + void *HostAddr, void *DeviceAddr, + uint64_t ModuleId) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::DeviceLoad( + "Device Load", "", ObserveState::generated, DeviceNum, Filename, + OffsetInFile, VmaInFile, Bytes, HostAddr, DeviceAddr, ModuleId)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::DeviceLoad( + "Device Load", "", ObserveState::generated, DeviceNum, Filename, + OffsetInFile, VmaInFile, Bytes, HostAddr, DeviceAddr, ModuleId)); +} + +void OmptCallbackHandler::handleDeviceUnload(int DeviceNum, uint64_t ModuleId) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::DeviceUnload("Device Unload", "", + ObserveState::generated)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::DeviceUnload("Device Unload", "", + ObserveState::generated)); +} + +void OmptCallbackHandler::handleBufferRequest(int DeviceNum, + ompt_buffer_t **Buffer, + size_t *Bytes) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::BufferRequest("Buffer Request", "", + ObserveState::generated, + DeviceNum, Buffer, Bytes)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::BufferRequest("Buffer Request", "", + ObserveState::generated, DeviceNum, + Buffer, Bytes)); +} + +void OmptCallbackHandler::handleBufferComplete(int DeviceNum, + ompt_buffer_t *Buffer, + size_t Bytes, + ompt_buffer_cursor_t Begin, + int BufferOwned) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::BufferComplete( + "Buffer Complete", "", ObserveState::generated, DeviceNum, Buffer, + Bytes, Begin, BufferOwned)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::BufferComplete( + "Buffer Complete", "", ObserveState::generated, DeviceNum, Buffer, + Bytes, Begin, BufferOwned)); +} + +void OmptCallbackHandler::handleBufferRecord(ompt_record_ompt_t *Record) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::BufferRecord("Buffer Record", "", + ObserveState::generated, Record)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::BufferRecord("Buffer Record", "", + ObserveState::generated, Record)); +} + +void OmptCallbackHandler::handleBufferRecordDeallocation( + ompt_buffer_t *Buffer) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::BufferRecordDeallocation( + "Buffer Deallocation", "", ObserveState::generated, Buffer)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::BufferRecordDeallocation( + "Buffer Deallocation", "", ObserveState::generated, Buffer)); +} + +void OmptCallbackHandler::handleWork(ompt_work_t WorkType, + ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData, + ompt_data_t *TaskData, uint64_t Count, + const void *CodeptrRA) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::Work("Work", "", ObserveState::generated, + WorkType, Endpoint, ParallelData, + TaskData, Count, CodeptrRA)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::Work("Work", "", ObserveState::generated, + WorkType, Endpoint, ParallelData, TaskData, + Count, CodeptrRA)); +} + +void OmptCallbackHandler::handleSyncRegion(ompt_sync_region_t Kind, + ompt_scope_endpoint_t Endpoint, + ompt_data_t *ParallelData, + ompt_data_t *TaskData, + const void *CodeptrRA) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::SyncRegion( + "SyncRegion", "", ObserveState::generated, Kind, Endpoint, ParallelData, + TaskData, CodeptrRA)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::SyncRegion( + "SyncRegion", "", ObserveState::generated, Kind, Endpoint, ParallelData, + TaskData, CodeptrRA)); +} + +void OmptCallbackHandler::handleDispatch(ompt_data_t *ParallelData, + ompt_data_t *TaskData, + ompt_dispatch_t Kind, + ompt_data_t Instance) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::Dispatch("Dispatch", "", + ObserveState::generated, ParallelData, + TaskData, Kind, Instance)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::Dispatch("Dispatch", "", ObserveState::generated, + ParallelData, TaskData, Kind, + Instance)); +} + +void OmptCallbackHandler::handleAssertionSyncPoint( + const std::string &SyncPointName) { + if (RecordAndReplay) { + recordEvent(OmptAssertEvent::AssertionSyncPoint( + "Assertion SyncPoint", "", ObserveState::generated, SyncPointName)); + return; + } + + for (const auto &S : Subscribers) + S->notify(OmptAssertEvent::AssertionSyncPoint( + "Assertion SyncPoint", "", ObserveState::generated, SyncPointName)); +} + +void OmptCallbackHandler::recordEvent(OmptAssertEvent &&Event) { + RecordedEvents.emplace_back(std::forward(Event)); +} diff --git a/openmp/tools/omptest/src/OmptTester.cpp b/openmp/tools/omptest/src/OmptTester.cpp new file mode 100644 index 0000000000000..22de91046fbdc --- /dev/null +++ b/openmp/tools/omptest/src/OmptTester.cpp @@ -0,0 +1,504 @@ +//===- OmptTester.cpp - ompTest OMPT tool implementation --------*- 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 +/// This file represents the core implementation file for the ompTest library. +/// It provides the actual OMPT tool implementation: registers callbacks, etc. +/// OMPT callbacks are passed to their corresponding handler, which in turn +/// notifies all registered asserters. +/// +//===----------------------------------------------------------------------===// + +#include "OmptTester.h" + +#include +#include +#include +#include + +using namespace omptest; + +// Callback handler, which receives and relays OMPT callbacks +extern OmptCallbackHandler *Handler; + +// EventListener, which will actually print the OMPT events +static OmptEventReporter *EventReporter; + +// From openmp/runtime/test/ompt/callback.h +#define register_ompt_callback_t(name, type) \ + do { \ + type f_##name = &on_##name; \ + if (ompt_set_callback(name, (ompt_callback_t)f_##name) == ompt_set_never) \ + printf("0: Could not register callback '" #name "'\n"); \ + } while (0) + +#define register_ompt_callback(name) register_ompt_callback_t(name, name##_t) + +#define OMPT_BUFFER_REQUEST_SIZE 256 + +#ifdef OPENMP_LIBOMPTEST_BUILD_STANDALONE +std::map TestRegistrar::Tests; +#endif + +static std::atomic NextOpId{0x8000000000000001}; +static bool UseEMICallbacks = false; +static bool UseTracing = false; +static bool RunAsTestSuite = false; +static bool ColoredLog = false; + +// OMPT entry point handles +static ompt_set_trace_ompt_t ompt_set_trace_ompt = 0; +static ompt_start_trace_t ompt_start_trace = 0; +static ompt_flush_trace_t ompt_flush_trace = 0; +static ompt_stop_trace_t ompt_stop_trace = 0; +static ompt_get_record_ompt_t ompt_get_record_ompt = 0; +static ompt_advance_buffer_cursor_t ompt_advance_buffer_cursor = 0; +static ompt_get_record_type_t ompt_get_record_type_fn = 0; + +// OMPT device side tracing: Currently traced devices +typedef std::unordered_set OmptDeviceSetTy; +typedef std::unique_ptr OmptDeviceSetPtrTy; +static OmptDeviceSetPtrTy TracedDevices; + +// OMPT callbacks + +// Trace record callbacks +static void on_ompt_callback_buffer_request(int device_num, + ompt_buffer_t **buffer, + size_t *bytes) { + *bytes = OMPT_BUFFER_REQUEST_SIZE; + *buffer = malloc(*bytes); + OmptCallbackHandler::get().handleBufferRequest(device_num, buffer, bytes); +} + +// Note: This callback must handle a null begin cursor. Currently, +// ompt_get_record_ompt, print_record_ompt, and +// ompt_advance_buffer_cursor handle a null cursor. +static void on_ompt_callback_buffer_complete( + int device_num, ompt_buffer_t *buffer, + size_t bytes, /* bytes returned in this callback */ + ompt_buffer_cursor_t begin, int buffer_owned) { + OmptCallbackHandler::get().handleBufferComplete(device_num, buffer, bytes, + begin, buffer_owned); + + int Status = 1; + ompt_buffer_cursor_t CurrentPos = begin; + while (Status) { + ompt_record_ompt_t *Record = ompt_get_record_ompt(buffer, CurrentPos); + if (ompt_get_record_type_fn(buffer, CurrentPos) != ompt_record_ompt) { + printf("WARNING: received non-ompt type buffer object\n"); + } + // TODO: Sometimes it may happen that the retrieved record may be null?! + // Only handle non-null records + if (Record != nullptr) + OmptCallbackHandler::get().handleBufferRecord(Record); + Status = ompt_advance_buffer_cursor(/*device=*/NULL, buffer, bytes, + CurrentPos, &CurrentPos); + } + if (buffer_owned) { + OmptCallbackHandler::get().handleBufferRecordDeallocation(buffer); + free(buffer); + } +} + +static ompt_set_result_t set_trace_ompt(ompt_device_t *Device) { + if (!ompt_set_trace_ompt) + return ompt_set_error; + + if (UseEMICallbacks) { + ompt_set_trace_ompt(Device, /*enable=*/1, + /*etype=*/ompt_callback_target_emi); + ompt_set_trace_ompt(Device, /*enable=*/1, + /*etype=*/ompt_callback_target_data_op_emi); + ompt_set_trace_ompt(Device, /*enable=*/1, + /*etype=*/ompt_callback_target_submit_emi); + } else { + ompt_set_trace_ompt(Device, /*enable=*/1, /*etype=*/ompt_callback_target); + ompt_set_trace_ompt(Device, /*enable=*/1, + /*etype=*/ompt_callback_target_data_op); + ompt_set_trace_ompt(Device, /*enable=*/1, + /*etype=*/ompt_callback_target_submit); + } + + return ompt_set_always; +} + +/////// HOST-RELATED ////// + +static void on_ompt_callback_thread_begin(ompt_thread_t thread_type, + ompt_data_t *thread_data) { + OmptCallbackHandler::get().handleThreadBegin(thread_type, thread_data); +} + +static void on_ompt_callback_thread_end(ompt_data_t *thread_data) { + OmptCallbackHandler::get().handleThreadEnd(thread_data); +} + +static void on_ompt_callback_parallel_begin( + ompt_data_t *encountering_task_data, + const ompt_frame_t *encountering_task_frame, ompt_data_t *parallel_data, + unsigned int requested_parallelism, int flags, const void *codeptr_ra) { + OmptCallbackHandler::get().handleParallelBegin( + encountering_task_data, encountering_task_frame, parallel_data, + requested_parallelism, flags, codeptr_ra); +} + +static void on_ompt_callback_parallel_end(ompt_data_t *parallel_data, + ompt_data_t *encountering_task_data, + int flags, const void *codeptr_ra) { + OmptCallbackHandler::get().handleParallelEnd( + parallel_data, encountering_task_data, flags, codeptr_ra); +} + +static void +on_ompt_callback_task_create(ompt_data_t *encountering_task_data, + const ompt_frame_t *encountering_task_frame, + ompt_data_t *new_task_data, int flags, + int has_dependences, const void *codeptr_ra) { + OmptCallbackHandler::get().handleTaskCreate( + encountering_task_data, encountering_task_frame, new_task_data, flags, + has_dependences, codeptr_ra); +} + +static void on_ompt_callback_task_schedule(ompt_data_t *prior_task_data, + ompt_task_status_t prior_task_status, + ompt_data_t *next_task_data) { + OmptCallbackHandler::get().handleTaskSchedule( + prior_task_data, prior_task_status, next_task_data); +} + +static void on_ompt_callback_implicit_task(ompt_scope_endpoint_t endpoint, + ompt_data_t *parallel_data, + ompt_data_t *task_data, + unsigned int actual_parallelism, + unsigned int index, int flags) { + OmptCallbackHandler::get().handleImplicitTask( + endpoint, parallel_data, task_data, actual_parallelism, index, flags); +} + +// Callbacks as of Table 19.4, which are not considered required for a minimal +// conforming OMPT implementation. +static void on_ompt_callback_work(ompt_work_t work_type, + ompt_scope_endpoint_t endpoint, + ompt_data_t *parallel_data, + ompt_data_t *task_data, uint64_t count, + const void *codeptr_ra) { + OmptCallbackHandler::get().handleWork(work_type, endpoint, parallel_data, + task_data, count, codeptr_ra); +} + +static void on_ompt_callback_dispatch(ompt_data_t *parallel_data, + ompt_data_t *task_data, + ompt_dispatch_t kind, + ompt_data_t instance) { + OmptCallbackHandler::get().handleDispatch(parallel_data, task_data, kind, + instance); +} + +static void on_ompt_callback_sync_region(ompt_sync_region_t kind, + ompt_scope_endpoint_t endpoint, + ompt_data_t *parallel_data, + ompt_data_t *task_data, + const void *codeptr_ra) { + OmptCallbackHandler::get().handleSyncRegion(kind, endpoint, parallel_data, + task_data, codeptr_ra); +} + +/////// DEVICE-RELATED ////// + +// Synchronous callbacks +static void on_ompt_callback_device_initialize(int device_num, const char *type, + ompt_device_t *device, + ompt_function_lookup_t lookup, + const char *documentation) { + OmptCallbackHandler::get().handleDeviceInitialize(device_num, type, device, + lookup, documentation); + if (!UseTracing) + return; + + if (!lookup) { + printf("Trace collection disabled on device %d\n", device_num); + return; + } + + ompt_set_trace_ompt = (ompt_set_trace_ompt_t)lookup("ompt_set_trace_ompt"); + ompt_start_trace = (ompt_start_trace_t)lookup("ompt_start_trace"); + ompt_flush_trace = (ompt_flush_trace_t)lookup("ompt_flush_trace"); + ompt_stop_trace = (ompt_stop_trace_t)lookup("ompt_stop_trace"); + ompt_get_record_ompt = (ompt_get_record_ompt_t)lookup("ompt_get_record_ompt"); + ompt_advance_buffer_cursor = + (ompt_advance_buffer_cursor_t)lookup("ompt_advance_buffer_cursor"); + + ompt_get_record_type_fn = + (ompt_get_record_type_t)lookup("ompt_get_record_type"); + if (!ompt_get_record_type_fn) { + printf("WARNING: No function ompt_get_record_type found in device " + "callbacks\n"); + } + + static bool IsDeviceMapInitialized = false; + if (!IsDeviceMapInitialized) { + TracedDevices = std::make_unique(); + IsDeviceMapInitialized = true; + } + + set_trace_ompt(device); + + // In many scenarios, this will be a good place to start the + // trace. If start_trace is called from the main program before this + // callback is dispatched, the start_trace handle will be null. This + // is because this device_init callback is invoked during the first + // target construct implementation. + + start_trace(device); +} + +static void on_ompt_callback_device_finalize(int device_num) { + OmptCallbackHandler::get().handleDeviceFinalize(device_num); +} + +static void on_ompt_callback_device_load(int device_num, const char *filename, + int64_t offset_in_file, + void *vma_in_file, size_t bytes, + void *host_addr, void *device_addr, + uint64_t module_id) { + OmptCallbackHandler::get().handleDeviceLoad( + device_num, filename, offset_in_file, vma_in_file, bytes, host_addr, + device_addr, module_id); +} + +static void on_ompt_callback_device_unload(int device_num, uint64_t module_id) { + OmptCallbackHandler::get().handleDeviceUnload(device_num, module_id); +} + +static void on_ompt_callback_target_data_op( + ompt_id_t target_id, ompt_id_t host_op_id, ompt_target_data_op_t optype, + void *src_addr, int src_device_num, void *dest_addr, int dest_device_num, + size_t bytes, const void *codeptr_ra) { + OmptCallbackHandler::get().handleTargetDataOp( + target_id, host_op_id, optype, src_addr, src_device_num, dest_addr, + dest_device_num, bytes, codeptr_ra); +} + +static void on_ompt_callback_target(ompt_target_t kind, + ompt_scope_endpoint_t endpoint, + int device_num, ompt_data_t *task_data, + ompt_id_t target_id, + const void *codeptr_ra) { + OmptCallbackHandler::get().handleTarget(kind, endpoint, device_num, task_data, + target_id, codeptr_ra); +} + +static void on_ompt_callback_target_submit(ompt_id_t target_id, + ompt_id_t host_op_id, + unsigned int requested_num_teams) { + OmptCallbackHandler::get().handleTargetSubmit(target_id, host_op_id, + requested_num_teams); +} + +static void on_ompt_callback_target_data_op_emi( + ompt_scope_endpoint_t endpoint, ompt_data_t *target_task_data, + ompt_data_t *target_data, ompt_id_t *host_op_id, + ompt_target_data_op_t optype, void *src_addr, int src_device_num, + void *dest_addr, int dest_device_num, size_t bytes, + const void *codeptr_ra) { + assert(codeptr_ra != 0 && "Unexpected null codeptr"); + // Both src and dest must not be null + // However, for omp_target_alloc only the END call holds a value for one of + // the two entries + if (optype != ompt_target_data_alloc) + assert((src_addr != 0 || dest_addr != 0) && "Both src and dest addr null"); + if (endpoint == ompt_scope_begin) + *host_op_id = NextOpId.fetch_add(1, std::memory_order_relaxed); + OmptCallbackHandler::get().handleTargetDataOpEmi( + endpoint, target_task_data, target_data, host_op_id, optype, src_addr, + src_device_num, dest_addr, dest_device_num, bytes, codeptr_ra); +} + +static void on_ompt_callback_target_emi(ompt_target_t kind, + ompt_scope_endpoint_t endpoint, + int device_num, ompt_data_t *task_data, + ompt_data_t *target_task_data, + ompt_data_t *target_data, + const void *codeptr_ra) { + assert(codeptr_ra != 0 && "Unexpected null codeptr"); + if (endpoint == ompt_scope_begin) + target_data->value = NextOpId.fetch_add(1, std::memory_order_relaxed); + OmptCallbackHandler::get().handleTargetEmi(kind, endpoint, device_num, + task_data, target_task_data, + target_data, codeptr_ra); +} + +static void on_ompt_callback_target_submit_emi( + ompt_scope_endpoint_t endpoint, ompt_data_t *target_data, + ompt_id_t *host_op_id, unsigned int requested_num_teams) { + OmptCallbackHandler::get().handleTargetSubmitEmi( + endpoint, target_data, host_op_id, requested_num_teams); +} + +static void on_ompt_callback_target_map(ompt_id_t target_id, + unsigned int nitems, void **host_addr, + void **device_addr, size_t *bytes, + unsigned int *mapping_flags, + const void *codeptr_ra) { + assert(0 && "Target map callback is unimplemented"); +} + +static void on_ompt_callback_target_map_emi(ompt_data_t *target_data, + unsigned int nitems, + void **host_addr, + void **device_addr, size_t *bytes, + unsigned int *mapping_flags, + const void *codeptr_ra) { + assert(0 && "Target map emi callback is unimplemented"); +} + +/// Load the value of a given boolean environmental variable. +bool getBoolEnvironmentVariable(const char *VariableName) { + if (VariableName == nullptr) + return false; + if (const char *EnvValue = std::getenv(VariableName)) { + std::string S{EnvValue}; + for (auto &C : S) + C = (char)std::tolower(C); + if (S == "1" || S == "on" || S == "true" || S == "yes") + return true; + } + return false; +} + +/// Called by the OMP runtime to initialize the OMPT +int ompt_initialize(ompt_function_lookup_t lookup, int initial_device_num, + ompt_data_t *tool_data) { + ompt_set_callback_t ompt_set_callback = nullptr; + ompt_set_callback = (ompt_set_callback_t)lookup("ompt_set_callback"); + if (!ompt_set_callback) + return 0; // failure + + UseEMICallbacks = getBoolEnvironmentVariable("OMPTEST_USE_OMPT_EMI"); + UseTracing = getBoolEnvironmentVariable("OMPTEST_USE_OMPT_TRACING"); + RunAsTestSuite = getBoolEnvironmentVariable("OMPTEST_RUN_AS_TESTSUITE"); + ColoredLog = getBoolEnvironmentVariable("OMPTEST_LOG_COLORED"); + + register_ompt_callback(ompt_callback_thread_begin); + register_ompt_callback(ompt_callback_thread_end); + register_ompt_callback(ompt_callback_parallel_begin); + register_ompt_callback(ompt_callback_parallel_end); + register_ompt_callback(ompt_callback_work); + // register_ompt_callback(ompt_callback_dispatch); + register_ompt_callback(ompt_callback_task_create); + // register_ompt_callback(ompt_callback_dependences); + // register_ompt_callback(ompt_callback_task_dependence); + register_ompt_callback(ompt_callback_task_schedule); + register_ompt_callback(ompt_callback_implicit_task); + // register_ompt_callback(ompt_callback_masked); + register_ompt_callback(ompt_callback_sync_region); + // register_ompt_callback(ompt_callback_mutex_acquire); + // register_ompt_callback(ompt_callback_mutex); + // register_ompt_callback(ompt_callback_nestLock); + // register_ompt_callback(ompt_callback_flush); + // register_ompt_callback(ompt_callback_cancel); + register_ompt_callback(ompt_callback_device_initialize); + register_ompt_callback(ompt_callback_device_finalize); + register_ompt_callback(ompt_callback_device_load); + register_ompt_callback(ompt_callback_device_unload); + + if (UseEMICallbacks) { + register_ompt_callback(ompt_callback_target_emi); + register_ompt_callback(ompt_callback_target_submit_emi); + register_ompt_callback(ompt_callback_target_data_op_emi); + register_ompt_callback(ompt_callback_target_map_emi); + } else { + register_ompt_callback(ompt_callback_target); + register_ompt_callback(ompt_callback_target_submit); + register_ompt_callback(ompt_callback_target_data_op); + register_ompt_callback(ompt_callback_target_map); + } + + // Construct & subscribe the reporter, so it will be notified of events + EventReporter = new OmptEventReporter(); + OmptCallbackHandler::get().subscribe(EventReporter); + + if (RunAsTestSuite) + EventReporter->setActive(false); + + return 1; // success +} + +void ompt_finalize(ompt_data_t *tool_data) { + assert(Handler && "Callback handler should be present at this point"); + assert(EventReporter && "EventReporter should be present at this point"); + delete Handler; + delete EventReporter; +} + +#ifdef __cplusplus +extern "C" { +#endif +/// Called from the OMP Runtime to start / initialize the tool +ompt_start_tool_result_t *ompt_start_tool(unsigned int omp_version, + const char *runtime_version) { + static ompt_start_tool_result_t ompt_start_tool_result = { + &ompt_initialize, &ompt_finalize, {0}}; + return &ompt_start_tool_result; +} + +int start_trace(ompt_device_t *Device) { + if (!ompt_start_trace) + return 0; + + // This device will be traced + assert(TracedDevices->find(Device) == TracedDevices->end() && + "Device already present in the map"); + TracedDevices->insert(Device); + + return ompt_start_trace(Device, &on_ompt_callback_buffer_request, + &on_ompt_callback_buffer_complete); +} + +int flush_trace(ompt_device_t *Device) { + if (!ompt_flush_trace) + return 0; + return ompt_flush_trace(Device); +} + +int flush_traced_devices() { + if (!ompt_flush_trace || TracedDevices == nullptr) + return 0; + + size_t NumFlushedDevices = 0; + for (auto Device : *TracedDevices) + if (ompt_flush_trace(Device) == 1) + ++NumFlushedDevices; + + // Provide time to process triggered assert events + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + return (NumFlushedDevices == TracedDevices->size()); +} + +int stop_trace(ompt_device_t *Device) { + if (!ompt_stop_trace) + return 0; + + // This device will not be traced anymore + assert(TracedDevices->find(Device) != TracedDevices->end() && + "Device not present in the map"); + TracedDevices->erase(Device); + + return ompt_stop_trace(Device); +} + +// This is primarily used to stop unwanted prints from happening. +void libomptest_global_eventreporter_set_active(bool State) { + assert(EventReporter && "EventReporter should be present at this point"); + EventReporter->setActive(State); +} +#ifdef __cplusplus +} +#endif diff --git a/openmp/tools/omptest/src/OmptTesterStandalone.cpp b/openmp/tools/omptest/src/OmptTesterStandalone.cpp new file mode 100644 index 0000000000000..d4f68b4576536 --- /dev/null +++ b/openmp/tools/omptest/src/OmptTesterStandalone.cpp @@ -0,0 +1,147 @@ +//===- OmptTesterStandalone.cpp - Standalone unit testing impl. -*- 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 +/// This file represents the 'standalone' ompTest unit testing core +/// implementation, defining the general test suite and test case execution. +/// +//===----------------------------------------------------------------------===// + +#include "OmptTesterStandalone.h" +#include "OmptCallbackHandler.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace omptest; + +Error TestCase::exec() { + Error E; + E.Fail = false; + + if (IsDisabled) + return E; + + OmptCallbackHandler::get().subscribe(SequenceAsserter.get()); + OmptCallbackHandler::get().subscribe(SetAsserter.get()); + OmptCallbackHandler::get().subscribe(EventReporter.get()); + + execImpl(); + + // Actively flush potential in-flight trace records + flush_traced_devices(); + + // We remove subscribers to not be notified of events after our test case + // finished. + OmptCallbackHandler::get().clearSubscribers(); + omptest::AssertState SequenceResultState = SequenceAsserter->checkState(); + omptest::AssertState SetResultState = SetAsserter->checkState(); + bool AnyFail = SequenceResultState == omptest::AssertState::fail || + SetResultState == omptest::AssertState::fail; + bool AllPass = SequenceResultState == omptest::AssertState::pass && + SetResultState == omptest::AssertState::pass; + if (ExpectedState == omptest::AssertState::pass && AnyFail) + E.Fail = true; + else if (ExpectedState == omptest::AssertState::fail && AllPass) + E.Fail = true; + if (AnyFail) + ResultState = omptest::AssertState::fail; + return E; +} + +TestSuite::TestSuite(TestSuite &&O) { + Name = O.Name; + TestCases.swap(O.TestCases); +} + +void TestSuite::setup() {} + +void TestSuite::teardown() {} + +TestSuite::TestCaseVec::iterator TestSuite::begin() { + return TestCases.begin(); +} + +TestSuite::TestCaseVec::iterator TestSuite::end() { return TestCases.end(); } + +TestRegistrar &TestRegistrar::get() { + static TestRegistrar TR; + return TR; +} + +std::vector TestRegistrar::getTestSuites() { + std::vector TSs; + for (auto &[k, v] : Tests) + TSs.emplace_back(std::move(v)); + return TSs; +} + +void TestRegistrar::addCaseToSuite(TestCase *TC, std::string TSName) { + auto &TS = Tests[TSName]; + if (TS.Name.empty()) + TS.Name = TSName; + TS.TestCases.emplace_back(TC); +} + +Registerer::Registerer(TestCase *TC, const std::string SuiteName) { + std::cout << "Adding " << TC->Name << " to " << SuiteName << std::endl; + TestRegistrar::get().addCaseToSuite(TC, SuiteName); +} + +int Runner::run() { + int ErrorCount = 0; + for (auto &TS : TestSuites) { + std::cout << "\n======\nExecuting for " << TS.Name << std::endl; + TS.setup(); + for (auto &TC : TS) { + std::cout << "\nExecuting " << TC->Name << std::endl; + if (Error Err = TC->exec()) { + reportError(Err); + abortOrKeepGoing(); + ++ErrorCount; + } + } + TS.teardown(); + } + printSummary(); + return ErrorCount; +} + +void Runner::reportError(const Error &Err) {} + +void Runner::abortOrKeepGoing() {} + +void Runner::printSummary() { + std::cout << "\n====== SUMMARY\n"; + for (auto &TS : TestSuites) { + std::cout << " - " << TS.Name; + for (auto &TC : TS) { + std::string Result; + if (TC->IsDisabled) { + Result = "-#-#-"; + } else if (TC->ResultState == TC->ExpectedState) { + if (TC->ResultState == omptest::AssertState::pass) + Result = "PASS"; + else if (TC->ResultState == omptest::AssertState::fail) + Result = "XFAIL"; + } else { + if (TC->ResultState == omptest::AssertState::fail) + Result = "FAIL"; + else if (TC->ResultState == omptest::AssertState::pass) + Result = "UPASS"; + } + std::cout << "\n " << std::setw(5) << Result << " : " << TC->Name; + } + std::cout << std::endl; + } +} diff --git a/openmp/tools/omptest/test/CMakeLists.txt b/openmp/tools/omptest/test/CMakeLists.txt new file mode 100644 index 0000000000000..427893313cc67 --- /dev/null +++ b/openmp/tools/omptest/test/CMakeLists.txt @@ -0,0 +1,28 @@ +##===----------------------------------------------------------------------===## +# +# Add ompTest unit tests to check-openmp. +# +##===----------------------------------------------------------------------===## + +# Target: ompTest library unit tests +file(GLOB UNITTEST_SOURCES "unittests/*.cpp") +add_executable(omptest-unittests ${UNITTEST_SOURCES}) + +# Add local and LLVM-provided GoogleTest include directories. +target_include_directories(omptest-unittests PRIVATE + ../include + ${LLVM_THIRD_PARTY_DIR}/unittest/googletest/include) + +target_link_libraries(omptest-unittests PRIVATE omptest) + +set_target_properties(omptest-unittests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +# Add ompTest unit tests to check-openmp +add_openmp_testsuite(check-ompt-omptest "Running OMPT ompTest unit tests" + ${CMAKE_CURRENT_BINARY_DIR} DEPENDS omptest-unittests) + +# Configure the lit.site.cfg.in file +set(AUTO_GEN_COMMENT "## Autogenerated by OPENMP_TOOLS_OMPTEST_TEST " + "configuration.\n# Do not edit!") +configure_file(lit.site.cfg.in lit.site.cfg @ONLY) diff --git a/openmp/tools/omptest/test/lit.cfg b/openmp/tools/omptest/test/lit.cfg new file mode 100644 index 0000000000000..69c401aed83b8 --- /dev/null +++ b/openmp/tools/omptest/test/lit.cfg @@ -0,0 +1,26 @@ +# -*- Python -*- vim: set ft=python ts=4 sw=4 expandtab tw=79: +# Configuration file for the 'lit' test runner. + +import os +import lit.formats + +# Tell pylint that we know config and lit_config exist somewhere. +if 'PYLINT_IMPORT' in os.environ: + config = object() + lit_config = object() + +# name: The name of this test suite. +config.name = 'OMPT ompTest' + +# suffixes: A list of file extensions to treat as test files. +config.suffixes = [''] + +# test_source_root: The root path where tests are located. +config.test_source_root = config.test_obj_root + +# test_exec_root: The root object directory where output is placed +config.test_exec_root = config.test_obj_root + +# test format, match (omptest-)unittests +# Matched binaries (GoogleTests) are executed +config.test_format = lit.formats.GoogleTest(".", "unittests") diff --git a/openmp/tools/omptest/test/lit.site.cfg.in b/openmp/tools/omptest/test/lit.site.cfg.in new file mode 100644 index 0000000000000..4fa8c7e349681 --- /dev/null +++ b/openmp/tools/omptest/test/lit.site.cfg.in @@ -0,0 +1,9 @@ +@AUTO_GEN_COMMENT@ + +config.test_obj_root = "@CMAKE_CURRENT_BINARY_DIR@" + +import lit.llvm +lit.llvm.initialize(lit_config, config) + +# Let the main config do the real work. +lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg") diff --git a/openmp/tools/omptest/test/unittests/asserter-seq-test.cpp b/openmp/tools/omptest/test/unittests/asserter-seq-test.cpp new file mode 100644 index 0000000000000..34ceb7597b791 --- /dev/null +++ b/openmp/tools/omptest/test/unittests/asserter-seq-test.cpp @@ -0,0 +1,358 @@ +#include "OmptAliases.h" +#include "OmptAsserter.h" +#include +#include + +#include "gtest/gtest.h" + +using namespace omptest; +using OAE = omptest::OmptAssertEvent; +using OS = omptest::ObserveState; + +/// SequencedAsserter test-fixture class to avoid code duplication among tests. +class OmptSequencedAsserterTest : public testing::Test { +protected: + OmptSequencedAsserterTest() { + // Construct default sequenced asserter + SeqAsserter = std::make_unique(); + + // Silence all potential log prints + SeqAsserter->getLog()->setLoggingLevel(logging::Level::SILENT); + } + + std::unique_ptr SeqAsserter; +}; + +TEST_F(OmptSequencedAsserterTest, DefaultState) { + // Assertion should neither start as 'deactivated' nor 'suspended' + ASSERT_EQ(SeqAsserter->isActive(), true); + ASSERT_EQ(SeqAsserter->AssertionSuspended, false); + + // Assertion should begin with event ID zero + ASSERT_EQ(SeqAsserter->NextEvent, 0); + + // Assertion should begin without previous notifications or assertions + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 0); + + // There should be no expected events + ASSERT_EQ(SeqAsserter->Events.empty(), true); + ASSERT_EQ(SeqAsserter->getRemainingEventCount(), 0); + + // Default mode should be strict + ASSERT_NE(SeqAsserter->getOperationMode(), AssertMode::relaxed); + ASSERT_EQ(SeqAsserter->getOperationMode(), AssertMode::strict); + + // Default state should be passing + ASSERT_NE(SeqAsserter->getState(), AssertState::fail); + ASSERT_EQ(SeqAsserter->getState(), AssertState::pass); + ASSERT_NE(SeqAsserter->checkState(), AssertState::fail); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::pass); +} + +TEST_F(OmptSequencedAsserterTest, IgnoreNotificationsWhenEmpty) { + // ParallelBegin events are suppressed by default + auto SuppressedEvent = OAE::ParallelBegin( + /*Name=*/"ParBegin", /*Group=*/"", /*Expected=*/OS::always, + /*NumThreads=*/3); + + // DeviceFinalize events are not ignored by default + auto IgnoredEvent = OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/7); + + // Situation: There is nothing to assert. + // Result: All notifications are ignored. + // Hence, check that the perceived count of notifications remains unchanged + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + + SeqAsserter->notify(std::move(SuppressedEvent)); + + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + SeqAsserter->notify(std::move(IgnoredEvent)); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 0); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::pass); +} + +TEST_F(OmptSequencedAsserterTest, IgnoreNotificationsWhileDeactivated) { + auto ExpectedEvent = OAE::DeviceUnload( + /*Name=*/"DevUnload", /*Group=*/"", /*Expected=*/OS::always); + SeqAsserter->insert(std::move(ExpectedEvent)); + ASSERT_EQ(SeqAsserter->Events.empty(), false); + + // Deactivate asserter, effectively ignoring notifications + SeqAsserter->setActive(false); + ASSERT_EQ(SeqAsserter->isActive(), false); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + + // DeviceFinalize events are not ignored by default + auto IgnoredEvent = OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/7); + SeqAsserter->notify(std::move(IgnoredEvent)); + + // Assertion was deactivated: No change + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 0); + + SeqAsserter->setActive(true); + ASSERT_EQ(SeqAsserter->isActive(), true); + + auto ObservedEvent = OAE::DeviceUnload( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always); + SeqAsserter->notify(std::move(ObservedEvent)); + + // Assertion was activated, one notification expected + ASSERT_EQ(SeqAsserter->getNotificationCount(), 1); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 1); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::pass); +} + +TEST_F(OmptSequencedAsserterTest, AddEvent) { + ASSERT_EQ(SeqAsserter->getRemainingEventCount(), 0); + auto ExpectedEvent = OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/7); + SeqAsserter->insert(std::move(ExpectedEvent)); + // Sanity check: Notifications should not be triggered + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + // Adding an expected event must change the event count but not the state + ASSERT_EQ(SeqAsserter->getRemainingEventCount(), 1); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 0); + ASSERT_EQ(SeqAsserter->getState(), AssertState::pass); +} + +TEST_F(OmptSequencedAsserterTest, AddEventIgnoreSuppressed) { + auto ExpectedEvent = OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/7); + SeqAsserter->insert(std::move(ExpectedEvent)); + // ParallelBegin events are suppressed by default + auto SuppressedEvent = OAE::ParallelBegin( + /*Name=*/"ParBegin", /*Group=*/"", /*Expected=*/OS::always, + /*NumThreads=*/3); + // Situation: There is one expected event and ParallelBegins are suppressed. + // Notification count remains unchanged for suppressed events + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + SeqAsserter->notify(std::move(SuppressedEvent)); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 0); + ASSERT_EQ(SeqAsserter->getState(), AssertState::pass); +} + +TEST_F(OmptSequencedAsserterTest, AddEventObservePass) { + auto ExpectedEvent = OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/7); + SeqAsserter->insert(std::move(ExpectedEvent)); + // DeviceFinalize events are not ignored by default + auto ObservedEvent = OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/7); + SeqAsserter->notify(std::move(ObservedEvent)); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 1); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 1); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::pass); +} + +TEST_F(OmptSequencedAsserterTest, AddEventObserveFail) { + auto ExpectedEvent = OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/7); + SeqAsserter->insert(std::move(ExpectedEvent)); + // DeviceFinalize events are not ignored by default + // Provide wrong DeviceNum + auto ObservedEvent = OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/23); + + SeqAsserter->notify(std::move(ObservedEvent)); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 1); + // Observed and expected event do not match: Fail + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 0); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::fail); +} + +TEST_F(OmptSequencedAsserterTest, AddEventObserveDifferentType) { + auto ExpectedEvent = OAE::DeviceUnload( + /*Name=*/"DevUnload", /*Group=*/"", /*Expected=*/OS::always); + SeqAsserter->insert(std::move(ExpectedEvent)); + // DeviceFinalize events are not ignored by default + auto ObservedEvent = OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/7); + + SeqAsserter->notify(std::move(ObservedEvent)); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 1); + // Observed and expected event do not match: Fail + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 0); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::fail); +} + +TEST_F(OmptSequencedAsserterTest, CheckTargetGroupNoEffect) { + // Situation: Groups are designed to be used as an indicator -WITHIN- target + // regions. Hence, comparing two target regions w.r.t. their groups has no + // effect on pass or fail. + + auto ExpectedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, + /*DeviceNum=*/7, /*TaskData=*/nullptr, /*TargetId=*/23, + /*CodeptrRA=*/nullptr); + SeqAsserter->insert(std::move(ExpectedEvent)); + ASSERT_EQ(SeqAsserter->Events.empty(), false); + + // Deactivate asserter, effectively ignoring notifications + SeqAsserter->setActive(false); + ASSERT_EQ(SeqAsserter->isActive(), false); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + + // Target events are not ignored by default + auto ObservedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, /*DeviceNum=*/7, + /*TaskData=*/nullptr, /*TargetId=*/23, /*CodeptrRA=*/nullptr); + SeqAsserter->notify(std::move(ObservedEvent)); + + // Assertion was deactivated: No change + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 0); + ASSERT_EQ(SeqAsserter->getRemainingEventCount(), 1); + + // Re-activate asserter + SeqAsserter->setActive(true); + ASSERT_EQ(SeqAsserter->isActive(), true); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + + // Actually observe a target event from "AnotherGroup" + auto AnotherObservedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"AnotherGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, /*DeviceNum=*/7, + /*TaskData=*/nullptr, /*TargetId=*/23, /*CodeptrRA=*/nullptr); + SeqAsserter->notify(std::move(AnotherObservedEvent)); + + // Observed all expected events; groups of target regions do not affect pass + ASSERT_EQ(SeqAsserter->getNotificationCount(), 1); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 1); + ASSERT_EQ(SeqAsserter->getRemainingEventCount(), 0); + + ASSERT_EQ(SeqAsserter->checkState(), AssertState::pass); +} + +TEST_F(OmptSequencedAsserterTest, CheckSyncPoint) { + auto ExpectedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, + /*DeviceNum=*/7, /*TaskData=*/nullptr, /*TargetId=*/23, + /*CodeptrRA=*/nullptr); + SeqAsserter->insert(std::move(ExpectedEvent)); + ASSERT_EQ(SeqAsserter->Events.empty(), false); + ASSERT_EQ(SeqAsserter->getRemainingEventCount(), 1); + + // Target events are not ignored by default + auto ObservedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, /*DeviceNum=*/7, + /*TaskData=*/nullptr, /*TargetId=*/23, /*CodeptrRA=*/nullptr); + SeqAsserter->notify(std::move(ObservedEvent)); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 1); + + SeqAsserter->notify(OAE::AssertionSyncPoint( + /*Name=*/"", /*Group=*/"", /*Expected=*/OS::always, + /*SyncPointName=*/"SyncPoint 1")); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 2); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 1); + + // All events processed: SyncPoint "passes" + ASSERT_EQ(SeqAsserter->checkState(), AssertState::pass); + + auto AnotherExpectedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, + /*DeviceNum=*/7, /*TaskData=*/nullptr, /*TargetId=*/23, + /*CodeptrRA=*/nullptr); + + ASSERT_EQ(SeqAsserter->getRemainingEventCount(), 0); + SeqAsserter->insert(std::move(AnotherExpectedEvent)); + ASSERT_EQ(SeqAsserter->getRemainingEventCount(), 1); + + // Remaining events present: SyncPoint "fails" + SeqAsserter->notify(OAE::AssertionSyncPoint( + /*Name=*/"", /*Group=*/"", /*Expected=*/OS::always, + /*SyncPointName=*/"SyncPoint 2")); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::fail); +} + +TEST_F(OmptSequencedAsserterTest, CheckExcessNotify) { + auto ExpectedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, + /*DeviceNum=*/7, /*TaskData=*/nullptr, /*TargetId=*/23, + /*CodeptrRA=*/nullptr); + SeqAsserter->insert(std::move(ExpectedEvent)); + ASSERT_EQ(SeqAsserter->Events.empty(), false); + ASSERT_EQ(SeqAsserter->getRemainingEventCount(), 1); + + // Target events are not ignored by default + auto ObservedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, /*DeviceNum=*/7, + /*TaskData=*/nullptr, /*TargetId=*/23, /*CodeptrRA=*/nullptr); + SeqAsserter->notify(std::move(ObservedEvent)); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 1); + + // All events processed: pass + ASSERT_EQ(SeqAsserter->checkState(), AssertState::pass); + + // Target events are not ignored by default + auto AnotherObservedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, /*DeviceNum=*/7, + /*TaskData=*/nullptr, /*TargetId=*/23, /*CodeptrRA=*/nullptr); + + // No more events expected: notify "fails" + SeqAsserter->notify(std::move(AnotherObservedEvent)); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 2); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::fail); +} + +TEST_F(OmptSequencedAsserterTest, CheckSuspend) { + SeqAsserter->insert(OAE::AssertionSuspend( + /*Name=*/"", /*Group=*/"", /*Expected=*/OS::never)); + ASSERT_EQ(SeqAsserter->Events.empty(), false); + + // Being notified while the next expected event is a "suspend" should change + // the asserter's state + ASSERT_EQ(SeqAsserter->getNotificationCount(), 0); + ASSERT_EQ(SeqAsserter->AssertionSuspended, false); + SeqAsserter->notify(OAE::DeviceFinalize( + /*Name=*/"DevFini", /*Group=*/"", /*Expected=*/OS::always, + /*DeviceNum=*/7)); + ASSERT_EQ(SeqAsserter->AssertionSuspended, true); + ASSERT_EQ(SeqAsserter->getNotificationCount(), 1); + + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 0); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::pass); + + auto ExpectedEvent = OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, + /*DeviceNum=*/7, /*TaskData=*/nullptr, /*TargetId=*/23, + /*CodeptrRA=*/nullptr); + SeqAsserter->insert(std::move(ExpectedEvent)); + + // Being notified with an observed event, which matches the next expected + // event, resumes assertion (suspended = false) + ASSERT_EQ(SeqAsserter->AssertionSuspended, true); + SeqAsserter->notify(OAE::Target( + /*Name=*/"Target", /*Group=*/"MyTargetGroup", /*Expected=*/OS::always, + /*Kind=*/TARGET, /*Endpoint=*/BEGIN, + /*DeviceNum=*/7, /*TaskData=*/nullptr, /*TargetId=*/23, + /*CodeptrRA=*/nullptr)); + ASSERT_EQ(SeqAsserter->AssertionSuspended, false); + + ASSERT_EQ(SeqAsserter->getNotificationCount(), 2); + ASSERT_EQ(SeqAsserter->getSuccessfulAssertionCount(), 1); + ASSERT_EQ(SeqAsserter->checkState(), AssertState::pass); +} diff --git a/openmp/tools/omptest/test/unittests/internal-event-test.cpp b/openmp/tools/omptest/test/unittests/internal-event-test.cpp new file mode 100644 index 0000000000000..a53025460c7e0 --- /dev/null +++ b/openmp/tools/omptest/test/unittests/internal-event-test.cpp @@ -0,0 +1,530 @@ +#include "InternalEvent.h" +#include +#include + +#include "gtest/gtest.h" + +using namespace omptest; + +TEST(InternalEvent_toString, AssertionSyncPoint) { + internal::AssertionSyncPoint SP{/*Name=*/"Test Sync Point"}; + + EXPECT_EQ(SP.toString(), "Assertion SyncPoint: 'Test Sync Point'"); +} + +TEST(InternalEvent_toString, ThreadBegin) { + internal::ThreadBegin TB{/*ThreadType=*/ompt_thread_t::ompt_thread_initial}; + + EXPECT_EQ(TB.toString(), "OMPT Callback ThreadBegin: ThreadType=1"); +} + +TEST(InternalEvent_toString, ThreadEnd) { + internal::ThreadEnd TE{}; + + EXPECT_EQ(TE.toString(), "OMPT Callback ThreadEnd"); +} + +TEST(InternalEvent_toString, ParallelBegin) { + internal::ParallelBegin PB{/*NumThreads=*/31}; + + EXPECT_EQ(PB.toString(), "OMPT Callback ParallelBegin: NumThreads=31"); +} + +TEST(InternalEvent_toString, ParallelEnd) { + internal::ParallelEnd PE{/*ParallelData=*/(ompt_data_t *)0x11, + /*EncounteringTaskData=*/(ompt_data_t *)0x22, + /*Flags=*/31, + /*CodeptrRA=*/(const void *)0x33}; + + EXPECT_EQ(PE.toString(), "OMPT Callback ParallelEnd"); +} + +TEST(InternalEvent_toString, Work) { + internal::Work WK{/*WorkType=*/ompt_work_t::ompt_work_loop_dynamic, + /*Endpoint=*/ompt_scope_endpoint_t::ompt_scope_beginend, + /*ParallelData=*/(ompt_data_t *)0x11, + /*TaskData=*/(ompt_data_t *)0x22, + /*Count=*/31, + /*CodeptrRA=*/(const void *)0x33}; + + EXPECT_EQ(WK.toString(), + "OMPT Callback Work: work_type=11 endpoint=3 parallel_data=0x11 " + "task_data=0x22 count=31 codeptr=0x33"); +} + +TEST(InternalEvent_toString, Dispatch_iteration) { + ompt_data_t DI{.value = 31}; + internal::Dispatch D{/*ParallelData=*/(ompt_data_t *)0x11, + /*TaskData=*/(ompt_data_t *)0x22, + /*Kind=*/ompt_dispatch_t::ompt_dispatch_iteration, + /*Instance=*/DI}; + + EXPECT_EQ(D.toString(), "OMPT Callback Dispatch: parallel_data=0x11 " + "task_data=0x22 kind=1 instance=[it=31]"); +} + +TEST(InternalEvent_toString, Dispatch_section) { + ompt_data_t DI{.ptr = (void *)0x33}; + internal::Dispatch D{/*ParallelData=*/(ompt_data_t *)0x11, + /*TaskData=*/(ompt_data_t *)0x22, + /*Kind=*/ompt_dispatch_t::ompt_dispatch_section, + /*Instance=*/DI}; + + EXPECT_EQ(D.toString(), "OMPT Callback Dispatch: parallel_data=0x11 " + "task_data=0x22 kind=2 instance=[ptr=0x33]"); +} + +TEST(InternalEvent_toString, Dispatch_chunks) { + ompt_dispatch_chunk_t DC{.start = 7, .iterations = 31}; + ompt_data_t DI{.ptr = (void *)&DC}; + + internal::Dispatch DLoop{ + /*ParallelData=*/(ompt_data_t *)0x11, + /*TaskData=*/(ompt_data_t *)0x22, + /*Kind=*/ompt_dispatch_t::ompt_dispatch_ws_loop_chunk, + /*Instance=*/DI}; + + internal::Dispatch DTask{ + /*ParallelData=*/(ompt_data_t *)0x11, + /*TaskData=*/(ompt_data_t *)0x22, + /*Kind=*/ompt_dispatch_t::ompt_dispatch_taskloop_chunk, + /*Instance=*/DI}; + + internal::Dispatch DDist{ + /*ParallelData=*/(ompt_data_t *)0x11, + /*TaskData=*/(ompt_data_t *)0x22, + /*Kind=*/ompt_dispatch_t::ompt_dispatch_distribute_chunk, + /*Instance=*/DI}; + + ompt_data_t DINull{.ptr = nullptr}; + internal::Dispatch DDistNull{ + /*ParallelData=*/(ompt_data_t *)0x11, + /*TaskData=*/(ompt_data_t *)0x22, + /*Kind=*/ompt_dispatch_t::ompt_dispatch_distribute_chunk, + /*Instance=*/DINull}; + + EXPECT_EQ(DLoop.toString(), + "OMPT Callback Dispatch: parallel_data=0x11 " + "task_data=0x22 kind=3 instance=[chunk=(start=7, iterations=31)]"); + + EXPECT_EQ(DTask.toString(), + "OMPT Callback Dispatch: parallel_data=0x11 " + "task_data=0x22 kind=4 instance=[chunk=(start=7, iterations=31)]"); + + EXPECT_EQ(DDist.toString(), + "OMPT Callback Dispatch: parallel_data=0x11 " + "task_data=0x22 kind=5 instance=[chunk=(start=7, iterations=31)]"); + + EXPECT_EQ(DDistNull.toString(), "OMPT Callback Dispatch: parallel_data=0x11 " + "task_data=0x22 kind=5"); +} + +TEST(InternalEvent_toString, TaskCreate) { + internal::TaskCreate TC{/*EncounteringTaskData=*/(ompt_data_t *)0x11, + /*EncounteringTaskFrame=*/(const ompt_frame_t *)0x22, + /*NewTaskData=*/(ompt_data_t *)0x33, + /*Flags=*/7, + /*HasDependences=*/31, + /*CodeptrRA=*/(const void *)0x44}; + + EXPECT_EQ(TC.toString(), + "OMPT Callback TaskCreate: encountering_task_data=0x11 " + "encountering_task_frame=0x22 new_task_data=0x33 flags=7 " + "has_dependences=31 codeptr=0x44"); +} + +TEST(InternalEvent_toString, ImplicitTask) { + internal::ImplicitTask IT{ + /*Endpoint=*/ompt_scope_endpoint_t::ompt_scope_begin, + /*ParallelData=*/(ompt_data_t *)0x11, + /*TaskData=*/(ompt_data_t *)0x22, + /*ActualParallelism=*/7, + /*Index=*/31, + /*Flags=*/127}; + + EXPECT_EQ(IT.toString(), + "OMPT Callback ImplicitTask: endpoint=1 parallel_data=0x11 " + "task_data=0x22 actual_parallelism=7 index=31 flags=127"); +} + +TEST(InternalEvent_toString, SyncRegion) { + internal::SyncRegion SR{ + /*Kind=*/ompt_sync_region_t::ompt_sync_region_taskwait, + /*Endpoint=*/ompt_scope_endpoint_t::ompt_scope_end, + /*ParallelData=*/(ompt_data_t *)0x11, + /*TaskData=*/(ompt_data_t *)0x22, + /*CodeptrRA=*/(const void *)0x33}; + + EXPECT_EQ(SR.toString(), "OMPT Callback SyncRegion: kind=5 endpoint=2 " + "parallel_data=0x11 task_data=0x22 codeptr=0x33"); +} + +TEST(InternalEvent_toString, Target) { + internal::Target T{/*Kind=*/ompt_target_t::ompt_target_enter_data_nowait, + /*Endpoint=*/ompt_scope_endpoint_t::ompt_scope_end, + /*DeviceNum=*/7, + /*TaskData=*/(ompt_data_t *)0x11, + /*TargetId=*/(ompt_id_t)31, + /*CodeptrRA=*/(const void *)0x22}; + + EXPECT_EQ(T.toString(), "Callback Target: target_id=31 kind=10 " + "endpoint=2 device_num=7 code=0x22"); +} + +TEST(InternalEvent_toString, TargetEmi) { + ompt_data_t TaskData{.value = 31}; + ompt_data_t TargetTaskData{.value = 127}; + ompt_data_t TargetData{.value = 8191}; + + internal::TargetEmi T{/*Kind=*/ompt_target_t::ompt_target_update, + /*Endpoint=*/ompt_scope_endpoint_t::ompt_scope_begin, + /*DeviceNum=*/7, + /*TaskData=*/(ompt_data_t *)&TaskData, + /*TargetTaskData=*/(ompt_data_t *)&TargetTaskData, + /*TargetData=*/(ompt_data_t *)&TargetData, + /*CodeptrRA=*/(const void *)0x11}; + + internal::TargetEmi TDataNull{ + /*Kind=*/ompt_target_t::ompt_target_update, + /*Endpoint=*/ompt_scope_endpoint_t::ompt_scope_begin, + /*DeviceNum=*/7, + /*TaskData=*/(ompt_data_t *)&TaskData, + /*TargetTaskData=*/(ompt_data_t *)nullptr, + /*TargetData=*/(ompt_data_t *)&TargetData, + /*CodeptrRA=*/(const void *)0x11}; + + std::ostringstream StreamT1; + std::ostringstream StreamT2; + std::string CallBackPrefix{ + "Callback Target EMI: kind=4 endpoint=1 device_num=7"}; + StreamT1 << CallBackPrefix << std::showbase << std::hex; + StreamT1 << " task_data=" << &TaskData << " (0x1f)"; + StreamT1 << " target_task_data=" << &TargetTaskData << " (0x7f)"; + StreamT1 << " target_data=" << &TargetData << " (0x1fff)"; + StreamT1 << " code=0x11"; + + StreamT2 << CallBackPrefix << std::showbase << std::hex; + StreamT2 << " task_data=" << &TaskData << " (0x1f)"; + StreamT2 << " target_task_data=(nil) (0x0)"; + StreamT2 << " target_data=" << &TargetData << " (0x1fff)"; + StreamT2 << " code=0x11"; + + EXPECT_EQ(T.toString(), StreamT1.str()); + EXPECT_EQ(TDataNull.toString(), StreamT2.str()); +} + +TEST(InternalEvent_toString, TargetDataOp) { + internal::TargetDataOp TDO{ + /*TargetId=*/7, + /*HostOpId=*/31, + /*OpType=*/ompt_target_data_op_t::ompt_target_data_associate, + /*SrcAddr=*/(void *)0x11, + /*SrcDeviceNum=*/127, + /*DstAddr=*/(void *)0x22, + /*DstDeviceNum=*/8191, + /*Bytes=*/4096, + /*CodeptrRA=*/(const void *)0x33}; + + EXPECT_EQ( + TDO.toString(), + " Callback DataOp: target_id=7 host_op_id=31 optype=5 src=0x11 " + "src_device_num=127 dest=0x22 dest_device_num=8191 bytes=4096 code=0x33"); +} + +TEST(InternalEvent_toString, TargetDataOpEmi) { + ompt_data_t TargetTaskData{.value = 31}; + ompt_data_t TargetData{.value = 127}; + ompt_id_t HostOpId = 8191; + + internal::TargetDataOpEmi TDO{ + /*Endpoint=*/ompt_scope_endpoint_t::ompt_scope_begin, + /*TargetTaskData=*/(ompt_data_t *)&TargetTaskData, + /*TargetData=*/(ompt_data_t *)&TargetData, + /*HostOpId=*/(ompt_id_t *)&HostOpId, + /*OpType=*/ompt_target_data_op_t::ompt_target_data_disassociate, + /*SrcAddr=*/(void *)0x11, + /*SrcDeviceNum=*/1, + /*DstAddr=*/(void *)0x22, + /*DstDeviceNum=*/2, + /*Bytes=*/4096, + /*CodeptrRA=*/(const void *)0x33}; + + // Set HostOpId=nullptr + internal::TargetDataOpEmi TDO_HostOpIdNull{ + /*Endpoint=*/ompt_scope_endpoint_t::ompt_scope_begin, + /*TargetTaskData=*/(ompt_data_t *)&TargetTaskData, + /*TargetData=*/(ompt_data_t *)&TargetData, + /*HostOpId=*/(ompt_id_t *)nullptr, + /*OpType=*/ompt_target_data_op_t::ompt_target_data_disassociate, + /*SrcAddr=*/(void *)0x11, + /*SrcDeviceNum=*/1, + /*DstAddr=*/(void *)0x22, + /*DstDeviceNum=*/2, + /*Bytes=*/4096, + /*CodeptrRA=*/(const void *)0x33}; + + std::ostringstream StreamTDO1; + std::ostringstream StreamTDO2; + std::string CallBackPrefix{" Callback DataOp EMI: endpoint=1 optype=6"}; + std::string CallBackSuffix{ + " src=0x11 src_device_num=1 dest=0x22 dest_device_num=2 " + "bytes=4096 code=0x33"}; + StreamTDO1 << CallBackPrefix << std::showbase << std::hex; + StreamTDO1 << " target_task_data=" << &TargetTaskData << " (0x1f)"; + StreamTDO1 << " target_data=" << &TargetData << " (0x7f)"; + StreamTDO1 << " host_op_id=" << &HostOpId << " (0x1fff)"; + StreamTDO1 << CallBackSuffix; + + StreamTDO2 << CallBackPrefix << std::showbase << std::hex; + StreamTDO2 << " target_task_data=" << &TargetTaskData << " (0x1f)"; + StreamTDO2 << " target_data=" << &TargetData << " (0x7f)"; + StreamTDO2 << " host_op_id=(nil) (0x0)"; + StreamTDO2 << CallBackSuffix; + + EXPECT_EQ(TDO.toString(), StreamTDO1.str()); + EXPECT_EQ(TDO_HostOpIdNull.toString(), StreamTDO2.str()); +} + +TEST(InternalEvent_toString, TargetSubmit) { + internal::TargetSubmit TS{/*TargetId=*/7, + /*HostOpId=*/31, + /*RequestedNumTeams=*/127}; + + EXPECT_EQ(TS.toString(), + " Callback Submit: target_id=7 host_op_id=31 req_num_teams=127"); +} + +TEST(InternalEvent_toString, TargetSubmitEmi) { + ompt_data_t TargetData{.value = 127}; + ompt_id_t HostOpId = 8191; + internal::TargetSubmitEmi TS{ + /*Endpoint=*/ompt_scope_endpoint_t::ompt_scope_begin, + /*TargetData=*/(ompt_data_t *)&TargetData, + /*HostOpId=*/(ompt_id_t *)&HostOpId, + /*RequestedNumTeams=*/7}; + + std::ostringstream StreamTS; + std::string CallBackPrefix{ + " Callback Submit EMI: endpoint=1 req_num_teams=7"}; + StreamTS << CallBackPrefix << std::showbase << std::hex; + StreamTS << " target_data=" << &TargetData << " (0x7f)"; + StreamTS << " host_op_id=" << &HostOpId << " (0x1fff)"; + + EXPECT_EQ(TS.toString(), StreamTS.str()); +} + +TEST(InternalEvent_toString, DeviceInitialize) { + const char *Type = "DeviceType"; + const char *DocStr = "DocumentationString"; + + internal::DeviceInitialize DI{/*DeviceNum=*/7, + /*Type=*/Type, + /*Device=*/(ompt_device_t *)0x11, + /*LookupFn=*/(ompt_function_lookup_t)0x22, + /*DocStr=*/DocStr}; + + internal::DeviceInitialize DINull{/*DeviceNum=*/0, + /*Type=*/nullptr, + /*Device=*/nullptr, + /*LookupFn=*/(ompt_function_lookup_t)0x0, + /*DocStr=*/nullptr}; + + std::ostringstream StreamDI; + std::string CallBackPrefix{"Callback Init: device_num=7 type=DeviceType " + "device=0x11 lookup=0x22 doc="}; + StreamDI << CallBackPrefix << std::showbase << std::hex; + StreamDI << (uint64_t)DocStr; + EXPECT_EQ(DI.toString(), StreamDI.str()); + + // TODO This looks inconsistent: (null) vs. (nil) + EXPECT_EQ(DINull.toString(), "Callback Init: device_num=0 type=(null) " + "device=(nil) lookup=(nil) doc=(nil)"); +} + +TEST(InternalEvent_toString, DeviceFinalize) { + internal::DeviceFinalize DF{/*DeviceNum=*/7}; + + EXPECT_EQ(DF.toString(), "Callback Fini: device_num=7"); +} + +TEST(InternalEvent_toString, DeviceLoad) { + const char *Filename = "FilenameToLoad"; + + internal::DeviceLoad DL{/*DeviceNum=*/7, + /*Filename=*/Filename, + /*OffsetInFile=*/31, + /*VmaInFile=*/(void *)0x11, + /*Bytes=*/127, + /*HostAddr=*/(void *)0x22, + /*DeviceAddr=*/(void *)0x33, + /*ModuleId=*/8191}; + + internal::DeviceLoad DLNull{/*DeviceNum=*/0, + /*Filename=*/nullptr, + /*OffsetInFile=*/0, + /*VmaInFile=*/nullptr, + /*Bytes=*/0, + /*HostAddr=*/nullptr, + /*DeviceAddr=*/nullptr, + /*ModuleId=*/0}; + + EXPECT_EQ( + DL.toString(), + "Callback Load: device_num:7 module_id:8191 " + "filename:FilenameToLoad host_adddr:0x22 device_addr:0x33 bytes:127"); + + // TODO This looks inconsistent: (null) vs. (nil) and ':' instead of '=' + EXPECT_EQ(DLNull.toString(), + "Callback Load: device_num:0 module_id:0 filename:(null) " + "host_adddr:(nil) device_addr:(nil) bytes:0"); +} + +TEST(InternalEvent_toString, BufferRequest) { + size_t Bytes = 7; + ompt_buffer_t *Buffer = (void *)0x11; + + internal::BufferRequest BR{/*DeviceNum=*/31, + /*Buffer=*/&Buffer, + /*Bytes=*/&Bytes}; + + internal::BufferRequest BRNull{/*DeviceNum=*/127, + /*Buffer=*/nullptr, + /*Bytes=*/nullptr}; + + EXPECT_EQ(BR.toString(), + "Allocated 7 bytes at 0x11 in buffer request callback"); + EXPECT_EQ(BRNull.toString(), + "Allocated 0 bytes at (nil) in buffer request callback"); +} + +TEST(InternalEvent_toString, BufferComplete) { + ompt_buffer_t *Buffer = (void *)0x11; + + internal::BufferComplete BC{/*DeviceNum=*/7, + /*Buffer=*/Buffer, + /*Bytes=*/127, + /*Begin=*/8191, + /*BufferOwned=*/1}; + + internal::BufferComplete BCNull{/*DeviceNum=*/0, + /*Buffer=*/nullptr, + /*Bytes=*/0, + /*Begin=*/0, + /*BufferOwned=*/0}; + + EXPECT_EQ(BC.toString(), + "Executing buffer complete callback: 7 0x11 127 0x1fff 1"); + EXPECT_EQ(BCNull.toString(), + "Executing buffer complete callback: 0 (nil) 0 (nil) 0"); +} + +TEST(InternalEvent_toString, BufferRecordInvalid) { + ompt_record_ompt_t InvalidRecord{ + /*type=*/ompt_callbacks_t::ompt_callback_parallel_begin, + /*time=*/7, + /*thread_id=*/31, + /*target_id=*/127, + /*record=*/{.parallel_begin = {}}}; + + internal::BufferRecord BRNull{/*RecordPtr=*/nullptr}; + internal::BufferRecord BRInvalid{/*RecordPtr=*/&InvalidRecord}; + + std::ostringstream StreamBRInvalid; + StreamBRInvalid << "rec=" << std::showbase << std::hex << &InvalidRecord; + StreamBRInvalid << " type=3 (unsupported record type)"; + + EXPECT_EQ(BRNull.toString(), "rec=(nil) type=0 (unsupported record type)"); + EXPECT_EQ(BRInvalid.toString(), StreamBRInvalid.str()); +} + +TEST(InternalEvent_toString, BufferRecordTarget) { + ompt_record_target_t SubRecordTarget{ + /*kind=*/ompt_target_t::ompt_target_update, + /*endpoint=*/ompt_scope_endpoint_t::ompt_scope_begin, + /*device_num=*/2, + /*task_id=*/127, + /*target_id=*/31, + /*codeptr_ra=*/(const void *)0x11}; + + ompt_record_ompt_t TargetRecord{ + /*type=*/ompt_callbacks_t::ompt_callback_target, + /*time=*/7, + /*thread_id=*/29, + /*target_id=*/31, + /*record*/ {.target = SubRecordTarget}}; + + internal::BufferRecord BR{/*RecordPtr=*/&TargetRecord}; + + std::ostringstream StreamBR; + StreamBR << "rec=" << std::showbase << std::hex << &TargetRecord; + StreamBR << " type=8 (Target task) time=7 thread_id=29 target_id=31 kind=4"; + StreamBR << " endpoint=1 device=2 task_id=127 codeptr=0x11"; + + EXPECT_EQ(BR.toString(), StreamBR.str()); +} + +TEST(InternalEvent_toString, BufferRecordDataOp) { + ompt_record_target_data_op_t SubRecordTargetDataOp{ + /*host_op_id=*/7, + /*optype=*/ompt_target_data_op_t::ompt_target_data_alloc_async, + /*src_addr=*/(void *)0x11, + /*src_device_num=*/1, + /*dest_addr=*/(void *)0x22, + /*dest_device_num=*/2, + /*bytes=*/127, + /*end_time=*/128, + /*codeptr_ra=*/(const void *)0x33, + }; + + ompt_record_ompt_t DataOpRecord{ + /*type=*/ompt_callbacks_t::ompt_callback_target_data_op_emi, + /*time=*/8, + /*thread_id=*/3, + /*target_id=*/5, + /*record=*/{.target_data_op = SubRecordTargetDataOp}}; + + internal::BufferRecord BR{/*RecordPtr=*/&DataOpRecord}; + + std::ostringstream StreamBR; + StreamBR << "rec=" << std::showbase << std::hex << &DataOpRecord; + StreamBR << " type=34 (Target data op) time=8 thread_id=3 target_id=5"; + StreamBR << " host_op_id=7 optype=17 src_addr=0x11 src_device=1"; + StreamBR << " dest_addr=0x22 dest_device=2 bytes=127 end_time=128"; + StreamBR << " duration=120 ns codeptr=0x33"; + + EXPECT_EQ(BR.toString(), StreamBR.str()); +} + +TEST(InternalEvent_toString, BufferRecordKernel) { + ompt_record_target_kernel_t SubRecordTargetKernel{ + /*host_op_id=*/11, + /*requested_num_teams=*/127, + /*granted_num_teams=*/63, + /*end_time=*/8191, + }; + + ompt_record_ompt_t KernelRecord{ + /*type=*/ompt_callbacks_t::ompt_callback_target_submit_emi, + /*time=*/9, + /*thread_id=*/19, + /*target_id=*/33, + /*record=*/{.target_kernel = SubRecordTargetKernel}}; + + internal::BufferRecord BR{/*RecordPtr=*/&KernelRecord}; + + std::ostringstream StreamBR; + StreamBR << "rec=" << std::showbase << std::hex << &KernelRecord; + StreamBR << " type=35 (Target kernel) time=9 thread_id=19 target_id=33"; + StreamBR << " host_op_id=11 requested_num_teams=127 granted_num_teams=63"; + StreamBR << " end_time=8191 duration=8182 ns"; + + EXPECT_EQ(BR.toString(), StreamBR.str()); +} + +TEST(InternalEvent_toString, BufferRecordDeallocation) { + internal::BufferRecordDeallocation BRD{/*Buffer=*/(ompt_record_ompt_t *)0x11}; + internal::BufferRecordDeallocation BRDNull{/*Buffer=*/nullptr}; + + EXPECT_EQ(BRD.toString(), "Deallocated 0x11"); + EXPECT_EQ(BRDNull.toString(), "Deallocated (nil)"); +} diff --git a/openmp/tools/omptest/test/unittests/internal-util-test.cpp b/openmp/tools/omptest/test/unittests/internal-util-test.cpp new file mode 100644 index 0000000000000..6a9868b85c3a3 --- /dev/null +++ b/openmp/tools/omptest/test/unittests/internal-util-test.cpp @@ -0,0 +1,95 @@ +#include "InternalEvent.h" +#include + +#include "gtest/gtest.h" + +using namespace omptest; + +TEST(InternalUtility, ExpectedDefault_Integer) { + // int: -2147483648 (decimal) = 0x80000000 (hexadecimal) + EXPECT_EQ(expectedDefault(int), 0x80000000); + EXPECT_EQ(expectedDefault(int), (0x1 << 31)); + // int64_t: -9223372036854775808 (decimal) = 0x8000000000000000 (hexadecimal) + EXPECT_EQ(expectedDefault(int64_t), 0x8000000000000000); + EXPECT_EQ(expectedDefault(int64_t), (0x1L << 63)); +} + +TEST(InternalUtility, ExpectedDefault_Zero) { + // Expectedly zero + EXPECT_EQ(expectedDefault(size_t), 0); + EXPECT_EQ(expectedDefault(unsigned int), 0); + EXPECT_EQ(expectedDefault(ompt_id_t), 0); + EXPECT_EQ(expectedDefault(ompt_dispatch_t), 0); + EXPECT_EQ(expectedDefault(ompt_device_time_t), 0); +} + +TEST(InternalUtility, ExpectedDefault_Nullpointer) { + // Expectedly nullptr + EXPECT_EQ(expectedDefault(const char *), nullptr); + EXPECT_EQ(expectedDefault(const void *), nullptr); + EXPECT_EQ(expectedDefault(int *), nullptr); + EXPECT_EQ(expectedDefault(void *), nullptr); + EXPECT_EQ(expectedDefault(ompt_data_t *), nullptr); + EXPECT_EQ(expectedDefault(ompt_device_t *), nullptr); + EXPECT_EQ(expectedDefault(ompt_frame_t *), nullptr); + EXPECT_EQ(expectedDefault(ompt_function_lookup_t), nullptr); + EXPECT_EQ(expectedDefault(ompt_id_t *), nullptr); +} + +TEST(InternalUtility, MakeHexString_PointerValues) { + // IsPointer should only affect zero value + EXPECT_EQ(util::makeHexString(0, /*IsPointer=*/true), "(nil)"); + EXPECT_EQ(util::makeHexString(0, /*IsPointer=*/false), "0x0"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true), "0xff"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/false), "0xff"); +} + +TEST(InternalUtility, MakeHexString_MinimumBytes) { + // Return a minimum length, based on the (minimum) requested bytes + EXPECT_EQ(util::makeHexString(15, /*IsPointer=*/true, /*MinBytes=*/0), "0xf"); + EXPECT_EQ(util::makeHexString(15, /*IsPointer=*/true, /*MinBytes=*/1), + "0x0f"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/0), + "0xff"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/1), + "0xff"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/2), + "0x00ff"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/3), + "0x0000ff"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/4), + "0x000000ff"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/5), + "0x00000000ff"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/6), + "0x0000000000ff"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/7), + "0x000000000000ff"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/8), + "0x00000000000000ff"); + + // Default to four bytes, if request exceeds eight byte range + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, /*MinBytes=*/9), + "0x000000ff"); + + // Disregard requested minimum byte width, if actual value exceeds it + EXPECT_EQ(util::makeHexString(1024, /*IsPointer=*/true, /*MinBytes=*/1), + "0x400"); +} + +TEST(InternalUtility, MakeHexString_HexBase) { + // Cut off "0x" when requested + EXPECT_EQ(util::makeHexString(0, /*IsPointer=*/true, /*MinBytes=*/0, + /*ShowHexBase=*/false), + "(nil)"); + EXPECT_EQ(util::makeHexString(0, /*IsPointer=*/false, /*MinBytes=*/0, + /*ShowHexBase=*/false), + "0"); + EXPECT_EQ(util::makeHexString(0, /*IsPointer=*/false, /*MinBytes=*/1, + /*ShowHexBase=*/false), + "00"); + EXPECT_EQ(util::makeHexString(255, /*IsPointer=*/true, + /*MinBytes=*/2, + /*ShowHexBase=*/false), + "00ff"); +} diff --git a/openmp/tools/omptest/test/unittests/main-test.cpp b/openmp/tools/omptest/test/unittests/main-test.cpp new file mode 100644 index 0000000000000..2eba663e49c8e --- /dev/null +++ b/openmp/tools/omptest/test/unittests/main-test.cpp @@ -0,0 +1,141 @@ +#include "OmptAssertEvent.h" +#include "OmptAsserter.h" +#include "OmptTester.h" +#include + +#include "gtest/gtest.h" + +using OS = omptest::ObserveState; +using OAE = omptest::OmptAssertEvent; + +TEST(CompareOperatorTests, ThreadBeginIdentity) { + auto TBInitial = + OAE::ThreadBegin("dflt", "", OS::always, ompt_thread_initial); + auto TBWorker = OAE::ThreadBegin("dflt", "", OS::always, ompt_thread_worker); + auto TBOther = OAE::ThreadBegin("dflt", "", OS::always, ompt_thread_other); + auto TBUnknown = + OAE::ThreadBegin("dflt", "", OS::always, ompt_thread_unknown); + + ASSERT_EQ(TBInitial, TBInitial); + ASSERT_EQ(TBWorker, TBWorker); + ASSERT_EQ(TBOther, TBOther); + ASSERT_EQ(TBUnknown, TBUnknown); +} + +TEST(CompareOperatorTests, ThreadEndIdentity) { + auto TE = OAE::ThreadEnd("dflt", "", OS::always); + + ASSERT_EQ(TE, TE); +} + +TEST(CompareOperatorTests, ParallelBeginIdentity) { + auto PBNumT = OAE::ParallelBegin("thrdenable", "", OS::always, 3); + + ASSERT_EQ(PBNumT, PBNumT); +} + +TEST(CompareOperatorTests, ParallelEndIdentity) { + auto PEDflt = OAE::ParallelEnd("dflt", "", OS::always); + // TODO: Add cases with parallel data set, task data set, flags + + ASSERT_EQ(PEDflt, PEDflt); +} + +TEST(CompareOperatorTests, WorkIdentity) { + auto WDLoopBgn = + OAE::Work("loopbgn", "", OS::always, ompt_work_loop, ompt_scope_begin); + auto WDLoopEnd = + OAE::Work("loobend", "", OS::always, ompt_work_loop, ompt_scope_end); + + ASSERT_EQ(WDLoopBgn, WDLoopBgn); + ASSERT_EQ(WDLoopEnd, WDLoopEnd); + + auto WDSectionsBgn = OAE::Work("sectionsbgn", "", OS::always, + ompt_work_sections, ompt_scope_begin); + auto WDSectionsEnd = OAE::Work("sectionsend", "", OS::always, + ompt_work_sections, ompt_scope_end); + + // TODO: singleexecutor, single_other, workshare, distribute, taskloop, scope, + // loop_static, loop_dynamic, loop_guided, loop_other + + ASSERT_EQ(WDSectionsBgn, WDSectionsBgn); + ASSERT_EQ(WDSectionsEnd, WDSectionsEnd); +} + +TEST(CompareOperatorTests, DispatchIdentity) { + auto DIDflt = OAE::Dispatch("dflt", "", OS::always); + + ASSERT_EQ(DIDflt, DIDflt); +} + +TEST(CompareOperatorTests, TaskCreateIdentity) { + auto TCDflt = OAE::TaskCreate("dflt", "", OS::always); + + ASSERT_EQ(TCDflt, TCDflt); +} + +TEST(CompareOperatorTests, TaskScheduleIdentity) { + auto TS = OAE::TaskSchedule("dflt", "", OS::always); + + ASSERT_EQ(TS, TS); +} + +TEST(CompareOperatorTests, ImplicitTaskIdentity) { + auto ITDfltBgn = + OAE::ImplicitTask("dfltbgn", "", OS::always, ompt_scope_begin); + auto ITDfltEnd = OAE::ImplicitTask("dfltend", "", OS::always, ompt_scope_end); + + ASSERT_EQ(ITDfltBgn, ITDfltBgn); + ASSERT_EQ(ITDfltEnd, ITDfltEnd); +} + +TEST(CompareOperatorTests, SyncRegionIdentity) { + auto SRDfltBgn = + OAE::SyncRegion("srdfltbgn", "", OS::always, + ompt_sync_region_barrier_explicit, ompt_scope_begin); + auto SRDfltEnd = + OAE::SyncRegion("srdfltend", "", OS::always, + ompt_sync_region_barrier_explicit, ompt_scope_end); + + ASSERT_EQ(SRDfltBgn, SRDfltBgn); + ASSERT_EQ(SRDfltEnd, SRDfltEnd); +} + +TEST(CompareOperatorTests, TargetIdentity) { + auto TargetDfltBgn = + OAE::Target("dfltbgn", "", OS::always, ompt_target, ompt_scope_begin); + auto TargetDfltEnd = + OAE::Target("dfltend", "", OS::always, ompt_target, ompt_scope_end); + + ASSERT_EQ(TargetDfltBgn, TargetDfltBgn); + ASSERT_EQ(TargetDfltEnd, TargetDfltEnd); + + auto TargetDevBgn = OAE::Target("tgtdevbgn", "", OS::always, ompt_target, + ompt_scope_begin, 1); + auto TargetDevEnd = + OAE::Target("tgtdevend", "", OS::always, ompt_target, ompt_scope_end, 1); + + ASSERT_EQ(TargetDevBgn, TargetDevBgn); + ASSERT_EQ(TargetDevEnd, TargetDevEnd); +} + +TEST(CompareOperatorTests, BufferRecordIdentity) { + // Default, no time limit or anything + auto BRDflt = + OAE::BufferRecord("dflt", "", OS::always, ompt_callback_target_submit); + + // Minimum time set, no max time + auto BRMinSet = OAE::BufferRecord("minset", "", OS::always, + ompt_callback_target_submit, 10); + + // Minimum time and maximum time set + auto BRMinMaxSet = OAE::BufferRecord("minmaxset", "", OS::always, + ompt_callback_target_submit, {10, 100}); + + ASSERT_EQ(BRDflt, BRDflt); + ASSERT_EQ(BRMinSet, BRMinSet); + ASSERT_EQ(BRMinMaxSet, BRMinMaxSet); +} + +// Add main definition +OMPTEST_TESTSUITE_MAIN()