635 lines · cpp
1//===- OMPContext.cpp ------ Collection of helpers for OpenMP contexts ----===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8/// \file9///10/// This file implements helper functions and classes to deal with OpenMP11/// contexts as used by `[begin/end] declare variant` and `metadirective`.12///13//===----------------------------------------------------------------------===//14 15#include "llvm/Frontend/OpenMP/OMPContext.h"16#include "llvm/ADT/StringRef.h"17#include "llvm/ADT/StringSwitch.h"18#include "llvm/Support/Debug.h"19#include "llvm/Support/raw_ostream.h"20#include "llvm/TargetParser/Triple.h"21 22#define DEBUG_TYPE "openmp-ir-builder"23 24using namespace llvm;25using namespace omp;26 27OMPContext::OMPContext(bool IsDeviceCompilation, Triple TargetTriple,28 Triple TargetOffloadTriple, int DeviceNum) {29 // Add the appropriate target device kind trait based on the target triple30 if (!TargetOffloadTriple.getTriple().empty() && DeviceNum > -1) {31 // If target triple is present, then target device is not a host32 ActiveTraits.set(unsigned(TraitProperty::target_device_kind_nohost));33 switch (TargetOffloadTriple.getArch()) {34 case Triple::arm:35 case Triple::armeb:36 case Triple::aarch64:37 case Triple::aarch64_be:38 case Triple::aarch64_32:39 case Triple::mips:40 case Triple::mipsel:41 case Triple::mips64:42 case Triple::mips64el:43 case Triple::ppc:44 case Triple::ppcle:45 case Triple::ppc64:46 case Triple::ppc64le:47 case Triple::systemz:48 case Triple::x86:49 case Triple::x86_64:50 ActiveTraits.set(unsigned(TraitProperty::target_device_kind_cpu));51 break;52 case Triple::amdgcn:53 case Triple::nvptx:54 case Triple::nvptx64:55 case Triple::spirv64:56 ActiveTraits.set(unsigned(TraitProperty::target_device_kind_gpu));57 break;58 default:59 break;60 }61 // Add the appropriate device architecture trait based on the triple.62#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \63 if (TraitSelector::TraitSelectorEnum == TraitSelector::target_device_arch) { \64 if (TargetOffloadTriple.getArch() == \65 TargetOffloadTriple.getArchTypeForLLVMName(Str)) \66 ActiveTraits.set(unsigned(TraitProperty::Enum)); \67 if (StringRef(Str) == "x86_64" && \68 TargetOffloadTriple.getArch() == Triple::x86_64) \69 ActiveTraits.set(unsigned(TraitProperty::Enum)); \70 }71#include "llvm/Frontend/OpenMP/OMPKinds.def"72 } else {73 // Add the appropriate device kind trait based on the triple and the74 // IsDeviceCompilation flag.75 ActiveTraits.set(unsigned(IsDeviceCompilation76 ? TraitProperty::device_kind_nohost77 : TraitProperty::device_kind_host));78 ActiveTraits.set(unsigned(TraitProperty::target_device_kind_host));79 switch (TargetTriple.getArch()) {80 case Triple::arm:81 case Triple::armeb:82 case Triple::aarch64:83 case Triple::aarch64_be:84 case Triple::aarch64_32:85 case Triple::mips:86 case Triple::mipsel:87 case Triple::mips64:88 case Triple::mips64el:89 case Triple::ppc:90 case Triple::ppcle:91 case Triple::ppc64:92 case Triple::ppc64le:93 case Triple::systemz:94 case Triple::x86:95 case Triple::x86_64:96 ActiveTraits.set(unsigned(TraitProperty::device_kind_cpu));97 ActiveTraits.set(unsigned(TraitProperty::target_device_kind_cpu));98 break;99 case Triple::amdgcn:100 case Triple::nvptx:101 case Triple::nvptx64:102 case Triple::spirv64:103 ActiveTraits.set(unsigned(TraitProperty::device_kind_gpu));104 ActiveTraits.set(unsigned(TraitProperty::target_device_kind_gpu));105 break;106 default:107 break;108 }109 110 // Add the appropriate device architecture trait based on the triple.111#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \112 if (TraitSelector::TraitSelectorEnum == TraitSelector::device_arch || \113 TraitSelector::TraitSelectorEnum == TraitSelector::target_device_arch) { \114 if (TargetTriple.getArch() == TargetTriple.getArchTypeForLLVMName(Str)) \115 ActiveTraits.set(unsigned(TraitProperty::Enum)); \116 if (StringRef(Str) == "x86_64" && \117 TargetTriple.getArch() == Triple::x86_64) \118 ActiveTraits.set(unsigned(TraitProperty::Enum)); \119 }120#include "llvm/Frontend/OpenMP/OMPKinds.def"121 122 // TODO: What exactly do we want to see as device ISA trait?123 // The discussion on the list did not seem to have come to an agreed124 // upon solution.125 126 // LLVM is the "OpenMP vendor" but we could also interpret vendor as the127 // target vendor.128 ActiveTraits.set(unsigned(TraitProperty::implementation_vendor_llvm));129 130 // The user condition true is accepted but not false.131 ActiveTraits.set(unsigned(TraitProperty::user_condition_true));132 133 // This is for sure some device.134 ActiveTraits.set(unsigned(TraitProperty::device_kind_any));135 136 LLVM_DEBUG({137 dbgs() << "[" << DEBUG_TYPE138 << "] New OpenMP context with the following properties:\n";139 for (unsigned Bit : ActiveTraits.set_bits()) {140 TraitProperty Property = TraitProperty(Bit);141 dbgs() << "\t " << getOpenMPContextTraitPropertyFullName(Property)142 << "\n";143 }144 });145 }146}147 148/// Return true if \p C0 is a subset of \p C1. Note that both arrays are149/// expected to be sorted.150template <typename T> static bool isSubset(ArrayRef<T> C0, ArrayRef<T> C1) {151#ifdef EXPENSIVE_CHECKS152 assert(llvm::is_sorted(C0) && llvm::is_sorted(C1) &&153 "Expected sorted arrays!");154#endif155 if (C0.size() > C1.size())156 return false;157 auto It0 = C0.begin(), End0 = C0.end();158 auto It1 = C1.begin(), End1 = C1.end();159 while (It0 != End0) {160 if (It1 == End1)161 return false;162 if (*It0 == *It1) {163 ++It0;164 ++It1;165 continue;166 }167 ++It0;168 }169 return true;170}171 172/// Return true if \p C0 is a strict subset of \p C1. Note that both arrays are173/// expected to be sorted.174template <typename T>175static bool isStrictSubset(ArrayRef<T> C0, ArrayRef<T> C1) {176 if (C0.size() >= C1.size())177 return false;178 return isSubset<T>(C0, C1);179}180 181static bool isStrictSubset(const VariantMatchInfo &VMI0,182 const VariantMatchInfo &VMI1) {183 // If all required traits are a strict subset and the ordered vectors storing184 // the construct traits, we say it is a strict subset. Note that the latter185 // relation is not required to be strict.186 if (VMI0.RequiredTraits.count() >= VMI1.RequiredTraits.count())187 return false;188 for (unsigned Bit : VMI0.RequiredTraits.set_bits())189 if (!VMI1.RequiredTraits.test(Bit))190 return false;191 if (!isSubset<TraitProperty>(VMI0.ConstructTraits, VMI1.ConstructTraits))192 return false;193 return true;194}195 196static int197isVariantApplicableInContextHelper(const VariantMatchInfo &VMI,198 const OMPContext &Ctx,199 SmallVectorImpl<unsigned> *ConstructMatches,200 bool DeviceOrImplementationSetOnly) {201 202 // The match kind determines if we need to match all traits, any of the203 // traits, or none of the traits for it to be an applicable context.204 enum MatchKind { MK_ALL, MK_ANY, MK_NONE };205 206 MatchKind MK = MK_ALL;207 // Determine the match kind the user wants, "all" is the default and provided208 // to the user only for completeness.209 if (VMI.RequiredTraits.test(210 unsigned(TraitProperty::implementation_extension_match_any)))211 MK = MK_ANY;212 if (VMI.RequiredTraits.test(213 unsigned(TraitProperty::implementation_extension_match_none)))214 MK = MK_NONE;215 216 // Helper to deal with a single property that was (not) found in the OpenMP217 // context based on the match kind selected by the user via218 // `implementation={extensions(match_[all,any,none])}'219 auto HandleTrait = [MK](TraitProperty Property,220 bool WasFound) -> std::optional<bool> /* Result */ {221 // For kind "any" a single match is enough but we ignore non-matched222 // properties.223 if (MK == MK_ANY) {224 if (WasFound)225 return true;226 return std::nullopt;227 }228 229 // In "all" or "none" mode we accept a matching or non-matching property230 // respectively and move on. We are not done yet!231 if ((WasFound && MK == MK_ALL) || (!WasFound && MK == MK_NONE))232 return std::nullopt;233 234 // We missed a property, provide some debug output and indicate failure.235 LLVM_DEBUG({236 if (MK == MK_ALL)237 dbgs() << "[" << DEBUG_TYPE << "] Property "238 << getOpenMPContextTraitPropertyName(Property, "")239 << " was not in the OpenMP context but match kind is all.\n";240 if (MK == MK_NONE)241 dbgs() << "[" << DEBUG_TYPE << "] Property "242 << getOpenMPContextTraitPropertyName(Property, "")243 << " was in the OpenMP context but match kind is none.\n";244 });245 return false;246 };247 248 for (unsigned Bit : VMI.RequiredTraits.set_bits()) {249 TraitProperty Property = TraitProperty(Bit);250 if (DeviceOrImplementationSetOnly &&251 getOpenMPContextTraitSetForProperty(Property) != TraitSet::device &&252 getOpenMPContextTraitSetForProperty(Property) !=253 TraitSet::implementation)254 continue;255 256 // So far all extensions are handled elsewhere, we skip them here as they257 // are not part of the OpenMP context.258 if (getOpenMPContextTraitSelectorForProperty(Property) ==259 TraitSelector::implementation_extension)260 continue;261 262 bool IsActiveTrait = Ctx.ActiveTraits.test(unsigned(Property));263 264 // We overwrite the isa trait as it is actually up to the OMPContext hook to265 // check the raw string(s).266 if (Property == TraitProperty::device_isa___ANY)267 IsActiveTrait = llvm::all_of(VMI.ISATraits, [&](StringRef RawString) {268 return Ctx.matchesISATrait(RawString);269 });270 if (Property == TraitProperty::target_device_isa___ANY)271 IsActiveTrait = llvm::all_of(VMI.ISATraits, [&](StringRef RawString) {272 return Ctx.matchesISATrait(RawString);273 });274 275 if (std::optional<bool> Result = HandleTrait(Property, IsActiveTrait))276 return *Result;277 }278 279 if (!DeviceOrImplementationSetOnly) {280 // We could use isSubset here but we also want to record the match281 // locations.282 unsigned ConstructIdx = 0, NoConstructTraits = Ctx.ConstructTraits.size();283 for (TraitProperty Property : VMI.ConstructTraits) {284 assert(getOpenMPContextTraitSetForProperty(Property) ==285 TraitSet::construct &&286 "Variant context is ill-formed!");287 288 // Verify the nesting.289 bool FoundInOrder = false;290 while (!FoundInOrder && ConstructIdx != NoConstructTraits)291 FoundInOrder = (Ctx.ConstructTraits[ConstructIdx++] == Property);292 if (ConstructMatches)293 ConstructMatches->push_back(ConstructIdx - 1);294 295 if (std::optional<bool> Result = HandleTrait(Property, FoundInOrder))296 return *Result;297 298 if (!FoundInOrder) {299 LLVM_DEBUG(dbgs() << "[" << DEBUG_TYPE << "] Construct property "300 << getOpenMPContextTraitPropertyName(Property, "")301 << " was not nested properly.\n");302 return false;303 }304 305 // TODO: Verify SIMD306 }307 308 assert(isSubset<TraitProperty>(VMI.ConstructTraits, Ctx.ConstructTraits) &&309 "Broken invariant!");310 }311 312 if (MK == MK_ANY) {313 LLVM_DEBUG(dbgs() << "[" << DEBUG_TYPE314 << "] None of the properties was in the OpenMP context "315 "but match kind is any.\n");316 return false;317 }318 319 return true;320}321 322bool llvm::omp::isVariantApplicableInContext(323 const VariantMatchInfo &VMI, const OMPContext &Ctx,324 bool DeviceOrImplementationSetOnly) {325 return isVariantApplicableInContextHelper(326 VMI, Ctx, /* ConstructMatches */ nullptr, DeviceOrImplementationSetOnly);327}328 329static APInt getVariantMatchScore(const VariantMatchInfo &VMI,330 const OMPContext &Ctx,331 SmallVectorImpl<unsigned> &ConstructMatches) {332 APInt Score(64, 1);333 334 unsigned NoConstructTraits = VMI.ConstructTraits.size();335 for (unsigned Bit : VMI.RequiredTraits.set_bits()) {336 TraitProperty Property = TraitProperty(Bit);337 // If there is a user score attached, use it.338 if (VMI.ScoreMap.count(Property)) {339 const APInt &UserScore = VMI.ScoreMap.lookup(Property);340 assert(UserScore.uge(0) && "Expect non-negative user scores!");341 Score += UserScore.getZExtValue();342 continue;343 }344 345 switch (getOpenMPContextTraitSetForProperty(Property)) {346 case TraitSet::construct:347 // We handle the construct traits later via the VMI.ConstructTraits348 // container.349 continue;350 case TraitSet::implementation:351 // No effect on the score (implementation defined).352 continue;353 case TraitSet::user:354 // No effect on the score.355 continue;356 case TraitSet::device:357 // Handled separately below.358 break;359 case TraitSet::target_device:360 // TODO: Handling separately.361 break;362 case TraitSet::invalid:363 llvm_unreachable("Unknown trait set is not to be used!");364 }365 366 // device={kind(any)} is "as if" no kind selector was specified.367 if (Property == TraitProperty::device_kind_any)368 continue;369 if (Property == TraitProperty::target_device_kind_any)370 continue;371 372 switch (getOpenMPContextTraitSelectorForProperty(Property)) {373 case TraitSelector::device_kind:374 Score += (1ULL << (NoConstructTraits + 0));375 continue;376 case TraitSelector::device_arch:377 Score += (1ULL << (NoConstructTraits + 1));378 continue;379 case TraitSelector::device_isa:380 Score += (1ULL << (NoConstructTraits + 2));381 continue;382 case TraitSelector::target_device_kind:383 Score += (1ULL << (NoConstructTraits + 0));384 continue;385 case TraitSelector::target_device_arch:386 Score += (1ULL << (NoConstructTraits + 1));387 continue;388 case TraitSelector::target_device_isa:389 Score += (1ULL << (NoConstructTraits + 2));390 continue;391 default:392 continue;393 }394 }395 396 unsigned ConstructIdx = 0;397 assert(NoConstructTraits == ConstructMatches.size() &&398 "Mismatch in the construct traits!");399 for (TraitProperty Property : VMI.ConstructTraits) {400 assert(getOpenMPContextTraitSetForProperty(Property) ==401 TraitSet::construct &&402 "Ill-formed variant match info!");403 (void)Property;404 // ConstructMatches is the position p - 1 and we need 2^(p-1).405 Score += (1ULL << ConstructMatches[ConstructIdx++]);406 }407 408 LLVM_DEBUG(dbgs() << "[" << DEBUG_TYPE << "] Variant has a score of " << Score409 << "\n");410 return Score;411}412 413int llvm::omp::getBestVariantMatchForContext(414 const SmallVectorImpl<VariantMatchInfo> &VMIs, const OMPContext &Ctx) {415 416 APInt BestScore(64, 0);417 int BestVMIIdx = -1;418 const VariantMatchInfo *BestVMI = nullptr;419 420 for (unsigned u = 0, e = VMIs.size(); u < e; ++u) {421 const VariantMatchInfo &VMI = VMIs[u];422 423 SmallVector<unsigned, 8> ConstructMatches;424 // If the variant is not applicable its not the best.425 if (!isVariantApplicableInContextHelper(426 VMI, Ctx, &ConstructMatches,427 /* DeviceOrImplementationSetOnly */ false))428 continue;429 // Check if its clearly not the best.430 APInt Score = getVariantMatchScore(VMI, Ctx, ConstructMatches);431 if (Score.ult(BestScore))432 continue;433 // Equal score need subset checks.434 if (Score.eq(BestScore)) {435 // Strict subset are never best.436 if (isStrictSubset(VMI, *BestVMI))437 continue;438 // Same score and the current best is no strict subset so we keep it.439 if (!isStrictSubset(*BestVMI, VMI))440 continue;441 }442 // New best found.443 BestVMI = &VMI;444 BestVMIIdx = u;445 BestScore = Score;446 }447 448 return BestVMIIdx;449}450 451TraitSet llvm::omp::getOpenMPContextTraitSetKind(StringRef S) {452 return StringSwitch<TraitSet>(S)453#define OMP_TRAIT_SET(Enum, Str) .Case(Str, TraitSet::Enum)454#include "llvm/Frontend/OpenMP/OMPKinds.def"455 .Default(TraitSet::invalid);456}457 458TraitSet459llvm::omp::getOpenMPContextTraitSetForSelector(TraitSelector Selector) {460 switch (Selector) {461#define OMP_TRAIT_SELECTOR(Enum, TraitSetEnum, Str, ReqProp) \462 case TraitSelector::Enum: \463 return TraitSet::TraitSetEnum;464#include "llvm/Frontend/OpenMP/OMPKinds.def"465 }466 llvm_unreachable("Unknown trait selector!");467}468TraitSet469llvm::omp::getOpenMPContextTraitSetForProperty(TraitProperty Property) {470 switch (Property) {471#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \472 case TraitProperty::Enum: \473 return TraitSet::TraitSetEnum;474#include "llvm/Frontend/OpenMP/OMPKinds.def"475 }476 llvm_unreachable("Unknown trait set!");477}478StringRef llvm::omp::getOpenMPContextTraitSetName(TraitSet Kind) {479 switch (Kind) {480#define OMP_TRAIT_SET(Enum, Str) \481 case TraitSet::Enum: \482 return Str;483#include "llvm/Frontend/OpenMP/OMPKinds.def"484 }485 llvm_unreachable("Unknown trait set!");486}487 488TraitSelector llvm::omp::getOpenMPContextTraitSelectorKind(StringRef S,489 TraitSet Set) {490 if (Set == TraitSet::target_device && S == "kind")491 return TraitSelector::target_device_kind;492 if (Set == TraitSet::target_device && S == "arch")493 return TraitSelector::target_device_arch;494 if (Set == TraitSet::target_device && S == "isa")495 return TraitSelector::target_device_isa;496 return StringSwitch<TraitSelector>(S)497#define OMP_TRAIT_SELECTOR(Enum, TraitSetEnum, Str, ReqProp) \498 .Case(Str, TraitSelector::Enum)499#include "llvm/Frontend/OpenMP/OMPKinds.def"500 .Default(TraitSelector::invalid);501}502TraitSelector503llvm::omp::getOpenMPContextTraitSelectorForProperty(TraitProperty Property) {504 switch (Property) {505#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \506 case TraitProperty::Enum: \507 return TraitSelector::TraitSelectorEnum;508#include "llvm/Frontend/OpenMP/OMPKinds.def"509 }510 llvm_unreachable("Unknown trait set!");511}512StringRef llvm::omp::getOpenMPContextTraitSelectorName(TraitSelector Kind) {513 switch (Kind) {514#define OMP_TRAIT_SELECTOR(Enum, TraitSetEnum, Str, ReqProp) \515 case TraitSelector::Enum: \516 return Str;517#include "llvm/Frontend/OpenMP/OMPKinds.def"518 }519 llvm_unreachable("Unknown trait selector!");520}521 522TraitProperty llvm::omp::getOpenMPContextTraitPropertyKind(523 TraitSet Set, TraitSelector Selector, StringRef S) {524 // Special handling for `device={isa(...)}` as we accept anything here. It is525 // up to the target to decide if the feature is available.526 if (Set == TraitSet::device && Selector == TraitSelector::device_isa)527 return TraitProperty::device_isa___ANY;528 if (Set == TraitSet::target_device &&529 Selector == TraitSelector::target_device_isa)530 return TraitProperty::target_device_isa___ANY;531#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \532 if (Set == TraitSet::TraitSetEnum && Str == S) \533 return TraitProperty::Enum;534#include "llvm/Frontend/OpenMP/OMPKinds.def"535 return TraitProperty::invalid;536}537TraitProperty538llvm::omp::getOpenMPContextTraitPropertyForSelector(TraitSelector Selector) {539 return StringSwitch<TraitProperty>(540 getOpenMPContextTraitSelectorName(Selector))541#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \542 .Case(Str, Selector == TraitSelector::TraitSelectorEnum \543 ? TraitProperty::Enum \544 : TraitProperty::invalid)545#include "llvm/Frontend/OpenMP/OMPKinds.def"546 .Default(TraitProperty::invalid);547}548StringRef llvm::omp::getOpenMPContextTraitPropertyName(TraitProperty Kind,549 StringRef RawString) {550 if (Kind == TraitProperty::device_isa___ANY)551 return RawString;552 if (Kind == TraitProperty::target_device_isa___ANY)553 return RawString;554 switch (Kind) {555#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \556 case TraitProperty::Enum: \557 return Str;558#include "llvm/Frontend/OpenMP/OMPKinds.def"559 }560 llvm_unreachable("Unknown trait property!");561}562StringRef llvm::omp::getOpenMPContextTraitPropertyFullName(TraitProperty Kind) {563 switch (Kind) {564#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \565 case TraitProperty::Enum: \566 return "(" #TraitSetEnum "," #TraitSelectorEnum "," Str ")";567#include "llvm/Frontend/OpenMP/OMPKinds.def"568 }569 llvm_unreachable("Unknown trait property!");570}571 572bool llvm::omp::isValidTraitSelectorForTraitSet(TraitSelector Selector,573 TraitSet Set,574 bool &AllowsTraitScore,575 bool &RequiresProperty) {576 AllowsTraitScore = Set != TraitSet::construct && Set != TraitSet::device &&577 Set != TraitSet::target_device;578 switch (Selector) {579#define OMP_TRAIT_SELECTOR(Enum, TraitSetEnum, Str, ReqProp) \580 case TraitSelector::Enum: \581 RequiresProperty = ReqProp; \582 return Set == TraitSet::TraitSetEnum;583#include "llvm/Frontend/OpenMP/OMPKinds.def"584 }585 llvm_unreachable("Unknown trait selector!");586}587 588bool llvm::omp::isValidTraitPropertyForTraitSetAndSelector(589 TraitProperty Property, TraitSelector Selector, TraitSet Set) {590 switch (Property) {591#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \592 case TraitProperty::Enum: \593 return Set == TraitSet::TraitSetEnum && \594 Selector == TraitSelector::TraitSelectorEnum;595#include "llvm/Frontend/OpenMP/OMPKinds.def"596 }597 llvm_unreachable("Unknown trait property!");598}599 600std::string llvm::omp::listOpenMPContextTraitSets() {601 std::string S;602#define OMP_TRAIT_SET(Enum, Str) \603 if (StringRef(Str) != "invalid") \604 S.append("'").append(Str).append("'").append(" ");605#include "llvm/Frontend/OpenMP/OMPKinds.def"606 S.pop_back();607 return S;608}609 610std::string llvm::omp::listOpenMPContextTraitSelectors(TraitSet Set) {611 std::string S;612#define OMP_TRAIT_SELECTOR(Enum, TraitSetEnum, Str, ReqProp) \613 if (TraitSet::TraitSetEnum == Set && StringRef(Str) != "Invalid") \614 S.append("'").append(Str).append("'").append(" ");615#include "llvm/Frontend/OpenMP/OMPKinds.def"616 S.pop_back();617 return S;618}619 620std::string621llvm::omp::listOpenMPContextTraitProperties(TraitSet Set,622 TraitSelector Selector) {623 std::string S;624#define OMP_TRAIT_PROPERTY(Enum, TraitSetEnum, TraitSelectorEnum, Str) \625 if (TraitSet::TraitSetEnum == Set && \626 TraitSelector::TraitSelectorEnum == Selector && \627 StringRef(Str) != "invalid") \628 S.append("'").append(Str).append("'").append(" ");629#include "llvm/Frontend/OpenMP/OMPKinds.def"630 if (S.empty())631 return "<none>";632 S.pop_back();633 return S;634}635