Index: include/llvm/Support/CommandLine.h =================================================================== --- include/llvm/Support/CommandLine.h +++ include/llvm/Support/CommandLine.h @@ -21,6 +21,7 @@ #define LLVM_SUPPORT_COMMANDLINE_H #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" @@ -1179,6 +1180,108 @@ applicator::opt(M, *O); } +struct Destructible { + virtual ~Destructible() = default; +}; + +//===----------------------------------------------------------------------===// +// ContextValues class + +// Set of options changed in a non-global context. +// For example, every thread can have a dedicated ContextValues (see the threads +// local variable ThreadOptionContext and SetThreadOptionContext) in which it +// can have its own set of option values not affecting other threads' contexts. +// It is thread safe to change the option values. +// +class ContextValues { + // Map from opt_storage* to context specific values of this opt + typedef DenseMap ValueMap; + ValueMap *LocalValues = nullptr; + + // Known problem: Many dead references to opt_storage may pollute the ValueMap + // if there are many temporary options that have changed their values + // in a non-global context (e.g. stack allocated cl::opt instances). In this + // case a new reference may point to an old dead one and getValue() may result + // in accessing bad memory. To prevent this we should not change short lived + // options in non-global contexts. +protected: + template + opt_storage *findStorageInternal(opt_storage *Opt) const { + if (!LocalValues) + return nullptr; + + auto Found = LocalValues->find(Opt); + if (Found != LocalValues->end()) + return reinterpret_cast(Found->getSecond()); + + return nullptr; + } + +public: + static LLVM_THREAD_LOCAL ContextValues *ThreadOptionContext; + static void SetThreadOptionContext(ContextValues *C) { ThreadOptionContext = C; } + + ~ContextValues() { + // Unbind current thread from deleted context. + if (ThreadOptionContext && ThreadOptionContext == this) + ThreadOptionContext = nullptr; + + // Free memory allocated for values. + if (LocalValues) + for (auto KeyValue : *LocalValues) + delete KeyValue.getSecond(); + } + + // Looks for a local storage for Opt. Returns Opt as a fallback: if + // the current thread context is not set or if there is no local value for + // Opt in the context. + // Never returns null if Opt != null. + template + static opt_storage *findStorage(opt_storage *Opt) { + if (auto *CV = ThreadOptionContext) + if (auto *Storage = CV->findStorageInternal(Opt)) + return Storage; + return Opt; + } + + // Looks for a local storage for Opt. Returns Opt as a fallback: if + // the current thread context is not set or if there is no local value for + // Opt in the context. + // Never returns null if Opt != null. + template + static const opt_storage *findStorage(const opt_storage *Opt) { + if (auto *CV = ThreadOptionContext) + if (CV->LocalValues != nullptr) { + auto Found = CV->LocalValues->find(Opt); + if (Found != CV->LocalValues->end()) + return reinterpret_cast(Found->getSecond()); + } + return Opt; + } + + // Allocates a local storage for a local value of Opt in the current thread + // context if it is set, otherwise returns the default storage. + // Never returns null if Opt != null. + template + static opt_storage *getStorage(opt_storage *Opt, bool initial) { + auto *CV = ThreadOptionContext; + if (!CV) + return Opt; + + if (!CV->LocalValues) + CV->LocalValues = new ValueMap(); + else if (auto *Storage = CV->findStorageInternal(Opt)) + return Storage; + + auto *Storage = new opt_storage(); + if (!initial) + Storage->Default = Opt->Default; + + CV->LocalValues->try_emplace(Opt, (Destructible*)Storage); + return Storage; + } +}; + //===----------------------------------------------------------------------===// // opt_storage class @@ -1187,7 +1290,8 @@ // cl::location(x) modifier. // template -class opt_storage { +class opt_storage : public Destructible { + friend ContextValues; DataType *Location = nullptr; // Where to store the object... OptionValue Default; @@ -1199,29 +1303,35 @@ public: opt_storage() = default; + virtual ~opt_storage() = default; bool setLocation(Option &O, DataType &L) { - if (Location) + auto *Storage = ContextValues::getStorage(this, true); + if (Storage->Location) return O.error("cl::location(x) specified more than once!"); - Location = &L; - Default = L; + Storage->Location = &L; + Storage->Default = L; return false; } template void setValue(const T &V, bool initial = false) { - check_location(); - *Location = V; + auto *Storage = ContextValues::getStorage(this, initial); + Storage->check_location(); + *(Storage->Location) = V; if (initial) - Default = V; + Storage->Default = V; } DataType &getValue() { - check_location(); - return *Location; + auto *Storage = ContextValues::findStorage(this); + Storage->check_location(); + return *(Storage->Location); } + const DataType &getValue() const { - check_location(); - return *Location; + const auto *Storage = ContextValues::findStorage(this); + Storage->check_location(); + return *(Storage->Location); } operator DataType() const { return this->getValue(); } @@ -1234,23 +1344,37 @@ // object in all cases that it is used. // template -class opt_storage { +class opt_storage : public Destructible { public: DataType Value; OptionValue Default; opt_storage() : Value(DataType()), Default(DataType()) {} + virtual ~opt_storage() = default; + template void setValue(const T &V, bool initial = false) { - Value = V; + auto *Storage = ContextValues::getStorage(this, initial); + + Storage->Value = V; if (initial) - Default = V; + Storage->Default = V; } - DataType &getValue() { return Value; } - const DataType &getValue() const { return Value; } + DataType &getValue() { + auto *Storage = ContextValues::findStorage(this); + return Storage->Value; + } - const OptionValue &getDefault() const { return Default; } + const DataType &getValue() const { + auto *Storage = ContextValues::findStorage(this); + return Storage->Value; + } + + const OptionValue &getDefault() const { + auto *Storage = ContextValues::findStorage(this); + return Storage->Default; + } operator DataType() const { return getValue(); } @@ -1269,7 +1393,7 @@ // this case, we store an instance through containment, and overload operators // to get at the value. // -template class opt_storage { +template class opt_storage : public Destructible { public: DataType Value; OptionValue Default; @@ -1278,20 +1402,35 @@ // type. opt_storage() : Value(DataType()), Default(DataType()) {} + virtual ~opt_storage() = default; + template void setValue(const T &V, bool initial = false) { - Value = V; + auto *Storage = ContextValues::getStorage(this, initial); + + Storage->Value = V; if (initial) - Default = V; + Storage->Default = V; } - DataType &getValue() { return Value; } - DataType getValue() const { return Value; } - const OptionValue &getDefault() const { return Default; } + DataType &getValue() { + auto *Storage = ContextValues::findStorage(this); + return Storage->Value; + } + + const DataType &getValue() const { + auto *Storage = ContextValues::findStorage(this); + return Storage->Value; + } + + const OptionValue &getDefault() const { + auto *Storage = ContextValues::findStorage(this); + return Storage->Default; + } operator DataType() const { return getValue(); } // If the datatype is a pointer, support -> on it. - DataType operator->() const { return Value; } + DataType operator->() const { return getValue(); } }; //===----------------------------------------------------------------------===// Index: lib/Support/CommandLine.cpp =================================================================== --- lib/Support/CommandLine.cpp +++ lib/Support/CommandLine.cpp @@ -2242,3 +2242,5 @@ llvm::cl::ParseCommandLineOptions(argc, argv, StringRef(Overview), &llvm::nulls()); } + +LLVM_THREAD_LOCAL cl::ContextValues *cl::ContextValues::ThreadOptionContext = nullptr; Index: unittests/Support/CommandLineTest.cpp =================================================================== --- unittests/Support/CommandLineTest.cpp +++ unittests/Support/CommandLineTest.cpp @@ -22,6 +22,12 @@ #include #include +#ifdef HAVE_PTHREAD_H +#include +#include +#include +#endif + using namespace llvm; namespace { @@ -840,4 +846,110 @@ } #endif +TEST(CommandLineTest, ContextSpecificValues) { + StackOption SO("test-option-ContextSpecificValues1", cl::init(9)); + + cl::ContextValues Ctx1; + + { // scope for Ctx2 + cl::ContextValues Ctx2; + + EXPECT_EQ(SO, 9); + SO=0; + EXPECT_EQ(SO, 0); + + cl::ContextValues::SetThreadOptionContext(&Ctx1); + EXPECT_EQ(SO, 0); + SO = 1; + EXPECT_EQ(SO, 1); + + cl::ContextValues::SetThreadOptionContext(&Ctx2); + EXPECT_EQ(SO, 0); + SO = 2; + EXPECT_EQ(SO, 2); + + { // scope for SO2 + cl::ContextValues::SetThreadOptionContext(nullptr); + StackOption SO2("test-option-ContextSpecificValues2", cl::init(29)); + + EXPECT_EQ(SO, 0); + EXPECT_EQ(SO2, 29); + SO2 = 20; + EXPECT_EQ(SO, 0); + + cl::ContextValues::SetThreadOptionContext(&Ctx1); + EXPECT_EQ(SO, 1); + EXPECT_EQ(SO2, 20); + SO2 = 21; + EXPECT_EQ(SO, 1); + + cl::ContextValues::SetThreadOptionContext(&Ctx2); + // SO2 = 22; don't set + EXPECT_EQ(SO, 2); + + cl::ContextValues::SetThreadOptionContext(nullptr); + EXPECT_EQ(SO2, 20); + + cl::ContextValues::SetThreadOptionContext(&Ctx1); + SO2 = 21; + EXPECT_EQ(SO2, 21); + + cl::ContextValues::SetThreadOptionContext(&Ctx2); + EXPECT_EQ(SO2, 20); + } // end of SO2 + + cl::ContextValues::SetThreadOptionContext(nullptr); + EXPECT_EQ(SO, 0); + + cl::ContextValues::SetThreadOptionContext(&Ctx1); + EXPECT_EQ(SO, 1); + + cl::ContextValues::SetThreadOptionContext(&Ctx2); + EXPECT_EQ(SO, 2); + } // end of Ctx2 + + cl::ContextValues::SetThreadOptionContext(nullptr); + EXPECT_EQ(SO, 0); + + cl::ContextValues::SetThreadOptionContext(&Ctx1); + EXPECT_EQ(SO, 1); +} + +#if LLVM_ENABLE_THREADS != 0 && defined(HAVE_PTHREAD_H) + +static cl::opt option_str("test-option-str", cl::init("-")); +static cl::opt option_int("test-option-int", cl::init(95)); +static volatile std::atomic_int atomic_int{0}; + +static void *thread_routine(void*) { + cl::ContextValues Ctx; + cl::ContextValues::SetThreadOptionContext(&Ctx); + for(int i = 0; i < 10; ++i) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + int value = ++atomic_int; + option_int = value; + option_str = std::to_string(value); + EXPECT_EQ(option_int, value); + EXPECT_EQ(*option_str, std::to_string(value)); + } + + return nullptr; +} + +TEST(CommandLineTest, ContextSpecificValues2) { + EXPECT_EQ(option_int, 95); + EXPECT_EQ(*option_str, "-"); + + pthread_t t1, t2; + pthread_create(&t1, nullptr, thread_routine, nullptr); + pthread_create(&t2, nullptr, thread_routine, nullptr); + pthread_join(t1, nullptr); + pthread_join(t2, nullptr); + + // unchanged default values + EXPECT_EQ(option_int, 95); + EXPECT_EQ(*option_str, "-"); +} +#endif + } // anonymous namespace