Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions docs/DevelopersDocumentation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,54 @@ library files and run pytest:
python -m pip install pytest
python -m pytest -sv

***********************************
###################################
CppInterOp Internal Documentation
***********************************
###################################

CppInterOp maintains an internal Doxygen documentation of its components.
Internal documentation aims to capture intrinsic details and overall usage of
code components. The goal of internal documentation is to make the codebase
easier to understand for the new developers. Internal documentation can be
visited : `here <build/html/index.html>`_

**************************************
Multiple Interpreter & Thread-Safety
**************************************

CppInterOp allows the user to create multiple interpreters at a time and
use those interpreters. The interpreters that are created are stored in a
stack and a map. The stack is used to enable the model where the user
wants to create a temporary interpreter and destroy it after performing a
few operations. In such a use case, the top of the stack is the only
interpreter in use at any given point in time.

The map is used to store the mapping from :code:`clang::ASTContext` to
:code:`Cpp::InterpreterInfo`. This is required to figure out which
interpreter an object belongs to. Say the library user performs the
following operations:

1. Create an Interpreter
2. Compile some code with variable :code:`a`
3. Create another Interpreter
4. Performs :code:`Cpp::GetVariableOffset(a)`

In step 4, the top of the stack is an interpreter without the definition of
:code:`a`. And we cannot use it to figure out the address of :code:`a`.
The :code:`clang::Decl` passed to :code:`Cpp::GetVariableOffset` is used to
retrieve the :code:`clang::ASTContext`, using
:code:`clang::Decl::getASTContext`. We then use the map to figure out the
exact Interpreter Instance this :code:`clang::Decl` belongs to and perform
the operation.

A shortcoming of this is that if the CppInterOp accepts a
:code:`clang::QualType` instead of :code:`clang::Decl`, then it is not
possible to get the :code:`clang::ASTContext` from the :code:`clang::QualType`.
In such cases, we iterate over the Allocator of all the Interpreters in our
stack and figure out which :code:`clang::ASTContext` allocated this
:code:`clang::QualType`. This is a very expensive operation. But there is no
alternative to this.

For **thread-safety**, we introduce a lock for each of the interpreters we
create. And lock only that one specific interpreter when required. We also
have 2 global locks, one for LLVM, and another is used to lock operations
performed on the interpreter stack and the map itself.
5 changes: 5 additions & 0 deletions include/CppInterOp/CppInterOp.h
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,11 @@ CreateInterpreter(const std::vector<const char*>& Args = {},
///\returns false on failure or if \c I is not tracked in the stack.
CPPINTEROP_API bool DeleteInterpreter(TInterp_t I = nullptr);

/// Take ownership of an interpreter instance.
///\param[in] I - the interpreter to be taken, if nullptr, returns the last.
///\returns nullptr on failure or if \c I is not tracked in the stack.
CPPINTEROP_API TInterp_t TakeInterpreter(TInterp_t I = nullptr);

/// Activates an instance of an interpreter to handle subsequent API requests
///\param[in] I - the interpreter to be activated.
///\returns false on failure.
Expand Down
Loading
Loading