diff --git a/llvm/include/llvm/Frontend/OpenMP/DirectiveNameParser.h b/llvm/include/llvm/Frontend/OpenMP/DirectiveNameParser.h new file mode 100644 index 0000000000000..898c32aa4b8f2 --- /dev/null +++ b/llvm/include/llvm/Frontend/OpenMP/DirectiveNameParser.h @@ -0,0 +1,77 @@ +//===- DirectiveNameParser.h ------------------------------------- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FRONTEND_OPENMP_DIRECTIVENAMEPARSER_H +#define LLVM_FRONTEND_OPENMP_DIRECTIVENAMEPARSER_H + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Frontend/OpenMP/OMP.h" + +#include + +namespace llvm::omp { +/// Parser class for OpenMP directive names. It only recognizes names listed +/// in OMP.td, in particular it does not recognize Fortran's end-directives +/// if they are not explicitly listed in OMP.td. +/// +/// The class itself may be a singleton, once it's constructed it never +/// changes. +/// +/// Usage: +/// { +/// DirectiveNameParser Parser; // Could be static const. +/// +/// DirectiveNameParser::State *S = Parser.initial(); +/// for (StringRef Token : Tokens) +/// S = Parser.consume(S, Token); // Passing nullptr is ok. +/// +/// if (S == nullptr) { +/// // Error: ended up in a state from which there is no possible path +/// // to a successful parse. +/// } else if (S->Value == OMPD_unknown) { +/// // Parsed a sequence of tokens that are not a complete name, but +/// // parsing more tokens could lead to a successful parse. +/// } else { +/// // Success. +/// ParsedId = S->Value; +/// } +/// } +struct DirectiveNameParser { + DirectiveNameParser(SourceLanguage L = SourceLanguage::C); + + struct State { + Directive Value = Directive::OMPD_unknown; + + private: + using TransitionMapTy = StringMap; + std::unique_ptr Transition; + + State *next(StringRef Tok); + const State *next(StringRef Tok) const; + bool isValid() const { + return Value != Directive::OMPD_unknown || !Transition->empty(); + } + friend struct DirectiveNameParser; + }; + + const State *initial() const { return &InitialState; } + const State *consume(const State *Current, StringRef Tok) const; + + static SmallVector tokenize(StringRef N); + +private: + void insertName(StringRef Name, Directive D); + State *insertTransition(State *From, StringRef Tok); + + State InitialState; +}; +} // namespace llvm::omp + +#endif // LLVM_FRONTEND_OPENMP_DIRECTIVENAMEPARSER_H diff --git a/llvm/lib/Frontend/OpenMP/CMakeLists.txt b/llvm/lib/Frontend/OpenMP/CMakeLists.txt index 5bf15ca3a8991..e60b59c1203b9 100644 --- a/llvm/lib/Frontend/OpenMP/CMakeLists.txt +++ b/llvm/lib/Frontend/OpenMP/CMakeLists.txt @@ -2,6 +2,7 @@ add_llvm_component_library(LLVMFrontendOpenMP OMP.cpp OMPContext.cpp OMPIRBuilder.cpp + DirectiveNameParser.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/Frontend diff --git a/llvm/lib/Frontend/OpenMP/DirectiveNameParser.cpp b/llvm/lib/Frontend/OpenMP/DirectiveNameParser.cpp new file mode 100644 index 0000000000000..62e24825111f6 --- /dev/null +++ b/llvm/lib/Frontend/OpenMP/DirectiveNameParser.cpp @@ -0,0 +1,83 @@ +//===- DirectiveNameParser.cpp --------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "llvm/Frontend/OpenMP/DirectiveNameParser.h" +#include "llvm/ADT/Sequence.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Frontend/OpenMP/OMP.h" + +#include +#include + +namespace llvm::omp { +DirectiveNameParser::DirectiveNameParser(SourceLanguage L) { + // Take every directive, get its name in every version, break the name up + // into whitespace-separated tokens, and insert each token. + for (size_t I : llvm::seq(Directive_enumSize)) { + auto D = static_cast(I); + if (D == Directive::OMPD_unknown || !(getDirectiveLanguages(D) & L)) + continue; + for (unsigned Ver : getOpenMPVersions()) + insertName(getOpenMPDirectiveName(D, Ver), D); + } +} + +const DirectiveNameParser::State * +DirectiveNameParser::consume(const State *Current, StringRef Tok) const { + if (!Current) + return Current; + assert(Current->isValid() && "Invalid input state"); + if (const State *Next = Current->next(Tok)) + return Next->isValid() ? Next : nullptr; + return nullptr; +} + +SmallVector DirectiveNameParser::tokenize(StringRef Str) { + SmallVector Tokens; + SplitString(Str, Tokens); + return Tokens; +} + +void DirectiveNameParser::insertName(StringRef Name, Directive D) { + State *Where = &InitialState; + + for (StringRef Tok : tokenize(Name)) + Where = insertTransition(Where, Tok); + + Where->Value = D; +} + +DirectiveNameParser::State * +DirectiveNameParser::insertTransition(State *From, StringRef Tok) { + assert(From && "Expecting state"); + if (!From->Transition) + From->Transition = std::make_unique(); + if (State *Next = From->next(Tok)) + return Next; + + auto [Where, DidIt] = From->Transition->try_emplace(Tok, State()); + assert(DidIt && "Map insertion failed"); + return &Where->second; +} + +const DirectiveNameParser::State * +DirectiveNameParser::State::next(StringRef Tok) const { + if (!Transition) + return nullptr; + auto F = Transition->find(Tok); + return F != Transition->end() ? &F->second : nullptr; +} + +DirectiveNameParser::State *DirectiveNameParser::State::next(StringRef Tok) { + if (!Transition) + return nullptr; + auto F = Transition->find(Tok); + return F != Transition->end() ? &F->second : nullptr; +} +} // namespace llvm::omp diff --git a/llvm/unittests/Frontend/CMakeLists.txt b/llvm/unittests/Frontend/CMakeLists.txt index 2412cc9d26c7a..281d509227a46 100644 --- a/llvm/unittests/Frontend/CMakeLists.txt +++ b/llvm/unittests/Frontend/CMakeLists.txt @@ -20,6 +20,7 @@ add_llvm_unittest(LLVMFrontendTests OpenMPCompositionTest.cpp OpenMPDecompositionTest.cpp OpenMPDirectiveNameTest.cpp + OpenMPDirectiveNameParserTest.cpp DEPENDS acc_gen diff --git a/llvm/unittests/Frontend/OpenMPDirectiveNameParserTest.cpp b/llvm/unittests/Frontend/OpenMPDirectiveNameParserTest.cpp new file mode 100644 index 0000000000000..0363a08cc0f03 --- /dev/null +++ b/llvm/unittests/Frontend/OpenMPDirectiveNameParserTest.cpp @@ -0,0 +1,171 @@ +//===- llvm/unittests/Frontend/OpenMPDirectiveNameParserTest.cpp ----------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/Sequence.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Frontend/OpenMP/DirectiveNameParser.h" +#include "llvm/Frontend/OpenMP/OMP.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include + +using namespace llvm; + +static const omp::DirectiveNameParser &getParser() { + static omp::DirectiveNameParser Parser(omp::SourceLanguage::C | + omp::SourceLanguage::Fortran); + return Parser; +} + +static std::vector tokenize(StringRef S) { + std::vector Tokens; + + using TokenIterator = std::istream_iterator; + std::string Copy = S.str(); + std::istringstream Stream(Copy); + + for (auto I = TokenIterator(Stream), E = TokenIterator(); I != E; ++I) + Tokens.push_back(*I); + return Tokens; +} + +static std::string &prepareParamName(std::string &Name) { + for (size_t I = 0, E = Name.size(); I != E; ++I) { + // The parameter name must only have alphanumeric characters. + if (!isalnum(Name[I])) + Name[I] = 'X'; + } + return Name; +} + +namespace llvm { +template <> struct enum_iteration_traits { + static constexpr bool is_iterable = true; +}; +} // namespace llvm + +// Test tokenizing. + +class Tokenize : public testing::TestWithParam {}; + +static bool isEqual(const SmallVector &A, + const std::vector &B) { + if (A.size() != B.size()) + return false; + + for (size_t I = 0, E = A.size(); I != E; ++I) { + if (A[I] != StringRef(B[I])) + return false; + } + return true; +} + +TEST_P(Tokenize, T) { + omp::Directive DirId = GetParam(); + StringRef Name = omp::getOpenMPDirectiveName(DirId, omp::FallbackVersion); + + SmallVector tokens1 = omp::DirectiveNameParser::tokenize(Name); + std::vector tokens2 = tokenize(Name); + ASSERT_TRUE(isEqual(tokens1, tokens2)); +} + +static std::string +getParamName1(const testing::TestParamInfo &Info) { + omp::Directive DirId = Info.param; + std::string Name = + omp::getOpenMPDirectiveName(DirId, omp::FallbackVersion).str(); + return prepareParamName(Name); +} + +INSTANTIATE_TEST_SUITE_P( + DirectiveNameParserTest, Tokenize, + testing::ValuesIn( + llvm::enum_seq(static_cast(0), + static_cast(omp::Directive_enumSize))), + getParamName1); + +// Test parsing of valid names. + +using ValueType = std::tuple; + +class ParseValid : public testing::TestWithParam {}; + +TEST_P(ParseValid, T) { + auto [DirId, Version] = GetParam(); + if (DirId == omp::Directive::OMPD_unknown) + return; + + std::string Name = omp::getOpenMPDirectiveName(DirId, Version).str(); + + // Tokenize and parse + auto &Parser = getParser(); + auto *State = Parser.initial(); + ASSERT_TRUE(State != nullptr); + + std::vector Tokens = tokenize(Name); + for (auto &Tok : Tokens) { + State = Parser.consume(State, Tok); + ASSERT_TRUE(State != nullptr); + } + + ASSERT_EQ(State->Value, DirId); +} + +static std::string +getParamName2(const testing::TestParamInfo &Info) { + auto [DirId, Version] = Info.param; + std::string Name = omp::getOpenMPDirectiveName(DirId, Version).str() + "v" + + std::to_string(Version); + return prepareParamName(Name); +} + +INSTANTIATE_TEST_SUITE_P( + DirectiveNameParserTest, ParseValid, + testing::Combine(testing::ValuesIn(llvm::enum_seq( + static_cast(0), + static_cast(omp::Directive_enumSize))), + testing::ValuesIn(omp::getOpenMPVersions())), + getParamName2); + +// Test parsing of invalid names + +class ParseInvalid : public testing::TestWithParam {}; + +TEST_P(ParseInvalid, T) { + std::string Name = GetParam(); + + auto &Parser = getParser(); + auto *State = Parser.initial(); + ASSERT_TRUE(State != nullptr); + + std::vector Tokens = tokenize(Name); + for (auto &Tok : Tokens) + State = Parser.consume(State, Tok); + + ASSERT_TRUE(State == nullptr || State->Value == omp::Directive::OMPD_unknown); +} + +namespace { +using namespace std; + +INSTANTIATE_TEST_SUITE_P(DirectiveNameParserTest, ParseInvalid, + testing::Values( + // Names that contain invalid tokens + "bad"s, "target teams invalid"s, + "target sections parallel"s, + "target teams distribute parallel for wrong"s, + // Valid beginning, but not a complete name + "begin declare"s, + // Complete name with extra tokens + "distribute simd target"s)); +} // namespace