-
Notifications
You must be signed in to change notification settings - Fork 30
add mutex per interpreter per thread #698
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #698 +/- ##
==========================================
+ Coverage 79.83% 80.34% +0.50%
==========================================
Files 9 9
Lines 3962 4115 +153
==========================================
+ Hits 3163 3306 +143
- Misses 799 809 +10
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy made some suggestions
There were too many comments to post at once. Showing the first 10 out of 13. Check the log or trigger a new build to see more.
@@ -125,8 +193,17 @@ | |||
|
|||
// std::deque avoids relocations and calling the dtor of InterpreterInfo. | |||
static llvm::ManagedStatic<std::deque<InterpreterInfo>> sInterpreters; | |||
static std::mutex InterpreterStackLock; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'InterpreterStackLock' is non-const and globally accessible, consider making it const [cppcoreguidelines-avoid-non-const-global-variables]
static std::mutex InterpreterStackLock;
^
lib/CppInterOp/CppInterOp.cpp
Outdated
auto* D = (clang::Decl*)handle; | ||
std::vector<TCppScope_t> GetEnumConstants(TCppScope_t scope) { | ||
LOCK(getInterpInfo()); | ||
auto* D = (clang::Decl*)scope; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]
auto* D = (clang::Decl*)scope;
^
@@ -224,11 +301,18 @@ std::string Demangle(const std::string& mangled_name) { | |||
return demangle; | |||
} | |||
|
|||
void EnableDebugOutput(bool value /* =true*/) { llvm::DebugFlag = value; } | |||
void EnableDebugOutput(bool value /* =true*/) { | |||
LOCK(getInterpInfo()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had a different mental model for this. I was thinking that we can have a StateMutatingSection
RAII object which in turn has a mutex-based locking (if multithreading was enabled) and a 'PushTransactionRAII` (for pch/modules). The object should take a parameter if the change is in clang AST which might trigger a derserialiazation or llvm-only. In the latter case we can skip pushing a transaction.
There will be cases where we need one of the two concepts and not both. We need to design a flexible solution addressing these needs without having to introduce both concepts at the same time when they are not needed.
lib/CppInterOp/CppInterOp.cpp
Outdated
if (result.empty()) | ||
Cpp::Declare("#include <utility>"); | ||
{ | ||
LOCK(getInterpInfo()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lock should be done in Declare
. The next 2-3 lines should not be changing the compiler state (unless modules are involved)...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we should lock the AST when we do any named lookups. What if some other thread is writing to the AST at the same time? These things should be atomic. Either we finish the read and perform the write, or finish the write and then perform the read. We cannot be writing and reading the symbol table at the same time.
But say we want to check the number of arguments of a function, that would not change once the function is parsed. So we can read that data without any locks.
b689053
to
7659fcb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy made some suggestions
@@ -125,8 +133,17 @@ struct InterpreterInfo { | |||
|
|||
// std::deque avoids relocations and calling the dtor of InterpreterInfo. | |||
static llvm::ManagedStatic<std::deque<InterpreterInfo>> sInterpreters; | |||
static std::mutex InterpreterStackLock; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'sInterpreters' is non-const and globally accessible, consider making it const [cppcoreguidelines-avoid-non-const-global-variables]
static llvm::ManagedStatic<std::deque<InterpreterInfo>> sInterpreters;
^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add the relevant suppression comments for clang-tidy?
@@ -125,8 +133,17 @@ | |||
|
|||
// std::deque avoids relocations and calling the dtor of InterpreterInfo. | |||
static llvm::ManagedStatic<std::deque<InterpreterInfo>> sInterpreters; | |||
static std::mutex InterpreterStackLock; | |||
|
|||
static InterpreterInfo& getInterpInfo() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'InterpreterStackLock' is non-const and globally accessible, consider making it const [cppcoreguidelines-avoid-non-const-global-variables]
static std::mutex InterpreterStackLock;
^
lib/CppInterOp/CppInterOp.cpp
Outdated
@@ -350,8 +379,10 @@ | |||
|
|||
bool IsAbstract(TCppType_t klass) { | |||
auto* D = (clang::Decl*)klass; | |||
if (auto* CXXRD = llvm::dyn_cast_or_null<clang::CXXRecordDecl>(D)) | |||
if (auto* CXXRD = llvm::dyn_cast_or_null<clang::CXXRecordDecl>(D)) { | |||
LOCK(getInterpInfo()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]
auto* D = (clang::Decl*)klass;
^
lib/CppInterOp/CppInterOp.cpp
Outdated
@@ -451,6 +483,7 @@ | |||
std::vector<TCppScope_t> GetEnumConstants(TCppScope_t handle) { | |||
auto* D = (clang::Decl*)handle; | |||
|
|||
LOCK(getInterpInfo()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]
auto* D = (clang::Decl*)handle;
^
@@ -822,6 +861,9 @@ | |||
return -1; | |||
CXXRecordDecl* DCXXRD = cast<CXXRecordDecl>(DD); | |||
CXXRecordDecl* BCXXRD = cast<CXXRecordDecl>(BD); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: use auto when initializing with a template cast to avoid duplicating the type name [modernize-use-auto]
auto* DCXXRD = cast<CXXRecordDecl>(DD); |
@@ -1862,6 +1927,7 @@ | |||
if (!builtin.isNull()) | |||
return builtin.getAsOpaquePtr(); | |||
|
|||
LOCK(getInterpInfo()); | |||
auto* D = (Decl*)GetNamed(name, /* Within= */ 0); | |||
if (auto* TD = llvm::dyn_cast_or_null<TypeDecl>(D)) { | |||
return QualType(TD->getTypeForDecl(), 0).getAsOpaquePtr(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: argument name 'Within' in comment does not match parameter name 'parent' [bugprone-argument-comment]
auto* D = (Decl*)GetNamed(name, /* Within= */ 0);
^
Additional context
include/CppInterOp/CppInterOp.h:411: 'parent' declared here
TCppScope_t parent = nullptr);
^
lib/CppInterOp/CppInterOp.cpp:718: actual callee ('GetNamed') is declared here
TCppScope_t GetNamed(const std::string& name,
^
7659fcb
to
46697ce
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy made some suggestions
There were too many comments to post at once. Showing the first 10 out of 19. Check the log or trigger a new build to see more.
@@ -126,15 +134,50 @@ struct InterpreterInfo { | |||
}; | |||
|
|||
// std::deque avoids relocations and calling the dtor of InterpreterInfo. | |||
static llvm::ManagedStatic<std::deque<InterpreterInfo>> sInterpreters; | |||
static llvm::ManagedStatic<std::deque<std::shared_ptr<InterpreterInfo>>> | |||
sInterpreters; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'sInterpreters' is non-const and globally accessible, consider making it const [cppcoreguidelines-avoid-non-const-global-variables]
sInterpreters;
^
static llvm::ManagedStatic<std::deque<std::shared_ptr<InterpreterInfo>>> | ||
sInterpreters; | ||
static llvm::ManagedStatic< | ||
std::unordered_map<clang::ASTContext*, std::weak_ptr<InterpreterInfo>>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: no header providing "std::unordered_map" is directly included [misc-include-cleaner]
lib/CppInterOp/CppInterOp.cpp:40:
- #if CLANG_VERSION_MAJOR >= 19
+ #include <unordered_map>
+ #if CLANG_VERSION_MAJOR >= 19
sInterpreters; | ||
static llvm::ManagedStatic< | ||
std::unordered_map<clang::ASTContext*, std::weak_ptr<InterpreterInfo>>> | ||
sInterpreterASTMap; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: variable 'sInterpreterASTMap' is non-const and globally accessible, consider making it const [cppcoreguidelines-avoid-non-const-global-variables]
sInterpreterASTMap;
^
lib/CppInterOp/CppInterOp.cpp
Outdated
@@ -684,13 +747,14 @@ | |||
TCppScope_t GetNamed(const std::string& name, | |||
TCppScope_t parent /*= nullptr*/) { | |||
clang::DeclContext* Within = 0; | |||
auto* D = (clang::Decl*)parent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]
auto* D = (clang::Decl*)parent;
^
D = GetUnderlyingScope(D); | ||
Within = llvm::dyn_cast<clang::DeclContext>(D); | ||
} | ||
|
||
auto* ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); | ||
auto* ND = Cpp_utils::Lookup::Named(&getSema(D), name, Within); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: no header providing "Cpp::utils::Lookup::Named" is directly included [misc-include-cleaner]
lib/CppInterOp/CppInterOp.cpp:12:
+ #include "CppInterOpInterpreter.h"
lib/CppInterOp/CppInterOp.cpp
Outdated
@@ -734,7 +801,11 @@ | |||
TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase) { | |||
auto* D = (Decl*)klass; | |||
auto* CXXRD = llvm::dyn_cast_or_null<CXXRecordDecl>(D); | |||
if (!CXXRD || CXXRD->getNumBases() <= ibase) | |||
if (!CXXRD) | |||
return 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: use nullptr [modernize-use-nullptr]
return 0; | |
return nullptr; |
lib/CppInterOp/CppInterOp.cpp
Outdated
return interp.getCI()->getSema().LookupDefaultConstructor(CXXRD); | ||
} | ||
|
||
TCppFunction_t GetDefaultConstructor(TCppScope_t scope) { | ||
return GetDefaultConstructor(getInterp(), scope); | ||
auto* CXXRD = (clang::CXXRecordDecl*)scope; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]
auto* CXXRD = (clang::CXXRecordDecl*)scope;
^
lib/CppInterOp/CppInterOp.cpp
Outdated
|
||
auto* D = (clang::Decl*)func; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]
auto* D = (clang::Decl*)func;
^
@@ -1085,12 +1172,14 @@ | |||
// the template function exists and >1 means overloads | |||
bool ExistsFunctionTemplate(const std::string& name, TCppScope_t parent) { | |||
DeclContext* Within = 0; | |||
auto* D = (Decl*)parent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]
auto* D = (Decl*)parent;
^
lib/CppInterOp/CppInterOp.cpp
Outdated
auto& S = getSema(); | ||
if (candidates.empty()) | ||
return nullptr; | ||
InterpreterInfo& II = getInterpInfo((clang::Decl*)candidates[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]
InterpreterInfo& II = getInterpInfo((clang::Decl*)candidates[0]);
^
@@ -361,3 +361,22 @@ if (llvm::sys::RunningOnValgrind()) | |||
delete ExtInterp; | |||
#endif | |||
} | |||
|
|||
TEST(InterpreterTest, MultipleInterpreter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will need a proper readthedocs design document, too :)
46697ce
to
68d616d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clang-tidy made some suggestions
@@ -1349,7 +1451,7 @@ TCppFuncAddr_t GetFunctionAddress(const char* mangled_name) { | |||
|
|||
static TCppFuncAddr_t GetFunctionAddress(const FunctionDecl* FD) { | |||
const auto get_mangled_name = [](const FunctionDecl* FD) { | |||
auto MangleCtxt = getASTContext().createMangleContext(); | |||
auto MangleCtxt = getASTContext(FD).createMangleContext(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: 'auto MangleCtxt' can be declared as 'auto *MangleCtxt' [llvm-qualified-auto]
auto MangleCtxt = getASTContext(FD).createMangleContext(); | |
auto *MangleCtxt = getASTContext(FD).createMangleContext(); |
static Decl* InstantiateTemplate(TemplateDecl* TemplateD, | ||
TemplateArgumentListInfo& TLI, Sema& S, | ||
bool instantiate_body) { | ||
Decl* InstantiateTemplate(TemplateDecl* TemplateD, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: function 'InstantiateTemplate' can be made static or moved into an anonymous namespace to enforce internal linkage [misc-use-internal-linkage]
Decl* InstantiateTemplate(TemplateDecl* TemplateD, | |
static Decl* InstantiateTemplate(TemplateDecl* TemplateD, |
Cpp::Declare(R"( | ||
void f() {} | ||
)"); | ||
auto f = Cpp::GetNamed("f"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: 'auto f' can be declared as 'auto *f' [llvm-qualified-auto]
auto f = Cpp::GetNamed("f"); | |
auto *f = Cpp::GetNamed("f"); |
Cpp::Declare(R"( | ||
void ff() {} | ||
)"); | ||
auto ff = Cpp::GetNamed("ff"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warning: 'auto ff' can be declared as 'auto *ff' [llvm-qualified-auto]
auto ff = Cpp::GetNamed("ff"); | |
auto *ff = Cpp::GetNamed("ff"); |
A note for this PR. The Emscripten build of CppInterOp doesn't currently support threads
It is possible to turns threads on, and modify CppInterOp, so that it builds with threads. I did this locally yesterday (I did this in the past before we had tests). The tests can run, but several of our currently passing tests fail with threads enabled. We also get a warning about shared memory for the passing tests. I can put a PR showing my current progress, but I don't know how far I would get enabling past what I already have since shared library with pthreads is labelled as experimental on Emscripten, so we might be trying to fight possible bugs in Emscripten. |
@@ -361,3 +361,22 @@ if (llvm::sys::RunningOnValgrind()) | |||
delete ExtInterp; | |||
#endif | |||
} | |||
|
|||
TEST(InterpreterTest, MultipleInterpreter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its probably fine to disable this test for llvm 19 Emscripten builds, since the llvm 20 Emscripten build passes. There are already many tests we have passing for llvm 20, but not llvm 19.
68d616d
to
47d1f58
Compare
@@ -0,0 +1,2 @@ | |||
Checks: > | |||
-cppcoreguidelines-avoid-non-const-global-variables |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant // NOLINTNEXTLINE or something..
We need to identify which interpreter a Decl belongs to, when using multiple interpreter. We do it by checking which `clang::ASTContext` the `clang::Decl` belongs We maintain a map: `clang::ASTContext -> Cpp::InterpreterInfo`. Using this map, be identify the correct interpreter. There are 2 usecases for this: 1. We can now lock the correct interpreter making it thread safe. 2. User of `libCppInterOp` need not set the correct active interpreter using `Cpp::ActivateInterpreter`, this information can be retrived using the map.
47d1f58
to
e6a33f8
Compare
Big changes in this PR. Some parts of the explanation might be like a conversation.
Mutex lock per interpreter.
Question: Do we need a per-interpreter lock?
Answer: Yes. Ideally, that would improve the performance in multi-threaded cases.
Question: Does the InterOp API fully support dispatching queries (some kind of lookup) or compiling code across multiple interpreters at the same time?
Answer: Actually, (I guess) No. Our API only supports stack-like access of multiple interpreters.
Example code that will not work:
But you can make the above example work, if the user rotates the
sInterpreters
.Example:
Question: Ok. So is there a way for InterOp is maintain the rotation thing?
Answer: Would not be a easy thing to achieve. From my initial view on the matter, it might be possible. But a more important question; Should we support such usecases?
Question: Ok. So given the limitation of using multiple interpreter but only as a stack-access. Do we need a mutex lock per interpreter?
Answer: No, the user can only access the top most interpreter at a time. We don't need a mutex per interpreter. (Let me know if my reasoning is wrong somewhere)
Testing this by running in parallel
gtest does not support running in parallel (I could not find anything with my google search). But there is this project, gtest-parallel, that can split the tasks into separate isolated processes. But that is not what we want. We want to split the tests to run in parallel threads that share the same interpreter instance.
Code recovery RAII
@vgvassilev, I need some pointers on incorporating the codegen error recovery RAII. I don't think we should combine the two into the same class, but let me know your opinion. And how should I go about doing it?