diff --git a/.github/actions/Build_LLVM/action.yml b/.github/actions/Build_LLVM/action.yml index 53fd301a8..468d0fe88 100644 --- a/.github/actions/Build_LLVM/action.yml +++ b/.github/actions/Build_LLVM/action.yml @@ -43,6 +43,10 @@ runs: ninja LLVMOrcDebugging -j ${{ env.ncpus }} ninja clingInterpreter -j ${{ env.ncpus }} else + if [[ "${{ matrix.oop-jit }}" == "On" ]]; then + git apply -v ../patches/llvm/clang20-1-out-of-process.patch + echo "Apply clang20-1-out-of-process.patch:" + fi cd build cmake -DLLVM_ENABLE_PROJECTS="${{ matrix.llvm_enable_projects}}" \ -DLLVM_TARGETS_TO_BUILD="${{ matrix.llvm_targets_to_build }}" \ @@ -58,6 +62,14 @@ runs: -DLLVM_INCLUDE_TESTS=OFF \ ../llvm ninja clang clangInterpreter clangStaticAnalyzerCore -j ${{ env.ncpus }} + if [[ "${{ matrix.oop-jit }}" == "On" ]]; then + if [[ "${{ matrix.os }}" == macos* ]]; then + SUFFIX="_osx" + elif [[ "${{ matrix.os }}" == ubuntu* ]]; then + SUFFIX="-x86_64" + fi + ninja llvm-jitlink-executor orc_rt${SUFFIX} -j ${{ env.ncpus }} + fi cd ./tools/ rm -rf $(find . -maxdepth 1 ! -name "clang" ! -name ".") cd .. diff --git a/.github/actions/Build_and_Test_CppInterOp/action.yml b/.github/actions/Build_and_Test_CppInterOp/action.yml index b97d2e8ae..804b4b4e2 100644 --- a/.github/actions/Build_and_Test_CppInterOp/action.yml +++ b/.github/actions/Build_and_Test_CppInterOp/action.yml @@ -1,6 +1,7 @@ name: 'Builds and test CppInterOp' description: 'This action builds and tests CppInterOp for native platforms' +# REVERT BEFORE MERGING runs: using: composite steps: @@ -48,6 +49,7 @@ runs: -DCODE_COVERAGE=${{ env.CODE_COVERAGE }} \ -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR \ -DLLVM_ENABLE_WERROR=On \ + -DLLVM_BUILT_WITH_OOP_JIT=${{ matrix.oop-jit }} \ ../ fi docs_on=$(echo "${{ matrix.documentation }}" | tr '[:lower:]' '[:upper:]') @@ -58,7 +60,17 @@ runs: cmake --build . --target check-cppinterop --parallel ${{ env.ncpus }} os="${{ matrix.os }}" if [[ "${os}" != "macos"* ]]; then - valgrind --show-error-list=yes --track-origins=yes --error-exitcode=1 unittests/CppInterOp/CppInterOpTests/unittests/bin/${{ env.BUILD_TYPE }}/CppInterOpTests + valgrind \ + --leak-check=full \ + --show-leak-kinds=all \ + --track-origins=yes \ + --gen-suppressions=all \ + --log-fd=9 \ + unittests/CppInterOp/CppInterOpTests/unittests/bin/${{ env.BUILD_TYPE }}/CppInterOpTests \ + 9>>memcheck.log + + echo "======= Valgrind log (for suppression generation) =======" + cat memcheck.log fi fi echo "CB_PYTHON_DIR=$CB_PYTHON_DIR" >> $GITHUB_ENV diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ac9450d0b..dd9e3ccf3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,15 +23,6 @@ jobs: matrix: include: # Ubuntu Arm Jobs - - name: ubu22-arm-gcc12-clang-repl-20-coverage - os: ubuntu-22.04-arm - compiler: gcc-12 - clang-runtime: '20' - cling: Off - cppyy: Off - llvm_enable_projects: "clang" - llvm_targets_to_build: "host;NVPTX" - coverage: true - name: ubu24-arm-gcc12-clang-repl-20 os: ubuntu-24.04-arm compiler: gcc-12 @@ -66,6 +57,16 @@ jobs: llvm_enable_projects: "clang" llvm_targets_to_build: "host;NVPTX" # Ubuntu X86 Jobs + - name: ubu22-x86-gcc12-clang-repl-20-coverage + os: ubuntu-22.04 + compiler: gcc-12 + clang-runtime: '20' + cling: Off + cppyy: Off + llvm_enable_projects: "clang;compiler-rt" + llvm_targets_to_build: "host;NVPTX" + coverage: true + oop-jit: On - name: ubu24-x86-gcc12-clang-repl-20 os: ubuntu-24.04 compiler: gcc-12 @@ -74,6 +75,15 @@ jobs: cppyy: Off llvm_enable_projects: "clang" llvm_targets_to_build: "host;NVPTX" + - name: ubu24-x86-gcc12-clang-repl-20-out-of-process + os: ubuntu-24.04 + compiler: gcc-12 + clang-runtime: '20' + cling: Off + cppyy: Off + llvm_enable_projects: "clang;compiler-rt" + llvm_targets_to_build: "host;NVPTX" + oop-jit: On - name: ubu24-x86-gcc12-clang-repl-19-cppyy os: ubuntu-24.04 compiler: gcc-12 @@ -100,6 +110,15 @@ jobs: llvm_enable_projects: "clang" llvm_targets_to_build: "host;NVPTX" # MacOS Arm Jobs + - name: osx15-arm-clang-clang-repl-20-out-of-process + os: macos-15 + compiler: clang + clang-runtime: '20' + cling: Off + cppyy: Off + llvm_enable_projects: "clang;compiler-rt" + llvm_targets_to_build: "host" + oop-jit: On - name: osx15-arm-clang-clang-repl-20 os: macos-15 compiler: clang @@ -220,7 +239,7 @@ jobs: path: | llvm-project ${{ matrix.cling=='On' && 'cling' || '' }} - key: ${{ env.CLING_HASH }}-${{ runner.os }}-${{ matrix.os }}-${{ matrix.compiler }}-clang-${{ matrix.clang-runtime }}.x-patch-${{ hashFiles(format('patches/llvm/clang{0}-*.patch', matrix.clang-runtime)) || 'none' }} + key: ${{ env.CLING_HASH }}-${{ runner.os }}-${{ matrix.os }}-${{ matrix.compiler }}-clang-${{ matrix.clang-runtime }}.x-patch-${{ hashFiles(format('patches/llvm/clang{0}-*.patch', matrix.clang-runtime)) || 'none' }}${{ matrix.oop-jit == 'On' && '-oop' || '' }} - name: Setup default Build Type uses: ./.github/actions/Miscellaneous/Select_Default_Build_Type @@ -236,14 +255,16 @@ jobs: with: cache-hit: ${{ steps.cache.outputs.cache-hit }} - - name: Cache LLVM-${{ matrix.clang-runtime }} and ${{ matrix.cling == 'On' && 'Cling' || 'Clang-REPL' }} build - uses: actions/cache/save@v4 - if: ${{ steps.cache.outputs.cache-hit != 'true' }} - with: - path: | - llvm-project - ${{ matrix.cling=='On' && 'cling' || '' }} - key: ${{ steps.cache.outputs.cache-primary-key }} +# REVERT BEFORE MERGING + # - name: Cache LLVM-${{ matrix.clang-runtime }} and ${{ matrix.cling == 'On' && 'Cling' || 'Clang-REPL' }} build + # uses: actions/cache/save@v4 + # if: ${{ steps.cache.outputs.cache-hit != 'true' }} + # with: + # path: | + # llvm-project + # ${{ matrix.cling=='On' && 'cling' || '' }} + # key: ${{ steps.cache.outputs.cache-primary-key }} +# REVERT BEFORE MERGING - name: Setup code coverage if: ${{ success() && (matrix.coverage == true) }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d1aa034d..181e03b7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,6 +298,18 @@ include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) add_definitions(${LLVM_DEFINITIONS_LIST}) +string(REGEX REPLACE "/lib/cmake/llvm$" "" LLVM_BUILD_LIB_DIR "${LLVM_DIR}") +add_definitions(-DLLVM_BUILD_LIB_DIR="${LLVM_BUILD_LIB_DIR}") + +if(LLVM_BUILT_WITH_OOP_JIT) + if((CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64") OR + (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")) + add_definitions(-DLLVM_BUILT_WITH_OOP_JIT) + else() + message(FATAL_ERROR "LLVM_BUILT_WITH_OOP_JIT is only supported on Darwin arm64 or Linux x86_64. Build aborted.") + endif() +endif() + # If the llvm sources are present add them with higher priority. if (LLVM_BUILD_MAIN_SRC_DIR) # LLVM_INCLUDE_DIRS contains the include paths to both LLVM's source and diff --git a/README.md b/README.md index e14a94d94..b4809b63c 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,13 @@ git clone --depth=1 --branch release/20.x https://github.com/llvm/llvm-project.g cd llvm-project ``` +If you want to have out-of-process JIT execution enabled in CppInterOp, then apply this patch on Linux-x86_64 and MacOS-Darwin environment. +> Note that this patch will not work for Windows because out-of-process JIT execution is currently implemented for Linux-x86_64 and MacOS-Darwin only. + +```bash +git apply -v ../CppInterOp/patches/llvm/clang20-1-out-of-process.patch +``` + ##### Build Clang-REPL Clang-REPL is an interpreter that CppInterOp works alongside. Build Clang (and @@ -140,6 +147,45 @@ export LLVM_DIR=$PWD cd ../ ``` +##### Build Clang-REPL with Out-of-Process JIT Execution + +To have ``Out-of-Process JIT Execution`` enabled, run following commands to build clang and clang-repl to support this feature: +> Only for Linux x86_64 and Macos amr64 + +```bash +mkdir build +cd build +cmake -DLLVM_ENABLE_PROJECTS="clang;compiler-rt" \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + ../llvm +``` + +###### For Linux x86_64 + +```bash +cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt-x86_64 --parallel $(nproc --all) +``` + +###### For MacOS arm64 + +```bash +cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt_osx --parallel $(sysctl -n hw.ncpu) +``` + +Note the 'llvm-project' directory location by executing + +```bash +cd ../ +export LLVM_DIR=$PWD +cd ../ +``` + #### Environment variables You will need to define the following environment variables for the build of CppInterOp and cppyy (as they clear for a new session, it is recommended that you also add these to your .bashrc in linux, .bash_profile if on MacOS). On Linux and MacOS you define as follows @@ -167,6 +213,10 @@ cmake -DBUILD_SHARED_LIBS=ON -DCPPINTEROP_USE_CLING=ON -DCPPINTEROP_USE_REPL=Off cmake --build . --target install --parallel $(nproc --all) ``` +and + +> Do make sure to pass ``DLLVM_BUILT_WITH_OOP_JIT=ON``, if you want to have out-of-process JIT execution feature enabled. + #### Testing CppInterOp To test the built CppInterOp execute the following command in the CppInterOP build folder on Linux and MacOS diff --git a/docs/DevelopersDocumentation.rst b/docs/DevelopersDocumentation.rst index 0be22db90..43a0fc90c 100644 --- a/docs/DevelopersDocumentation.rst +++ b/docs/DevelopersDocumentation.rst @@ -41,6 +41,15 @@ Clone the 20.x release of the LLVM project repository. git clone --depth=1 --branch release/20.x https://github.com/llvm/llvm-project.git cd llvm-project +If you want to have out-of-process JIT execution enabled in CppInterOp, then apply this patch on Linux-x86_64 and MacOS-Darwin environment. +.. note:: + + This patch will not work for Windows because out-of-process JIT execution is currently implemented for Linux and MacOS only. + +.. code:: bash + + git apply -v ../CppInterOp/patches/llvm/clang20-1-out-of-process.patch + ****************** Build Clang-REPL ****************** @@ -99,6 +108,34 @@ On Windows you execute the following $env:LLVM_DIR= $PWD.Path cd ..\ +*************************************************** +Build Clang-REPL with Out-of-Process JIT Execution +*************************************************** + +To have `Out-of-Process JIT Execution` enabled, run following commands to build clang and clang-repl to support this feature: + +.. note:: + + Only for Linux x86_64 and Macos arm64 + +.. code:: bash + mkdir build + cd build + cmake -DLLVM_ENABLE_PROJECTS="clang;compiler-rt" \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + ../llvm + + # For Linux x86_64 + cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt-x86_64 --parallel $(nproc --all) + # For MacOS arm64 + cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt_osx --parallel $(sysctl -n hw.ncpu) + ************************************** Build Cling and related dependencies ************************************** @@ -261,6 +298,10 @@ commands on Linux and MacOS cmake -DBUILD_SHARED_LIBS=ON -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. cmake --build . --target install --parallel $(nproc --all) +.. note:: + + Do make sure to pass ``DLLVM_BUILT_WITH_OOP_JIT=ON``, if you want to have out-of-process JIT execution feature enabled. + and .. code:: powershell diff --git a/docs/InstallationAndUsage.rst b/docs/InstallationAndUsage.rst index 3ec6470b4..4a445f2fb 100644 --- a/docs/InstallationAndUsage.rst +++ b/docs/InstallationAndUsage.rst @@ -41,6 +41,15 @@ Clone the 20.x release of the LLVM project repository. git clone --depth=1 --branch release/20.x https://github.com/llvm/llvm-project.git cd llvm-project +If you want to have out-of-process JIT execution enabled in CppInterOp, then apply this patch on Linux-x86_64 and MacOS-Darwin environment. +.. note:: + + This patch will not work for Windows because out-of-process JIT execution is currently implemented for Linux and MacOS only. + +.. code:: bash + + git apply -v ../CppInterOp/patches/llvm/clang20-1-out-of-process.patch + ****************** Build Clang-REPL ****************** @@ -99,6 +108,34 @@ On Windows you execute the following $env:LLVM_DIR= $PWD.Path cd ..\ +*************************************************** +Build Clang-REPL with Out-of-Process JIT Execution +*************************************************** + +To have `Out-of-Process JIT Execution` enabled, run following commands to build clang and clang-repl to support this feature: + +.. note:: + + Only for Linux x86_64 and Macos arm64 + +.. code:: bash + mkdir build + cd build + cmake -DLLVM_ENABLE_PROJECTS="clang;compiler-rt" \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + ../llvm + + # For Linux x86_64 + cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt-x86_64 --parallel $(nproc --all) + # For MacOS arm64 + cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt_osx --parallel $(sysctl -n hw.ncpu) + ************************************** Build Cling and related dependencies ************************************** @@ -263,6 +300,10 @@ commands on Linux and MacOS cmake -DBUILD_SHARED_LIBS=ON -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. cmake --build . --target install --parallel $(nproc --all) +.. note:: + + Do make sure to pass ``DLLVM_BUILT_WITH_OOP_JIT=ON``, if you want to have out-of-process JIT execution feature enabled. + and .. code:: powershell diff --git a/include/CppInterOp/CppInterOp.h b/include/CppInterOp/CppInterOp.h index 2262de047..79cffad12 100644 --- a/include/CppInterOp/CppInterOp.h +++ b/include/CppInterOp/CppInterOp.h @@ -19,6 +19,7 @@ #include #include #include +#include #include // The cross-platform CPPINTEROP_API macro definition @@ -935,6 +936,12 @@ CPPINTEROP_API void CodeComplete(std::vector& Results, ///\returns 0 on success, non-zero on failure. CPPINTEROP_API int Undo(unsigned N = 1); +#ifndef _WIN32 +/// Returns the process ID of the executor process. +/// \returns the PID of the executor process. +CPPINTEROP_API pid_t GetExecutorPID(); +#endif + } // end namespace Cpp #endif // CPPINTEROP_CPPINTEROP_H diff --git a/lib/CppInterOp/Compatibility.h b/lib/CppInterOp/Compatibility.h index 24c822dec..ea5bb0571 100644 --- a/lib/CppInterOp/Compatibility.h +++ b/lib/CppInterOp/Compatibility.h @@ -63,6 +63,9 @@ static inline char* GetEnv(const char* Var_Name) { CXXSpecialMemberKind::MoveConstructor #endif +#define STRINGIFY(s) STRINGIFY_X(s) +#define STRINGIFY_X(...) #__VA_ARGS__ + #include "clang/Interpreter/CodeCompletion.h" #include "llvm/ADT/SmallString.h" @@ -87,6 +90,7 @@ static inline char* GetEnv(const char* Var_Name) { #include "cling/Utils/AST.h" #include +#include namespace Cpp { namespace Cpp_utils = cling::utils; @@ -202,10 +206,22 @@ inline void codeComplete(std::vector& Results, #include "llvm/Support/Error.h" +#ifdef LLVM_BUILT_WITH_OOP_JIT +#include "clang/Basic/Version.h" +#include "llvm/TargetParser/Host.h" + +#include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h" + +#include +#endif + +#include + namespace compat { inline std::unique_ptr -createClangInterpreter(std::vector& args) { +createClangInterpreter(std::vector& args, int stdin_fd = -1, + int stdout_fd = -1, int stderr_fd = -1) { auto has_arg = [](const char* x, llvm::StringRef match = "cuda") { llvm::StringRef Arg = x; Arg = Arg.trim().ltrim('-'); @@ -243,11 +259,55 @@ createClangInterpreter(std::vector& args) { (*ciOrErr)->LoadRequestedPlugins(); if (CudaEnabled) DeviceCI->LoadRequestedPlugins(); + + bool outOfProcess = (stdin_fd != -1 && stdout_fd != -1 && stderr_fd != -1); + +#ifdef LLVM_BUILT_WITH_OOP_JIT + + clang::Interpreter::JITConfig OutOfProcessConfig; + if (outOfProcess) { + OutOfProcessConfig.IsOutOfProcess = true; + OutOfProcessConfig.OOPExecutor = + LLVM_BUILD_LIB_DIR "/bin/llvm-jitlink-executor"; + OutOfProcessConfig.UseSharedMemory = false; + OutOfProcessConfig.SlabAllocateSize = 0; + OutOfProcessConfig.CustomizeFork = [stdin_fd, stdout_fd, + stderr_fd]() { // Lambda defined inline + dup2(stdin_fd, STDIN_FILENO); + dup2(stdout_fd, STDOUT_FILENO); + dup2(stderr_fd, STDERR_FILENO); + + setvbuf(fdopen(stdout_fd, "w+"), nullptr, _IONBF, 0); + setvbuf(fdopen(stderr_fd, "w+"), nullptr, _IONBF, 0); + }; + +#ifdef __APPLE__ + std::string OrcRuntimePath = LLVM_BUILD_LIB_DIR "/lib/clang/" STRINGIFY( + LLVM_VERSION_MAJOR) "/lib/darwin/liborc_rt_osx.a"; +#else + std::string OrcRuntimePath = LLVM_BUILD_LIB_DIR "/lib/clang/" STRINGIFY( + LLVM_VERSION_MAJOR) "/lib/x86_64-unknown-linux-gnu/liborc_rt.a"; +#endif + OutOfProcessConfig.OrcRuntimePath = OrcRuntimePath; + } + auto innerOrErr = + CudaEnabled + ? clang::Interpreter::createWithCUDA(std::move(*ciOrErr), + std::move(DeviceCI)) + : clang::Interpreter::create(std::move(*ciOrErr), OutOfProcessConfig); +#else + if (outOfProcess) { + llvm::errs() + << "[CreateClangInterpreter]: No compatibility with out-of-process " + "JIT. Running in-process JIT execution." + << "(To enable recompile CppInterOp with -DLLVM_BUILT_WITH_OOP_JIT=ON)" + << "\n"; + } auto innerOrErr = CudaEnabled ? clang::Interpreter::createWithCUDA(std::move(*ciOrErr), std::move(DeviceCI)) : clang::Interpreter::create(std::move(*ciOrErr)); - +#endif if (!innerOrErr) { llvm::logAllUnhandledErrors(innerOrErr.takeError(), llvm::errs(), "Failed to build Interpreter:"); @@ -387,7 +447,7 @@ class SynthesizingCodeRAII { "Failed to generate PTU:"); } }; -} +} // namespace compat #endif // CPPINTEROP_USE_REPL diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index b6cf555e8..3a5a345f4 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -61,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -69,13 +70,17 @@ #include #include #include -#include +#include +#ifndef _WIN32 +#include +#endif // Stream redirect. #ifdef _WIN32 #include #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 +#define STDERR_FILENO 2 // For exec(). #include #define popen(x, y) (_popen(x, y)) @@ -3238,8 +3243,9 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, #ifdef CPPINTEROP_USE_CLING auto I = new compat::Interpreter(ClingArgv.size(), &ClingArgv[0]); #else - auto Interp = compat::Interpreter::create(static_cast(ClingArgv.size()), - ClingArgv.data()); + auto Interp = + compat::Interpreter::create(static_cast(ClingArgv.size()), + ClingArgv.data(), nullptr, {}, nullptr, true); if (!Interp) return nullptr; auto* I = Interp.release(); @@ -3970,12 +3976,10 @@ bool Destruct(TCppObject_t This, TCppConstScope_t scope, } class StreamCaptureInfo { - struct file_deleter { - void operator()(FILE* fp) { pclose(fp); } - }; - std::unique_ptr m_TempFile; + FILE* m_TempFile = nullptr; int m_FD = -1; int m_DupFD = -1; + bool m_OwnsFile = true; public: #ifdef _MSC_VER @@ -3990,8 +3994,32 @@ class StreamCaptureInfo { }()}, m_FD(FD) { #else - StreamCaptureInfo(int FD) : m_TempFile{tmpfile()}, m_FD(FD) { + StreamCaptureInfo(int FD) : m_FD(FD) { +#if !defined(CPPINTEROP_USE_CLING) && !defined(_WIN32) + auto& I = getInterp(); + if (I.isOutOfProcess()) { + // Use interpreter-managed redirection file for out-of-process + // redirection. Since, we are using custom pipes instead of stdout, sterr, + // it is kind of necessary to have this complication in StreamCaptureInfo. + + // TODO(issues/733): Refactor the stream redirection + FILE* redirected = I.getRedirectionFileForOutOfProcess(FD); + if (redirected) { + m_TempFile = redirected; + m_OwnsFile = false; + if (ftruncate(fileno(m_TempFile), 0) != 0) + perror("ftruncate"); + if (lseek(fileno(m_TempFile), 0, SEEK_SET) == -1) + perror("lseek"); + } + } else { + m_TempFile = tmpfile(); + } +#else + m_TempFile = tmpfile(); #endif +#endif + if (!m_TempFile) { perror("StreamCaptureInfo: Unable to create temp file"); return; @@ -4003,7 +4031,7 @@ class StreamCaptureInfo { // This seems only necessary when piping stdout or stderr, but do it // for ttys to avoid over complicated code for minimal benefit. ::fflush(FD == STDOUT_FILENO ? stdout : stderr); - if (dup2(fileno(m_TempFile.get()), FD) < 0) + if (dup2(fileno(m_TempFile), FD) < 0) perror("StreamCaptureInfo:"); } StreamCaptureInfo(const StreamCaptureInfo&) = delete; @@ -4011,7 +4039,12 @@ class StreamCaptureInfo { StreamCaptureInfo(StreamCaptureInfo&&) = delete; StreamCaptureInfo& operator=(StreamCaptureInfo&&) = delete; - ~StreamCaptureInfo() { assert(m_DupFD == -1 && "Captured output not used?"); } + ~StreamCaptureInfo() { + assert(m_DupFD == -1 && "Captured output not used?"); + // Only close the temp file if we own it + if (m_OwnsFile && m_TempFile) + fclose(m_TempFile); + } std::string GetCapturedString() { assert(m_DupFD != -1 && "Multiple calls to GetCapturedString"); @@ -4020,25 +4053,28 @@ class StreamCaptureInfo { if (dup2(m_DupFD, m_FD) < 0) perror("StreamCaptureInfo:"); // Go to the end of the file. - if (fseek(m_TempFile.get(), 0L, SEEK_END) != 0) + if (fseek(m_TempFile, 0L, SEEK_END) != 0) perror("StreamCaptureInfo:"); // Get the size of the file. - long bufsize = ftell(m_TempFile.get()); - if (bufsize == -1) + long bufsize = ftell(m_TempFile); + if (bufsize == -1) { perror("StreamCaptureInfo:"); + close(m_DupFD); + m_DupFD = -1; + return ""; + } // Allocate our buffer to that size. std::unique_ptr content(new char[bufsize + 1]); // Go back to the start of the file. - if (fseek(m_TempFile.get(), 0L, SEEK_SET) != 0) + if (fseek(m_TempFile, 0L, SEEK_SET) != 0) perror("StreamCaptureInfo:"); // Read the entire file into memory. - size_t newLen = - fread(content.get(), sizeof(char), bufsize, m_TempFile.get()); - if (ferror(m_TempFile.get()) != 0) + size_t newLen = fread(content.get(), sizeof(char), bufsize, m_TempFile); + if (ferror(m_TempFile) != 0) fputs("Error reading file", stderr); else content[newLen++] = '\0'; // Just to be safe. @@ -4046,6 +4082,16 @@ class StreamCaptureInfo { std::string result = content.get(); close(m_DupFD); m_DupFD = -1; +#if !defined(_WIN32) && !defined(CPPINTEROP_USE_CLING) + auto& I = getInterp(); + if (I.isOutOfProcess()) { + int fd = fileno(m_TempFile); + if (ftruncate(fd, 0) != 0) + perror("ftruncate"); + if (lseek(fd, 0, SEEK_SET) == -1) + perror("lseek"); + } +#endif return result; } }; @@ -4085,4 +4131,15 @@ int Undo(unsigned N) { #endif } +#ifndef _WIN32 +pid_t GetExecutorPID() { +#ifdef LLVM_BUILT_WITH_OOP_JIT + auto& I = getInterp(); + return I.getOutOfProcessExecutorPID(); +#endif + return getpid(); +} + +#endif + } // end namespace Cpp diff --git a/lib/CppInterOp/CppInterOpInterpreter.h b/lib/CppInterOp/CppInterOpInterpreter.h index 06a04da01..18f09b223 100644 --- a/lib/CppInterOp/CppInterOpInterpreter.h +++ b/lib/CppInterOp/CppInterOpInterpreter.h @@ -39,6 +39,14 @@ #include "llvm/Support/raw_ostream.h" #include "llvm/TargetParser/Triple.h" +#ifndef _WIN32 +#include +#include +#endif +#include +#include +#include +#include #include #include @@ -140,10 +148,72 @@ namespace Cpp { /// CppInterOp Interpreter /// class Interpreter { +public: + struct FileDeleter { + void operator()(FILE* f /* owns */) { + if (f) + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + fclose(f); + } + }; + + struct IOContext { + std::unique_ptr stdin_file; + std::unique_ptr stdout_file; + std::unique_ptr stderr_file; + + bool initializeTempFiles() { + stdin_file.reset(tmpfile()); // NOLINT(cppcoreguidelines-owning-memory) + stdout_file.reset(tmpfile()); // NOLINT(cppcoreguidelines-owning-memory) + stderr_file.reset(tmpfile()); // NOLINT(cppcoreguidelines-owning-memory) + return stdin_file && stdout_file && stderr_file; + } + }; + private: + static std::tuple + initAndGetFileDescriptors(std::vector& vargs, + std::unique_ptr& io_ctx) { + int stdin_fd = -1; + int stdout_fd = -1; + int stderr_fd = -1; + bool outOfProcess = false; + +#if defined(_WIN32) + outOfProcess = false; +#else + outOfProcess = std::any_of(vargs.begin(), vargs.end(), [](const char* arg) { + return llvm::StringRef(arg).trim() == "--use-oop-jit"; + }); + + if (outOfProcess) { + // Only initialize temp files if not already initialized + if (!io_ctx->stdin_file || !io_ctx->stdout_file || !io_ctx->stderr_file) { + bool init = io_ctx->initializeTempFiles(); + if (!init) { + llvm::errs() << "Can't start out-of-process JIT execution.\n"; + outOfProcess = false; + } + } + if (outOfProcess) { + stdin_fd = fileno(io_ctx->stdin_file.get()); + stdout_fd = fileno(io_ctx->stdout_file.get()); + stderr_fd = fileno(io_ctx->stderr_file.get()); + } + } +#endif + + return std::make_tuple(stdin_fd, stdout_fd, stderr_fd); + } + std::unique_ptr inner; + std::unique_ptr io_context; + bool outOfProcess; - Interpreter(std::unique_ptr CI) : inner(std::move(CI)) {} +public: + Interpreter(std::unique_ptr CI, + std::unique_ptr ctx = nullptr, bool oop = false) + : inner(std::move(CI)), io_context(std::move(ctx)), outOfProcess(oop) {} public: static std::unique_ptr @@ -159,13 +229,25 @@ class Interpreter { llvm::InitializeAllAsmPrinters(); std::vector vargs(argv + 1, argv + argc); - auto CI = compat::createClangInterpreter(vargs); + + int stdin_fd; + int stdout_fd; + int stderr_fd; + auto io_ctx = std::make_unique(); + std::tie(stdin_fd, stdout_fd, stderr_fd) = + initAndGetFileDescriptors(vargs, io_ctx); + + bool outOfProcess = (stdin_fd != -1 && stdout_fd != -1 && stderr_fd != -1); + + auto CI = + compat::createClangInterpreter(vargs, stdin_fd, stdout_fd, stderr_fd); if (!CI) { llvm::errs() << "Interpreter creation failed\n"; return nullptr; } - return std::unique_ptr(new Interpreter(std::move(CI))); + return std::make_unique(std::move(CI), std::move(io_ctx), + outOfProcess); } ~Interpreter() {} @@ -173,6 +255,30 @@ class Interpreter { operator const clang::Interpreter&() const { return *inner; } operator clang::Interpreter&() { return *inner; } + [[nodiscard]] bool isOutOfProcess() const { return outOfProcess; } + +// Since, we are using custom pipes instead of stdout, sterr, +// it is kind of necessary to have this complication in StreamCaptureInfo. + +// TODO(issues/733): Refactor the stream redirection +#ifndef _WIN32 + FILE* getRedirectionFileForOutOfProcess(int FD) { + if (!io_context) + return nullptr; + switch (FD) { + case (STDIN_FILENO): + return io_context->stdin_file.get(); + case (STDOUT_FILENO): + return io_context->stdout_file.get(); + case (STDERR_FILENO): + return io_context->stderr_file.get(); + default: + llvm::errs() << "No temp file for the FD\n"; + return nullptr; + } + } +#endif + ///\brief Describes the return result of the different routines that do the /// incremental compilation. /// @@ -231,6 +337,15 @@ class Interpreter { return llvm::orc::ExecutorAddr(*AddrOrErr); } +#ifndef _WIN32 + [[nodiscard]] pid_t getOutOfProcessExecutorPID() const { +#ifdef LLVM_BUILT_WITH_OOP_JIT + return inner->getOutOfProcessExecutorPID(); +#endif + return 0; + } +#endif + /// \returns the \c ExecutorAddr of a given name as written in the object /// file. llvm::Expected diff --git a/patches/llvm/clang20-1-out-of-process.patch b/patches/llvm/clang20-1-out-of-process.patch new file mode 100644 index 000000000..198afb99a --- /dev/null +++ b/patches/llvm/clang20-1-out-of-process.patch @@ -0,0 +1,966 @@ +diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h +index f8663e319..1f9553e68 100644 +--- a/clang/include/clang/Interpreter/Interpreter.h ++++ b/clang/include/clang/Interpreter/Interpreter.h +@@ -20,8 +20,10 @@ + + #include "llvm/ADT/DenseMap.h" + #include "llvm/ExecutionEngine/JITSymbol.h" ++#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" + #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" + #include "llvm/Support/Error.h" ++#include + #include + #include + +@@ -35,6 +37,10 @@ class ThreadSafeContext; + + namespace clang { + ++namespace driver { ++class ToolChain; ++} // namespace driver ++ + class CompilerInstance; + class CodeGenerator; + class CXXRecordDecl; +@@ -119,15 +125,40 @@ class Interpreter { + /// An optional compiler instance for CUDA offloading + std::unique_ptr DeviceCI; + ++public: ++ struct JITConfig { ++ /// Indicates whether out-of-process JIT execution is enabled. ++ bool IsOutOfProcess = false; ++ /// Path to the out-of-process JIT executor. ++ std::string OOPExecutor = ""; ++ std::string OOPExecutorConnect = ""; ++ /// Indicates whether to use shared memory for communication. ++ bool UseSharedMemory = false; ++ /// Representing the slab allocation size for memory management in kb. ++ unsigned SlabAllocateSize = 0; ++ /// Path to the ORC runtime library. ++ std::string OrcRuntimePath = ""; ++ /// PID of the out-of-process JIT executor. ++ uint32_t ExecutorPID = 0; ++ /// Custom lambda to be executed inside child process/executor ++ std::function CustomizeFork = nullptr; ++ ++ JITConfig() ++ : IsOutOfProcess(false), OOPExecutor(""), OOPExecutorConnect(""), ++ UseSharedMemory(false), SlabAllocateSize(0), OrcRuntimePath(""), ++ ExecutorPID(0), CustomizeFork(nullptr) {} ++ }; ++ + protected: + // Derived classes can use an extended interface of the Interpreter. + Interpreter(std::unique_ptr Instance, llvm::Error &Err, + std::unique_ptr JITBuilder = nullptr, +- std::unique_ptr Consumer = nullptr); ++ std::unique_ptr Consumer = nullptr, ++ JITConfig Config = JITConfig()); + + // Create the internal IncrementalExecutor, or re-create it after calling + // ResetExecutor(). +- llvm::Error CreateExecutor(); ++ llvm::Error CreateExecutor(JITConfig Config = JITConfig()); + + // Delete the internal IncrementalExecutor. This causes a hard shutdown of the + // JIT engine. In particular, it doesn't run cleanup or destructors. +@@ -136,10 +167,19 @@ protected: + public: + virtual ~Interpreter(); + static llvm::Expected> +- create(std::unique_ptr CI); ++ create(std::unique_ptr CI, JITConfig Config = {}); + static llvm::Expected> + createWithCUDA(std::unique_ptr CI, + std::unique_ptr DCI); ++ static llvm::Expected> ++ createLLJITBuilder(std::unique_ptr EPC, ++ llvm::StringRef OrcRuntimePath); ++ static llvm::Expected< ++ std::pair, uint32_t>> ++ outOfProcessJITBuilder(JITConfig Config); ++ static llvm::Expected ++ getOrcRuntimePath(const driver::ToolChain &TC); ++ + const ASTContext &getASTContext() const; + ASTContext &getASTContext(); + const CompilerInstance *getCompilerInstance() const; +@@ -170,6 +210,8 @@ public: + llvm::Expected + getSymbolAddressFromLinkerName(llvm::StringRef LinkerName) const; + ++ uint32_t getOutOfProcessExecutorPID() const; ++ + const llvm::SmallVectorImpl &getValuePrintingInfo() const { + return ValuePrintingInfo; + } +diff --git a/clang/lib/Interpreter/IncrementalExecutor.cpp b/clang/lib/Interpreter/IncrementalExecutor.cpp +index 4d2adecaa..45620fcd3 100644 +--- a/clang/lib/Interpreter/IncrementalExecutor.cpp ++++ b/clang/lib/Interpreter/IncrementalExecutor.cpp +@@ -15,19 +15,36 @@ + #include "clang/Basic/TargetInfo.h" + #include "clang/Basic/TargetOptions.h" + #include "clang/Interpreter/PartialTranslationUnit.h" ++#include "llvm/ADT/StringExtras.h" + #include "llvm/ExecutionEngine/ExecutionEngine.h" + #include "llvm/ExecutionEngine/Orc/CompileUtils.h" ++#include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" + #include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h" ++#include "llvm/ExecutionEngine/Orc/EPCDebugObjectRegistrar.h" ++#include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" + #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" + #include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" + #include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" + #include "llvm/ExecutionEngine/Orc/LLJIT.h" ++#include "llvm/ExecutionEngine/Orc/MapperJITLinkMemoryManager.h" + #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" ++#include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" ++#include "llvm/ExecutionEngine/Orc/Shared/SimpleRemoteEPCUtils.h" + #include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" + #include "llvm/ExecutionEngine/SectionMemoryManager.h" + #include "llvm/IR/Module.h" ++#include "llvm/Support/FileSystem.h" + #include "llvm/Support/ManagedStatic.h" ++#include "llvm/Support/Path.h" + #include "llvm/Support/TargetSelect.h" ++#include "llvm/TargetParser/Host.h" ++ ++#ifdef LLVM_ON_UNIX ++#include ++#include ++#include ++#include ++#endif // LLVM_ON_UNIX + + // Force linking some of the runtimes that helps attaching to a debugger. + LLVM_ATTRIBUTE_USED void linkComponents() { +@@ -55,8 +72,9 @@ IncrementalExecutor::createDefaultJITBuilder( + + IncrementalExecutor::IncrementalExecutor(llvm::orc::ThreadSafeContext &TSC, + llvm::orc::LLJITBuilder &JITBuilder, ++ Interpreter::JITConfig Config, + llvm::Error &Err) +- : TSCtx(TSC) { ++ : TSCtx(TSC), OutOfProcessChildPid(Config.ExecutorPID) { + using namespace llvm::orc; + llvm::ErrorAsOutParameter EAO(&Err); + +@@ -118,4 +136,229 @@ IncrementalExecutor::getSymbolAddress(llvm::StringRef Name, + return SymOrErr->getAddress(); + } + ++Expected> ++createSharedMemoryManager(llvm::orc::SimpleRemoteEPC &SREPC, ++ unsigned SlabAllocateSize) { ++ llvm::orc::SharedMemoryMapper::SymbolAddrs SAs; ++ if (auto Err = SREPC.getBootstrapSymbols( ++ {{SAs.Instance, ++ llvm::orc::rt::ExecutorSharedMemoryMapperServiceInstanceName}, ++ {SAs.Reserve, ++ llvm::orc::rt::ExecutorSharedMemoryMapperServiceReserveWrapperName}, ++ {SAs.Initialize, ++ llvm::orc::rt:: ++ ExecutorSharedMemoryMapperServiceInitializeWrapperName}, ++ {SAs.Deinitialize, ++ llvm::orc::rt:: ++ ExecutorSharedMemoryMapperServiceDeinitializeWrapperName}, ++ {SAs.Release, ++ llvm::orc::rt:: ++ ExecutorSharedMemoryMapperServiceReleaseWrapperName}})) ++ return std::move(Err); ++ ++ size_t SlabSize; ++ if (llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()) ++ SlabSize = 1024 * 1024; ++ else ++ SlabSize = 1024 * 1024 * 1024; ++ ++ if (SlabAllocateSize > 0) ++ SlabSize = SlabAllocateSize; ++ ++ return llvm::orc::MapperJITLinkMemoryManager::CreateWithMapper< ++ llvm::orc::SharedMemoryMapper>(SlabSize, SREPC, SAs); ++} ++ ++llvm::Expected, uint32_t>> ++IncrementalExecutor::launchExecutor(llvm::StringRef ExecutablePath, ++ bool UseSharedMemory, ++ unsigned SlabAllocateSize, ++ std::function CustomizeFork) { ++#ifndef LLVM_ON_UNIX ++ // FIXME: Add support for Windows. ++ return llvm::make_error( ++ "-" + ExecutablePath + " not supported on non-unix platforms", ++ llvm::inconvertibleErrorCode()); ++#elif !LLVM_ENABLE_THREADS ++ // Out of process mode using SimpleRemoteEPC depends on threads. ++ return llvm::make_error( ++ "-" + ExecutablePath + ++ " requires threads, but LLVM was built with " ++ "LLVM_ENABLE_THREADS=Off", ++ llvm::inconvertibleErrorCode()); ++#else ++ ++ if (!llvm::sys::fs::can_execute(ExecutablePath)) ++ return llvm::make_error( ++ llvm::formatv("Specified executor invalid: {0}", ExecutablePath), ++ llvm::inconvertibleErrorCode()); ++ ++ constexpr int ReadEnd = 0; ++ constexpr int WriteEnd = 1; ++ ++ // Pipe FDs. ++ int ToExecutor[2]; ++ int FromExecutor[2]; ++ ++ uint32_t ChildPID; ++ ++ // Create pipes to/from the executor.. ++ if (pipe(ToExecutor) != 0 || pipe(FromExecutor) != 0) ++ return llvm::make_error( ++ "Unable to create pipe for executor", llvm::inconvertibleErrorCode()); ++ ++ ChildPID = fork(); ++ ++ if (ChildPID == 0) { ++ // In the child... ++ ++ // Close the parent ends of the pipes ++ close(ToExecutor[WriteEnd]); ++ close(FromExecutor[ReadEnd]); ++ ++ if (CustomizeFork) ++ CustomizeFork(); ++ ++ // Execute the child process. ++ std::unique_ptr ExecutorPath, FDSpecifier; ++ { ++ ExecutorPath = std::make_unique(ExecutablePath.size() + 1); ++ strcpy(ExecutorPath.get(), ExecutablePath.data()); ++ ++ std::string FDSpecifierStr("filedescs="); ++ FDSpecifierStr += llvm::utostr(ToExecutor[ReadEnd]); ++ FDSpecifierStr += ','; ++ FDSpecifierStr += llvm::utostr(FromExecutor[WriteEnd]); ++ FDSpecifier = std::make_unique(FDSpecifierStr.size() + 1); ++ strcpy(FDSpecifier.get(), FDSpecifierStr.c_str()); ++ } ++ ++ char *const Args[] = {ExecutorPath.get(), FDSpecifier.get(), nullptr}; ++ int RC = execvp(ExecutorPath.get(), Args); ++ if (RC != 0) { ++ llvm::errs() << "unable to launch out-of-process executor \"" ++ << ExecutorPath.get() << "\"\n"; ++ exit(1); ++ } ++ } ++ // else we're the parent... ++ ++ // Close the child ends of the pipes ++ close(ToExecutor[ReadEnd]); ++ close(FromExecutor[WriteEnd]); ++ ++ llvm::orc::SimpleRemoteEPC::Setup S = llvm::orc::SimpleRemoteEPC::Setup(); ++ if (UseSharedMemory) ++ S.CreateMemoryManager = ++ [SlabAllocateSize](llvm::orc::SimpleRemoteEPC &EPC) { ++ return createSharedMemoryManager(EPC, SlabAllocateSize); ++ }; ++ ++ auto EPCOrErr = ++ llvm::orc::SimpleRemoteEPC::Create( ++ std::make_unique( ++ std::nullopt), ++ std::move(S), FromExecutor[ReadEnd], ToExecutor[WriteEnd]); ++ if (!EPCOrErr) ++ return EPCOrErr.takeError(); ++ return std::make_pair(std::move(*EPCOrErr), ChildPID); ++#endif ++} ++ ++#if LLVM_ON_UNIX && LLVM_ENABLE_THREADS ++ ++static Expected connectTCPSocketImpl(std::string Host, ++ std::string PortStr) { ++ addrinfo *AI; ++ addrinfo Hints{}; ++ Hints.ai_family = AF_INET; ++ Hints.ai_socktype = SOCK_STREAM; ++ Hints.ai_flags = AI_NUMERICSERV; ++ ++ if (int EC = getaddrinfo(Host.c_str(), PortStr.c_str(), &Hints, &AI)) ++ return llvm::make_error( ++ llvm::formatv("address resolution failed ({0})", strerror(EC)), ++ llvm::inconvertibleErrorCode()); ++ // Cycle through the returned addrinfo structures and connect to the first ++ // reachable endpoint. ++ int SockFD; ++ addrinfo *Server; ++ for (Server = AI; Server != nullptr; Server = Server->ai_next) { ++ // socket might fail, e.g. if the address family is not supported. Skip to ++ // the next addrinfo structure in such a case. ++ if ((SockFD = socket(AI->ai_family, AI->ai_socktype, AI->ai_protocol)) < 0) ++ continue; ++ ++ // If connect returns null, we exit the loop with a working socket. ++ if (connect(SockFD, Server->ai_addr, Server->ai_addrlen) == 0) ++ break; ++ ++ close(SockFD); ++ } ++ freeaddrinfo(AI); ++ ++ // If we reached the end of the loop without connecting to a valid endpoint, ++ // dump the last error that was logged in socket() or connect(). ++ if (Server == nullptr) ++ return llvm::make_error("invalid hostname", ++ llvm::inconvertibleErrorCode()); ++ ++ return SockFD; ++} ++ ++llvm::Expected> ++IncrementalExecutor::connectTCPSocket(llvm::StringRef NetworkAddress, ++ bool UseSharedMemory, ++ unsigned SlabAllocateSize) { ++#ifndef LLVM_ON_UNIX ++ // FIXME: Add TCP support for Windows. ++ return llvm::make_error( ++ "-" + NetworkAddress + " not supported on non-unix platforms", ++ llvm::inconvertibleErrorCode()); ++#elif !LLVM_ENABLE_THREADS ++ // Out of process mode using SimpleRemoteEPC depends on threads. ++ return llvm::make_error( ++ "-" + NetworkAddress + ++ " requires threads, but LLVM was built with " ++ "LLVM_ENABLE_THREADS=Off", ++ llvm::inconvertibleErrorCode()); ++#else ++ ++ auto CreateErr = [NetworkAddress](Twine Details) { ++ return llvm::make_error( ++ formatv("Failed to connect TCP socket '{0}': {1}", NetworkAddress, ++ Details), ++ llvm::inconvertibleErrorCode()); ++ }; ++ ++ StringRef Host, PortStr; ++ std::tie(Host, PortStr) = NetworkAddress.split(':'); ++ if (Host.empty()) ++ return CreateErr("Host name for -" + NetworkAddress + " can not be empty"); ++ if (PortStr.empty()) ++ return CreateErr("Port number in -" + NetworkAddress + " can not be empty"); ++ int Port = 0; ++ if (PortStr.getAsInteger(10, Port)) ++ return CreateErr("Port number '" + PortStr + "' is not a valid integer"); ++ ++ Expected SockFD = connectTCPSocketImpl(Host.str(), PortStr.str()); ++ if (!SockFD) ++ return SockFD.takeError(); ++ ++ llvm::orc::SimpleRemoteEPC::Setup S = llvm::orc::SimpleRemoteEPC::Setup(); ++ if (UseSharedMemory) ++ S.CreateMemoryManager = ++ [SlabAllocateSize](llvm::orc::SimpleRemoteEPC &EPC) { ++ return createSharedMemoryManager(EPC, SlabAllocateSize); ++ }; ++ ++ return llvm::orc::SimpleRemoteEPC::Create< ++ llvm::orc::FDSimpleRemoteEPCTransport>( ++ std::make_unique( ++ std::nullopt), ++ std::move(S), *SockFD, *SockFD); ++#endif ++} ++#endif // _WIN32 ++ + } // namespace clang +diff --git a/clang/lib/Interpreter/IncrementalExecutor.h b/clang/lib/Interpreter/IncrementalExecutor.h +index 71d71bc38..56e83378f 100644 +--- a/clang/lib/Interpreter/IncrementalExecutor.h ++++ b/clang/lib/Interpreter/IncrementalExecutor.h +@@ -13,13 +13,20 @@ + #ifndef LLVM_CLANG_LIB_INTERPRETER_INCREMENTALEXECUTOR_H + #define LLVM_CLANG_LIB_INTERPRETER_INCREMENTALEXECUTOR_H + ++#include "clang/Interpreter/Interpreter.h" + #include "llvm/ADT/DenseMap.h" + #include "llvm/ADT/StringRef.h" ++#include "llvm/ExecutionEngine/Orc/Core.h" ++#include "llvm/ExecutionEngine/Orc/Core.h" + #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" ++#include "llvm/ExecutionEngine/Orc/Layer.h" + #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" ++#include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h" ++#include "llvm/Support/Error.h" + ++#include + #include +- ++#include + namespace llvm { + class Error; + namespace orc { +@@ -39,6 +46,7 @@ class IncrementalExecutor { + using CtorDtorIterator = llvm::orc::CtorDtorIterator; + std::unique_ptr Jit; + llvm::orc::ThreadSafeContext &TSCtx; ++ uint32_t OutOfProcessChildPid = -1; + + llvm::DenseMap + ResourceTrackers; +@@ -50,7 +58,8 @@ public: + enum SymbolNameKind { IRName, LinkerName }; + + IncrementalExecutor(llvm::orc::ThreadSafeContext &TSC, +- llvm::orc::LLJITBuilder &JITBuilder, llvm::Error &Err); ++ llvm::orc::LLJITBuilder &JITBuilder, ++ Interpreter::JITConfig Config, llvm::Error &Err); + virtual ~IncrementalExecutor(); + + virtual llvm::Error addModule(PartialTranslationUnit &PTU); +@@ -62,8 +71,22 @@ public: + + llvm::orc::LLJIT &GetExecutionEngine() { return *Jit; } + ++ uint32_t getOutOfProcessChildPid() const { return OutOfProcessChildPid; } ++ + static llvm::Expected> + createDefaultJITBuilder(llvm::orc::JITTargetMachineBuilder JTMB); ++ ++ static llvm::Expected< ++ std::pair, uint32_t>> ++ launchExecutor(llvm::StringRef ExecutablePath, bool UseSharedMemory, ++ unsigned SlabAllocateSize, ++ std::function CustomizeFork = nullptr); ++ ++#if LLVM_ON_UNIX && LLVM_ENABLE_THREADS ++ static llvm::Expected> ++ connectTCPSocket(llvm::StringRef NetworkAddress, bool UseSharedMemory, ++ unsigned SlabAllocateSize); ++#endif + }; + + } // end namespace clang +diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp +index 3b81f9d70..6ef46a942 100644 +--- a/clang/lib/Interpreter/Interpreter.cpp ++++ b/clang/lib/Interpreter/Interpreter.cpp +@@ -46,6 +46,7 @@ + #include "clang/Sema/Lookup.h" + #include "clang/Serialization/ObjectFilePCHContainerReader.h" + #include "llvm/ExecutionEngine/JITSymbol.h" ++#include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" + #include "llvm/ExecutionEngine/Orc/LLJIT.h" + #include "llvm/IR/Module.h" + #include "llvm/Support/Errc.h" +@@ -365,7 +366,8 @@ public: + Interpreter::Interpreter(std::unique_ptr Instance, + llvm::Error &ErrOut, + std::unique_ptr JITBuilder, +- std::unique_ptr Consumer) ++ std::unique_ptr Consumer, ++ JITConfig Config) + : JITBuilder(std::move(JITBuilder)) { + CI = std::move(Instance); + llvm::ErrorAsOutParameter EAO(&ErrOut); +@@ -395,7 +397,7 @@ Interpreter::Interpreter(std::unique_ptr Instance, + ASTContext &C = CI->getASTContext(); + RegisterPTU(C.getTranslationUnitDecl(), std::move(M)); + } +- if (llvm::Error Err = CreateExecutor()) { ++ if (llvm::Error Err = CreateExecutor(Config)) { + ErrOut = joinErrors(std::move(ErrOut), std::move(Err)); + return; + } +@@ -454,19 +456,118 @@ const char *const Runtimes = R"( + EXTERN_C void __clang_Interpreter_SetValueNoAlloc(void *This, void *OutVal, void *OpaqueType, ...); + )"; + ++llvm::Expected, uint32_t>> ++Interpreter::outOfProcessJITBuilder(JITConfig Config) { ++ std::unique_ptr EPC; ++ uint32_t childPid = -1; ++ if (!Config.OOPExecutor.empty()) { ++ // Launch an out-of-process executor locally in a child process. ++ auto ResultOrErr = IncrementalExecutor::launchExecutor( ++ Config.OOPExecutor, Config.UseSharedMemory, Config.SlabAllocateSize, ++ Config.CustomizeFork); ++ if (!ResultOrErr) ++ return ResultOrErr.takeError(); ++ childPid = ResultOrErr->second; ++ auto EPCOrErr = std::move(ResultOrErr->first); ++ EPC = std::move(EPCOrErr); ++ } else if (Config.OOPExecutorConnect != "") { ++#if LLVM_ON_UNIX && LLVM_ENABLE_THREADS ++ auto EPCOrErr = IncrementalExecutor::connectTCPSocket( ++ Config.OOPExecutorConnect, Config.UseSharedMemory, ++ Config.SlabAllocateSize); ++ if (!EPCOrErr) ++ return EPCOrErr.takeError(); ++ EPC = std::move(*EPCOrErr); ++#else ++ return llvm::make_error( ++ "Out-of-process JIT over TCP is not supported on this platform", ++ std::error_code()); ++#endif ++ } ++ ++ std::unique_ptr JB; ++ if (EPC) { ++ auto JBOrErr = clang::Interpreter::createLLJITBuilder( ++ std::move(EPC), Config.OrcRuntimePath); ++ if (!JBOrErr) ++ return JBOrErr.takeError(); ++ JB = std::move(*JBOrErr); ++ } ++ ++ return std::make_pair(std::move(JB), childPid); ++} ++ ++llvm::Expected ++Interpreter::getOrcRuntimePath(const driver::ToolChain &TC) { ++ std::optional CompilerRTPath = TC.getCompilerRTPath(); ++ std::optional ResourceDir = TC.getRuntimePath(); ++ ++ if (!CompilerRTPath) { ++ return llvm::make_error("CompilerRT path not found", ++ std::error_code()); ++ } ++ ++ const std::array OrcRTLibNames = { ++ "liborc_rt.a", "liborc_rt_osx.a", "liborc_rt-x86_64.a"}; ++ ++ for (const char *LibName : OrcRTLibNames) { ++ llvm::SmallString<256> CandidatePath((*CompilerRTPath).c_str()); ++ llvm::sys::path::append(CandidatePath, LibName); ++ ++ if (llvm::sys::fs::exists(CandidatePath)) { ++ return CandidatePath.str().str(); ++ } ++ } ++ ++ return llvm::make_error( ++ llvm::Twine("OrcRuntime library not found in: ") + (*CompilerRTPath), ++ std::error_code()); ++} ++ + llvm::Expected> +-Interpreter::create(std::unique_ptr CI) { ++Interpreter::create(std::unique_ptr CI, JITConfig Config) { + llvm::Error Err = llvm::Error::success(); +- auto Interp = +- std::unique_ptr(new Interpreter(std::move(CI), Err)); +- if (Err) +- return std::move(Err); ++ ++ std::unique_ptr JB; ++ ++ if (Config.IsOutOfProcess) { ++ const TargetInfo &TI = CI->getTarget(); ++ const llvm::Triple &Triple = TI.getTriple(); ++ ++ DiagnosticsEngine &Diags = CI->getDiagnostics(); ++ std::string BinaryName = llvm::sys::fs::getMainExecutable(nullptr, nullptr); ++ driver::Driver Driver(BinaryName, Triple.str(), Diags); ++ // Need fake args to get the driver to create a compilation. ++ std::vector Args = {"clang", "--version"}; ++ std::unique_ptr C( ++ Driver.BuildCompilation(Args)); ++ if (!C) { ++ return llvm::make_error( ++ "Failed to create driver compilation for out-of-process JIT", ++ std::error_code()); ++ } ++ if (Config.OrcRuntimePath == "") { ++ const clang::driver::ToolChain &TC = C->getDefaultToolChain(); ++ ++ auto OrcRuntimePathOrErr = getOrcRuntimePath(TC); ++ if (!OrcRuntimePathOrErr) { ++ return OrcRuntimePathOrErr.takeError(); ++ } ++ ++ Config.OrcRuntimePath = *OrcRuntimePathOrErr; ++ } ++ } ++ ++ auto Interp = std::unique_ptr(new Interpreter( ++ std::move(CI), Err, std::move(JB), /*Consumer=*/nullptr, Config)); ++ if (auto E = std::move(Err)) ++ return std::move(E); + + // Add runtime code and set a marker to hide it from user code. Undo will not + // go through that. +- auto PTU = Interp->Parse(Runtimes); +- if (!PTU) +- return PTU.takeError(); ++ if (auto E = Interp->ParseAndExecute(Runtimes)) ++ return std::move(E); ++ + Interp->markUserCodeStart(); + + Interp->ValuePrintingInfo.resize(4); +@@ -551,6 +652,12 @@ size_t Interpreter::getEffectivePTUSize() const { + return PTUs.size() - InitPTUSize; + } + ++uint32_t Interpreter::getOutOfProcessExecutorPID() const { ++ if (IncrExecutor) ++ return IncrExecutor->getOutOfProcessChildPid(); ++ return -1; ++} ++ + PartialTranslationUnit & + Interpreter::RegisterPTU(TranslationUnitDecl *TU, + std::unique_ptr M /*={}*/, +@@ -617,7 +724,26 @@ createJITTargetMachineBuilder(const std::string &TT) { + return llvm::orc::JITTargetMachineBuilder(llvm::Triple(TT)); + } + +-llvm::Error Interpreter::CreateExecutor() { ++llvm::Expected> ++Interpreter::createLLJITBuilder( ++ std::unique_ptr EPC, ++ llvm::StringRef OrcRuntimePath) { ++ const std::string &TT = EPC->getTargetTriple().getTriple(); ++ auto JTMB = createJITTargetMachineBuilder(TT); ++ if (!JTMB) ++ return JTMB.takeError(); ++ auto JB = IncrementalExecutor::createDefaultJITBuilder(std::move(*JTMB)); ++ if (!JB) ++ return JB.takeError(); ++ ++ (*JB)->setExecutorProcessControl(std::move(EPC)); ++ (*JB)->setPlatformSetUp( ++ llvm::orc::ExecutorNativePlatform(OrcRuntimePath.str())); ++ ++ return std::move(*JB); ++} ++ ++llvm::Error Interpreter::CreateExecutor(JITConfig Config) { + if (IncrExecutor) + return llvm::make_error("Operation failed. " + "Execution engine exists", +@@ -626,8 +752,26 @@ llvm::Error Interpreter::CreateExecutor() { + return llvm::make_error("Operation failed. " + "No code generator available", + std::error_code()); ++ ++ const std::string &TT = getCompilerInstance()->getTargetOpts().Triple; ++ llvm::Triple TargetTriple(TT); ++ bool IsWindowsTarget = TargetTriple.isOSWindows(); ++ ++ if (!IsWindowsTarget && Config.IsOutOfProcess) { ++ if (!JITBuilder) { ++ auto ResOrErr = outOfProcessJITBuilder(Config); ++ if (!ResOrErr) ++ return ResOrErr.takeError(); ++ JITBuilder = std::move(ResOrErr->first); ++ Config.ExecutorPID = ResOrErr->second; ++ } ++ if (!JITBuilder) ++ return llvm::make_error( ++ "Operation failed. No LLJITBuilder for out-of-process JIT", ++ std::error_code()); ++ } ++ + if (!JITBuilder) { +- const std::string &TT = getCompilerInstance()->getTargetOpts().Triple; + auto JTMB = createJITTargetMachineBuilder(TT); + if (!JTMB) + return JTMB.takeError(); +@@ -638,11 +782,15 @@ llvm::Error Interpreter::CreateExecutor() { + } + + llvm::Error Err = llvm::Error::success(); ++ ++ // Fix: Declare Executor as the appropriate unique_ptr type ++ std::unique_ptr Executor; ++ + #ifdef __EMSCRIPTEN__ +- auto Executor = std::make_unique(*TSCtx); ++ Executor = std::make_unique(*TSCtx); + #else +- auto Executor = +- std::make_unique(*TSCtx, *JITBuilder, Err); ++ Executor = ++ std::make_unique(*TSCtx, *JITBuilder, Config, Err); + #endif + if (!Err) + IncrExecutor = std::move(Executor); +diff --git a/clang/tools/clang-repl/ClangRepl.cpp b/clang/tools/clang-repl/ClangRepl.cpp +index 7af8e4f25..c1a7ec397 100644 +--- a/clang/tools/clang-repl/ClangRepl.cpp ++++ b/clang/tools/clang-repl/ClangRepl.cpp +@@ -11,6 +11,8 @@ + //===----------------------------------------------------------------------===// + + #include "clang/Basic/Diagnostic.h" ++#include "clang/Basic/Version.h" ++#include "clang/Config/config.h" + #include "clang/Frontend/CompilerInstance.h" + #include "clang/Frontend/FrontendDiagnostic.h" + #include "clang/Interpreter/CodeCompletion.h" +@@ -18,14 +20,27 @@ + #include "clang/Lex/Preprocessor.h" + #include "clang/Sema/Sema.h" + ++#include "llvm/ADT/SmallString.h" ++#include "llvm/ADT/StringRef.h" + #include "llvm/ExecutionEngine/Orc/LLJIT.h" + #include "llvm/LineEditor/LineEditor.h" + #include "llvm/Support/CommandLine.h" ++#include "llvm/Support/FileSystem.h" + #include "llvm/Support/ManagedStatic.h" // llvm_shutdown ++#include "llvm/Support/Path.h" + #include "llvm/Support/Signals.h" + #include "llvm/Support/TargetSelect.h" ++#include "llvm/Support/VirtualFileSystem.h" ++#include "llvm/Support/raw_ostream.h" ++#include "llvm/TargetParser/Host.h" ++#include "llvm/TargetParser/Triple.h" + #include + ++#include ++#include ++ ++#include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h" ++ + // Disable LSan for this test. + // FIXME: Re-enable once we can assume GCC 13.2 or higher. + // https://llvm.org/github.com/llvm/llvm-project/issues/67586. +@@ -34,10 +49,36 @@ + LLVM_ATTRIBUTE_USED int __lsan_is_turned_off() { return 1; } + #endif + ++#define DEBUG_TYPE "clang-repl" ++ + static llvm::cl::opt CudaEnabled("cuda", llvm::cl::Hidden); + static llvm::cl::opt CudaPath("cuda-path", llvm::cl::Hidden); + static llvm::cl::opt OffloadArch("offload-arch", llvm::cl::Hidden); +- ++static llvm::cl::OptionCategory OOPCategory("Out-of-process Execution Options"); ++static llvm::cl::opt SlabAllocateSizeString( ++ "slab-allocate", ++ llvm::cl::desc("Allocate from a slab of the given size " ++ "(allowable suffixes: Kb, Mb, Gb. default = " ++ "Kb)"), ++ llvm::cl::init(""), llvm::cl::cat(OOPCategory)); ++static llvm::cl::opt ++ OOPExecutor("oop-executor", ++ llvm::cl::desc("Launch an out-of-process executor to run code"), ++ llvm::cl::init(""), llvm::cl::ValueOptional, ++ llvm::cl::cat(OOPCategory)); ++static llvm::cl::opt OOPExecutorConnect( ++ "oop-executor-connect", ++ llvm::cl::desc( ++ "Connect to an out-of-process executor through a TCP socket"), ++ llvm::cl::value_desc(":")); ++static llvm::cl::opt ++ OrcRuntimePath("orc-runtime", llvm::cl::desc("Path to the ORC runtime"), ++ llvm::cl::init(""), llvm::cl::ValueOptional, ++ llvm::cl::cat(OOPCategory)); ++static llvm::cl::opt UseSharedMemory( ++ "use-shared-memory", ++ llvm::cl::desc("Use shared memory to transfer generated code and data"), ++ llvm::cl::init(false), llvm::cl::cat(OOPCategory)); + static llvm::cl::list + ClangArgs("Xcc", + llvm::cl::desc("Argument to pass to the CompilerInvocation"), +@@ -47,6 +88,79 @@ static llvm::cl::opt OptHostSupportsJit("host-supports-jit", + static llvm::cl::list OptInputs(llvm::cl::Positional, + llvm::cl::desc("[code to run]")); + ++static llvm::Error sanitizeOopArguments(const char *ArgV0) { ++ // Only one of -oop-executor and -oop-executor-connect can be used. ++ if (!!OOPExecutor.getNumOccurrences() && ++ !!OOPExecutorConnect.getNumOccurrences()) ++ return llvm::make_error( ++ "Only one of -" + OOPExecutor.ArgStr + " and -" + ++ OOPExecutorConnect.ArgStr + " can be specified", ++ llvm::inconvertibleErrorCode()); ++ ++ llvm::Triple SystemTriple(llvm::sys::getProcessTriple()); ++ // TODO: Remove once out-of-process execution support is implemented for ++ // non-Unix platforms. ++ if ((!SystemTriple.isOSBinFormatELF() && ++ !SystemTriple.isOSBinFormatMachO()) && ++ (OOPExecutor.getNumOccurrences() || ++ OOPExecutorConnect.getNumOccurrences())) ++ return llvm::make_error( ++ "Out-of-process execution is only supported on Unix platforms", ++ llvm::inconvertibleErrorCode()); ++ ++ // If -slab-allocate is passed, check that we're not trying to use it in ++ // -oop-executor or -oop-executor-connect mode. ++ // ++ // FIXME: Remove once we enable remote slab allocation. ++ if (SlabAllocateSizeString != "") { ++ if (OOPExecutor.getNumOccurrences() || ++ OOPExecutorConnect.getNumOccurrences()) ++ return llvm::make_error( ++ "-slab-allocate cannot be used with -oop-executor or " ++ "-oop-executor-connect", ++ llvm::inconvertibleErrorCode()); ++ } ++ ++ // Out-of-process executors require the ORC runtime. ORC Runtime Path ++ // resolution is done in Interpreter.cpp. ++ ++ // If -oop-executor was used but no value was specified then use a sensible ++ // default. ++ if (!!OOPExecutor.getNumOccurrences() && OOPExecutor.empty()) { ++ llvm::SmallString<256> OOPExecutorPath(llvm::sys::fs::getMainExecutable( ++ ArgV0, reinterpret_cast(&sanitizeOopArguments))); ++ llvm::sys::path::remove_filename(OOPExecutorPath); ++ llvm::sys::path::append(OOPExecutorPath, "llvm-jitlink-executor"); ++ OOPExecutor = OOPExecutorPath.str().str(); ++ } ++ ++ return llvm::Error::success(); ++} ++ ++static llvm::Expected getSlabAllocSize(llvm::StringRef SizeString) { ++ SizeString = SizeString.trim(); ++ ++ uint64_t Units = 1024; ++ ++ if (SizeString.ends_with_insensitive("kb")) ++ SizeString = SizeString.drop_back(2).rtrim(); ++ else if (SizeString.ends_with_insensitive("mb")) { ++ Units = 1024 * 1024; ++ SizeString = SizeString.drop_back(2).rtrim(); ++ } else if (SizeString.ends_with_insensitive("gb")) { ++ Units = 1024 * 1024 * 1024; ++ SizeString = SizeString.drop_back(2).rtrim(); ++ } else if (SizeString.empty()) ++ return 0; ++ ++ uint64_t SlabSize = 0; ++ if (SizeString.getAsInteger(10, SlabSize)) ++ return llvm::make_error( ++ "Invalid numeric format for slab size", llvm::inconvertibleErrorCode()); ++ ++ return SlabSize * Units; ++} ++ + static void LLVMErrorHandler(void *UserData, const char *Message, + bool GenCrashDiag) { + auto &Diags = *static_cast(UserData); +@@ -86,7 +200,7 @@ struct ReplListCompleter { + clang::Interpreter &MainInterp; + ReplListCompleter(clang::IncrementalCompilerBuilder &CB, + clang::Interpreter &Interp) +- : CB(CB), MainInterp(Interp){}; ++ : CB(CB), MainInterp(Interp) {}; + + std::vector operator()(llvm::StringRef Buffer, + size_t Pos) const; +@@ -183,6 +297,19 @@ int main(int argc, const char **argv) { + DeviceCI = ExitOnErr(CB.CreateCudaDevice()); + } + ++ ExitOnErr(sanitizeOopArguments(argv[0])); ++ ++ clang::Interpreter::JITConfig Config; ++ Config.IsOutOfProcess = !OOPExecutor.empty() || !OOPExecutorConnect.empty(); ++ Config.OOPExecutor = OOPExecutor; ++ auto SizeOrErr = getSlabAllocSize(SlabAllocateSizeString); ++ if (!SizeOrErr) { ++ llvm::logAllUnhandledErrors(SizeOrErr.takeError(), llvm::errs(), "error: "); ++ return EXIT_FAILURE; ++ } ++ Config.SlabAllocateSize = *SizeOrErr; ++ Config.UseSharedMemory = UseSharedMemory; ++ + // FIXME: Investigate if we could use runToolOnCodeWithArgs from tooling. It + // can replace the boilerplate code for creation of the compiler instance. + std::unique_ptr CI; +@@ -214,8 +341,9 @@ int main(int argc, const char **argv) { + auto CudaRuntimeLibPath = CudaPath + "/lib/libcudart.so"; + ExitOnErr(Interp->LoadDynamicLibrary(CudaRuntimeLibPath.c_str())); + } +- } else +- Interp = ExitOnErr(clang::Interpreter::create(std::move(CI))); ++ } else { ++ Interp = ExitOnErr(clang::Interpreter::create(std::move(CI), Config)); ++ } + + bool HasError = false; + +@@ -243,15 +371,34 @@ int main(int argc, const char **argv) { + } + + Input += L; ++ // If we add more % commands, there should be better architecture than ++ // this. + if (Input == R"(%quit)") { + break; + } + if (Input == R"(%undo)") { + if (auto Err = Interp->Undo()) + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); ++ } else if (Input == R"(%help)") { ++ llvm::outs() << "%help\t\tlist clang-repl %commands\n" ++ << "%undo\t\tundo the previous input\n" ++ << "%lib\t\tlink a dynamic library\n" ++ << "%quit\t\texit clang-repl\n"; ++ } else if (Input == R"(%lib)") { ++ auto Err = llvm::make_error( ++ "%lib expects 1 argument: the path to a dynamic library\n", ++ std::error_code()); ++ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); + } else if (Input.rfind("%lib ", 0) == 0) { + if (auto Err = Interp->LoadDynamicLibrary(Input.data() + 5)) + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); ++ } else if (Input[0] == '%') { ++ auto Err = llvm::make_error( ++ llvm::formatv( ++ "Invalid % command \"{0}\", use \"%help\" to list commands\n", ++ Input), ++ std::error_code()); ++ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); + } else if (auto Err = Interp->ParseAndExecute(Input)) { + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); + } +@@ -267,4 +414,4 @@ int main(int argc, const char **argv) { + llvm::remove_fatal_error_handler(); + + return checkDiagErrors(Interp->getCompilerInstance(), HasError); +-} ++} +\ No newline at end of file +diff --git a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp +index 972c24abc..a75a0afa7 100644 +--- a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp ++++ b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp +@@ -635,16 +635,19 @@ Error ORCPlatformSupport::initialize(orc::JITDylib &JD) { + int32_t result; + auto E = ES.callSPSWrapper(WrapperAddr->getAddress(), + result, DSOHandles[&JD]); +- if (result) ++ if (E) ++ return E; ++ else if (result) + return make_error("dlupdate failed", + inconvertibleErrorCode()); +- return E; +- } +- return ES.callSPSWrapper(WrapperAddr->getAddress(), +- DSOHandles[&JD], JD.getName(), +- int32_t(ORC_RT_RTLD_LAZY)); ++ } else ++ return ES.callSPSWrapper(WrapperAddr->getAddress(), ++ DSOHandles[&JD], JD.getName(), ++ int32_t(ORC_RT_RTLD_LAZY)); + } else + return WrapperAddr.takeError(); ++ ++ return Error::success(); + } + + Error ORCPlatformSupport::deinitialize(orc::JITDylib &JD) { diff --git a/unittests/CppInterOp/CMakeLists.txt b/unittests/CppInterOp/CMakeLists.txt index 4b4b43bdd..994ab8951 100644 --- a/unittests/CppInterOp/CMakeLists.txt +++ b/unittests/CppInterOp/CMakeLists.txt @@ -1,13 +1,15 @@ +set(EXTRA_TEST_SOURCE_FILES Utils.cpp) + if (EMSCRIPTEN) # So we create a html file, as well as the javascript file set(CMAKE_EXECUTABLE_SUFFIX ".html") # Omitting CUDATest.cpp since Emscripten build currently has no GPU support # For Emscripten builds linking to gtest_main will not suffice for gtest to run # the tests and an explicitly main.cpp is needed - set(EXTRA_TEST_SOURCE_FILES main.cpp) + list(APPEND EXTRA_TEST_SOURCE_FILES main.cpp) else() # Do not need main.cpp for native builds, but we do have GPU support for native builds - set(EXTRA_TEST_SOURCE_FILES CUDATest.cpp) + list(APPEND EXTRA_TEST_SOURCE_FILES CUDATest.cpp) set(EXTRA_PATH_TEST_BINARIES /CppInterOpTests/unittests/bin/$/) endif() @@ -18,7 +20,6 @@ add_cppinterop_unittest(CppInterOpTests JitTest.cpp ScopeReflectionTest.cpp TypeReflectionTest.cpp - Utils.cpp VariableReflectionTest.cpp ${EXTRA_TEST_SOURCE_FILES} ) @@ -85,7 +86,7 @@ export_executable_symbols(CppInterOpTests) unset(LLVM_LINK_COMPONENTS) if (NOT EMSCRIPTEN) - set(EXTRA_TEST_SOURCE_FILES "") + set(EXTRA_TEST_SOURCE_FILES Utils.cpp) set(EXTRA_PATH_TEST_BINARIES /TestSharedLib/unittests/bin/$/) endif() diff --git a/unittests/CppInterOp/DynamicLibraryManagerTest.cpp b/unittests/CppInterOp/DynamicLibraryManagerTest.cpp index 4925af329..ca17a16ad 100644 --- a/unittests/CppInterOp/DynamicLibraryManagerTest.cpp +++ b/unittests/CppInterOp/DynamicLibraryManagerTest.cpp @@ -1,3 +1,4 @@ +#include "Utils.h" #include "CppInterOp/CppInterOp.h" #include "clang/Basic/Version.h" @@ -19,7 +20,7 @@ std::string GetExecutablePath(const char* Argv0) { return llvm::sys::fs::getMainExecutable(Argv0, MainAddr); } -TEST(DynamicLibraryManagerTest, Sanity) { +TYPED_TEST(CppInterOpTest, DynamicLibraryManagerTestSanity) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -28,8 +29,10 @@ TEST(DynamicLibraryManagerTest, Sanity) { defined(_WIN32) GTEST_SKIP() << "Test fails with Cling on Windows"; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; - EXPECT_TRUE(Cpp::CreateInterpreter()); + EXPECT_TRUE(TestFixture::CreateInterpreter()); EXPECT_FALSE(Cpp::GetFunctionAddress("ret_zero")); std::string BinaryPath = GetExecutablePath(/*Argv0=*/nullptr); @@ -66,7 +69,7 @@ TEST(DynamicLibraryManagerTest, Sanity) { // EXPECT_FALSE(Cpp::GetFunctionAddress("ret_zero")); } -TEST(DynamicLibraryManagerTest, BasicSymbolLookup) { +TYPED_TEST(CppInterOpTest, DynamicLibraryManagerTestBasicSymbolLookup) { #ifndef EMSCRIPTEN GTEST_SKIP() << "This test is only intended for Emscripten builds."; #else @@ -74,8 +77,10 @@ TEST(DynamicLibraryManagerTest, BasicSymbolLookup) { GTEST_SKIP() << "Support for loading shared libraries was added in LLVM 20."; #endif #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; - ASSERT_TRUE(Cpp::CreateInterpreter()); + ASSERT_TRUE(TestFixture::CreateInterpreter()); EXPECT_FALSE(Cpp::GetFunctionAddress("ret_zero")); // Load the library manually. Use known preload path (MEMFS path) diff --git a/unittests/CppInterOp/EnumReflectionTest.cpp b/unittests/CppInterOp/EnumReflectionTest.cpp index f4b3d47de..89f41dddf 100644 --- a/unittests/CppInterOp/EnumReflectionTest.cpp +++ b/unittests/CppInterOp/EnumReflectionTest.cpp @@ -12,51 +12,7 @@ using namespace TestUtils; using namespace llvm; using namespace clang; -TEST(ScopeReflectionTest, IsEnumScope) { - std::vector Decls, SubDecls; - std::string code = R"( - enum Switch { - OFF, - ON - }; - - Switch s = Switch::OFF; - - int i = Switch::ON; - )"; - - GetAllTopLevelDecls(code, Decls); - GetAllSubDecls(Decls[0], SubDecls); - EXPECT_TRUE(Cpp::IsEnumScope(Decls[0])); - EXPECT_FALSE(Cpp::IsEnumScope(Decls[1])); - EXPECT_FALSE(Cpp::IsEnumScope(Decls[2])); - EXPECT_FALSE(Cpp::IsEnumScope(SubDecls[0])); - EXPECT_FALSE(Cpp::IsEnumScope(SubDecls[1])); -} - -TEST(ScopeReflectionTest, IsEnumConstant) { - std::vector Decls, SubDecls; - std::string code = R"( - enum Switch { - OFF, - ON - }; - - Switch s = Switch::OFF; - - int i = Switch::ON; - )"; - - GetAllTopLevelDecls(code, Decls); - GetAllSubDecls(Decls[0], SubDecls); - EXPECT_FALSE(Cpp::IsEnumConstant(Decls[0])); - EXPECT_FALSE(Cpp::IsEnumConstant(Decls[1])); - EXPECT_FALSE(Cpp::IsEnumConstant(Decls[2])); - EXPECT_TRUE(Cpp::IsEnumConstant(SubDecls[0])); - EXPECT_TRUE(Cpp::IsEnumConstant(SubDecls[1])); -} - -TEST(EnumReflectionTest, IsEnumType) { +TYPED_TEST(CppInterOpTest, EnumReflectionTestIsEnumType) { std::vector Decls; std::string code = R"( enum class E { @@ -84,7 +40,7 @@ TEST(EnumReflectionTest, IsEnumType) { EXPECT_TRUE(Cpp::IsEnumType(Cpp::GetVariableType(Decls[5]))); } -TEST(EnumReflectionTest, GetIntegerTypeFromEnumScope) { +TYPED_TEST(CppInterOpTest, EnumReflectionTestGetIntegerTypeFromEnumScope) { std::vector Decls; std::string code = R"( enum Switch : bool { @@ -134,7 +90,7 @@ TEST(EnumReflectionTest, GetIntegerTypeFromEnumScope) { EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(Decls[5])),"NULL TYPE"); } -TEST(EnumReflectionTest, GetIntegerTypeFromEnumType) { +TYPED_TEST(CppInterOpTest, EnumReflectionTestGetIntegerTypeFromEnumType) { std::vector Decls; std::string code = R"( enum Switch : bool { @@ -194,7 +150,7 @@ TEST(EnumReflectionTest, GetIntegerTypeFromEnumType) { EXPECT_EQ(get_int_type_from_enum_var(Decls[11]), "NULL TYPE"); // When a non Enum Type variable is used } -TEST(EnumReflectionTest, GetEnumConstants) { +TYPED_TEST(CppInterOpTest, EnumReflectionTestGetEnumConstants) { std::vector Decls; std::string code = R"( enum ZeroEnum { @@ -238,7 +194,7 @@ TEST(EnumReflectionTest, GetEnumConstants) { EXPECT_EQ(Cpp::GetEnumConstants(Decls[5]).size(), 0); } -TEST(EnumReflectionTest, GetEnumConstantType) { +TYPED_TEST(CppInterOpTest, EnumReflectionTestGetEnumConstantType) { std::vector Decls; std::string code = R"( enum Enum0 { @@ -269,7 +225,7 @@ TEST(EnumReflectionTest, GetEnumConstantType) { EXPECT_EQ(get_enum_constant_type_as_str(nullptr), "NULL TYPE"); } -TEST(EnumReflectionTest, GetEnumConstantValue) { +TYPED_TEST(CppInterOpTest, EnumReflectionTestGetEnumConstantValue) { std::vector Decls; std::string code = R"( enum Counter { @@ -297,7 +253,7 @@ TEST(EnumReflectionTest, GetEnumConstantValue) { EXPECT_EQ(Cpp::GetEnumConstantValue(Decls[1]), 0); // Checking value of non enum constant } -TEST(EnumReflectionTest, GetEnums) { +TYPED_TEST(CppInterOpTest, EnumReflectionTestGetEnums) { std::string code = R"( enum Color { Red, @@ -338,7 +294,7 @@ TEST(EnumReflectionTest, GetEnums) { int myVariable; )"; - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); Interp->declare(code); std::vector enumNames1, enumNames2, enumNames3, enumNames4; Cpp::TCppScope_t globalscope = Cpp::GetScope("", 0); diff --git a/unittests/CppInterOp/FunctionReflectionTest.cpp b/unittests/CppInterOp/FunctionReflectionTest.cpp index 1c76bf8d1..96dd45477 100644 --- a/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -19,7 +19,7 @@ using namespace TestUtils; using namespace llvm; using namespace clang; -TEST(FunctionReflectionTest, GetClassMethods) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetClassMethods) { std::vector Decls; std::string code = R"( class A; @@ -171,7 +171,7 @@ TEST(FunctionReflectionTest, GetClassMethods) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, ConstructorInGetClassMethods) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestConstructorInGetClassMethods) { std::vector Decls; std::string code = R"( struct S { @@ -195,7 +195,7 @@ TEST(FunctionReflectionTest, ConstructorInGetClassMethods) { EXPECT_TRUE(has_constructor(Decls[0])); } -TEST(FunctionReflectionTest, HasDefaultConstructor) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestHasDefaultConstructor) { std::vector Decls; std::string code = R"( class A { @@ -236,7 +236,7 @@ TEST(FunctionReflectionTest, HasDefaultConstructor) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, GetDestructor) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetDestructor) { std::vector Decls; std::string code = R"( class A { @@ -272,7 +272,7 @@ TEST(FunctionReflectionTest, GetDestructor) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, GetFunctionsUsingName) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionsUsingName) { std::vector Decls; std::string code = R"( class A { @@ -316,7 +316,7 @@ TEST(FunctionReflectionTest, GetFunctionsUsingName) { EXPECT_EQ(get_number_of_funcs_using_name(Decls[2], ""), 0); } -TEST(FunctionReflectionTest, GetClassDecls) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetClassDecls) { std::vector Decls, SubDecls; std::string code = R"( class MyTemplatedMethodClass { @@ -353,7 +353,7 @@ TEST(FunctionReflectionTest, GetClassDecls) { EXPECT_EQ(Cpp::GetName(methods[3]), Cpp::GetName(SubDecls[8])); } -TEST(FunctionReflectionTest, GetFunctionTemplatedDecls) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionTemplatedDecls) { std::vector Decls, SubDecls; std::string code = R"( class MyTemplatedMethodClass { @@ -390,7 +390,7 @@ TEST(FunctionReflectionTest, GetFunctionTemplatedDecls) { EXPECT_EQ(Cpp::GetName(template_methods[3]), Cpp::GetName(SubDecls[6])); } -TEST(FunctionReflectionTest, GetFunctionReturnType) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionReturnType) { std::vector Decls, SubDecls, TemplateSubDecls; std::string code = R"( namespace N { class C {}; } @@ -487,7 +487,7 @@ TEST(FunctionReflectionTest, GetFunctionReturnType) { "double"); } -TEST(FunctionReflectionTest, GetFunctionNumArgs) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionNumArgs) { std::vector Decls, TemplateSubDecls; std::string code = R"( void f1() {} @@ -526,7 +526,7 @@ TEST(FunctionReflectionTest, GetFunctionNumArgs) { EXPECT_EQ(Cpp::GetFunctionNumArgs(TemplateSubDecls[3]), 3); } -TEST(FunctionReflectionTest, GetFunctionRequiredArgs) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionRequiredArgs) { std::vector Decls, TemplateSubDecls; std::string code = R"( void f1() {} @@ -561,7 +561,7 @@ TEST(FunctionReflectionTest, GetFunctionRequiredArgs) { EXPECT_EQ(Cpp::GetFunctionRequiredArgs(TemplateSubDecls[3]), 2); } -TEST(FunctionReflectionTest, GetFunctionArgType) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionArgType) { std::vector Decls; std::string code = R"( void f1(int i, double d, long l, char ch) {} @@ -581,7 +581,7 @@ TEST(FunctionReflectionTest, GetFunctionArgType) { EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[2], 0)), "NULL TYPE"); } -TEST(FunctionReflectionTest, GetFunctionSignature) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionSignature) { std::vector Decls; std::string code = R"( class C { @@ -625,7 +625,7 @@ TEST(FunctionReflectionTest, GetFunctionSignature) { EXPECT_EQ(Cpp::GetFunctionSignature(nullptr), ""); } -TEST(FunctionReflectionTest, IsTemplatedFunction) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestIsTemplatedFunction) { std::vector Decls; std::vector SubDeclsC1; std::string code = R"( @@ -665,7 +665,7 @@ TEST(FunctionReflectionTest, IsTemplatedFunction) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, ExistsFunctionTemplate) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestExistsFunctionTemplate) { std::vector Decls; std::string code = R"( template @@ -693,7 +693,7 @@ TEST(FunctionReflectionTest, ExistsFunctionTemplate) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, InstantiateTemplateFunctionFromString) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestInstantiateTemplateFunctionFromString) { #if CLANG_VERSION_MAJOR == 18 && defined(CPPINTEROP_USE_CLING) && \ defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) GTEST_SKIP() << "Test fails with Cling on Windows on ARM"; @@ -701,7 +701,7 @@ TEST(FunctionReflectionTest, InstantiateTemplateFunctionFromString) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector interpreter_args = { "-include", "new" }; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); std::string code = R"(#include )"; Interp->process(code); const char* str = "std::make_unique"; @@ -709,7 +709,7 @@ TEST(FunctionReflectionTest, InstantiateTemplateFunctionFromString) { EXPECT_TRUE(Instance1); } -TEST(FunctionReflectionTest, InstantiateFunctionTemplate) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestInstantiateFunctionTemplate) { std::vector Decls; std::string code = R"( template T TrivialFnTemplate() { return T(); } @@ -729,7 +729,7 @@ template T TrivialFnTemplate() { return T(); } EXPECT_TRUE(TA1.getAsType()->isIntegerType()); } -TEST(FunctionReflectionTest, InstantiateTemplateMethod) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestInstantiateTemplateMethod) { std::vector Decls; std::string code = R"( class MyTemplatedMethodClass { @@ -757,7 +757,7 @@ TEST(FunctionReflectionTest, InstantiateTemplateMethod) { EXPECT_TRUE(TA1.getAsType()->isIntegerType()); } -TEST(FunctionReflectionTest, LookupConstructors) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestLookupConstructors) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -798,7 +798,7 @@ TEST(FunctionReflectionTest, LookupConstructors) { EXPECT_EQ(Cpp::GetFunctionSignature(ctors[3]), "MyClass::MyClass(T t)"); } -TEST(FunctionReflectionTest, GetClassTemplatedMethods) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetClassTemplatedMethods) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -856,7 +856,7 @@ TEST(FunctionReflectionTest, GetClassTemplatedMethods) { "void MyClass::templatedStaticMethod(T param)"); } -TEST(FunctionReflectionTest, GetClassTemplatedMethods_VariadicsAndOthers) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetClassTemplatedMethods_VariadicsAndOthers) { std::vector Decls; std::string code = R"( class MyClass { @@ -910,7 +910,7 @@ TEST(FunctionReflectionTest, GetClassTemplatedMethods_VariadicsAndOthers) { "void MyClass::staticVariadic(T t, Args ...args)"); } -TEST(FunctionReflectionTest, InstantiateVariadicFunction) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestInstantiateVariadicFunction) { std::vector Decls; std::string code = R"( class MyClass {}; @@ -973,7 +973,7 @@ TEST(FunctionReflectionTest, InstantiateVariadicFunction) { "fixedParam, MyClass args, double args)"); } -TEST(FunctionReflectionTest, BestOverloadFunctionMatch1) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestBestOverloadFunctionMatch1) { std::vector Decls; std::string code = R"( class MyTemplatedMethodClass { @@ -1053,7 +1053,7 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch1) { "template<> long MyTemplatedMethodClass::get_size<1, int>(int a)"); } -TEST(FunctionReflectionTest, BestOverloadFunctionMatch2) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestBestOverloadFunctionMatch2) { std::vector Decls; std::string code = R"( template @@ -1123,7 +1123,7 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch2) { "void somefunc(int arg1, double arg2)"); } -TEST(FunctionReflectionTest, BestOverloadFunctionMatch3) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestBestOverloadFunctionMatch3) { std::vector Decls; std::string code = R"( template @@ -1201,7 +1201,7 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch3) { "template<> A A::operator-(A rhs)"); } -TEST(FunctionReflectionTest, BestOverloadFunctionMatch4) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestBestOverloadFunctionMatch4) { std::vector Decls, SubDecls; std::string code = R"( template @@ -1272,7 +1272,7 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch4) { "template<> void B::fn(A x, A y)"); } -TEST(FunctionReflectionTest, IsPublicMethod) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestIsPublicMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1299,7 +1299,7 @@ TEST(FunctionReflectionTest, IsPublicMethod) { EXPECT_FALSE(Cpp::IsPublicMethod(SubDecls[9])); } -TEST(FunctionReflectionTest, IsProtectedMethod) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestIsProtectedMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1324,7 +1324,7 @@ TEST(FunctionReflectionTest, IsProtectedMethod) { EXPECT_TRUE(Cpp::IsProtectedMethod(SubDecls[8])); } -TEST(FunctionReflectionTest, IsPrivateMethod) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestIsPrivateMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1349,7 +1349,7 @@ TEST(FunctionReflectionTest, IsPrivateMethod) { EXPECT_FALSE(Cpp::IsPrivateMethod(SubDecls[8])); } -TEST(FunctionReflectionTest, IsConstructor) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestIsConstructor) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1396,7 +1396,7 @@ TEST(FunctionReflectionTest, IsConstructor) { EXPECT_EQ(templCtorCount, 1); } -TEST(FunctionReflectionTest, IsDestructor) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestIsDestructor) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1421,7 +1421,7 @@ TEST(FunctionReflectionTest, IsDestructor) { EXPECT_FALSE(Cpp::IsDestructor(SubDecls[8])); } -TEST(FunctionReflectionTest, IsStaticMethod) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestIsStaticMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1438,7 +1438,7 @@ TEST(FunctionReflectionTest, IsStaticMethod) { EXPECT_TRUE(Cpp::IsStaticMethod(SubDecls[2])); } -TEST(FunctionReflectionTest, GetFunctionAddress) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionAddress) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -1449,6 +1449,10 @@ TEST(FunctionReflectionTest, GetFunctionAddress) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif + + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + std::vector Decls; std::string code = "int f1(int i) { return i * i; }"; std::vector interpreter_args = {"-include", "new"}; @@ -1488,7 +1492,7 @@ TEST(FunctionReflectionTest, GetFunctionAddress) { EXPECT_TRUE(Cpp::GetFunctionAddress(add1_double)); } -TEST(FunctionReflectionTest, IsVirtualMethod) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestIsVirtualMethod) { std::vector Decls, SubDecls; std::string code = R"( class A { @@ -1508,7 +1512,7 @@ TEST(FunctionReflectionTest, IsVirtualMethod) { EXPECT_FALSE(Cpp::IsVirtualMethod(Decls[0])); } -TEST(FunctionReflectionTest, JitCallAdvanced) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestJitCallAdvanced) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -1517,6 +1521,9 @@ TEST(FunctionReflectionTest, JitCallAdvanced) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + Cpp::JitCall JC = Cpp::MakeFunctionCallable(nullptr); EXPECT_TRUE(JC.getKind() == Cpp::JitCall::kUnknown); @@ -1556,7 +1563,7 @@ TEST(FunctionReflectionTest, JitCallAdvanced) { #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST #ifndef _WIN32 // Death tests do not work on Windows -TEST(FunctionReflectionTest, JitCallDebug) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestJitCallDebug) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -1565,6 +1572,9 @@ TEST(FunctionReflectionTest, JitCallDebug) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1649,7 +1659,7 @@ instantiation_in_host(); template int instantiation_in_host(); #endif -TEST(FunctionReflectionTest, GetFunctionCallWrapper) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionCallWrapper) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -1658,6 +1668,8 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { #if defined(CPPINTEROP_USE_CLING) && defined(_WIN32) GTEST_SKIP() << "Disabled, invoking functions containing printf does not work with Cling on Windows"; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; std::vector Decls; std::string code = R"( int f1(int i) { return i * i; } @@ -2186,7 +2198,7 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { EXPECT_FALSE(Cpp::IsLambdaClass(Cpp::GetFunctionReturnType(bar))); } -TEST(FunctionReflectionTest, IsConstMethod) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestIsConstMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -2204,7 +2216,7 @@ TEST(FunctionReflectionTest, IsConstMethod) { EXPECT_FALSE(Cpp::IsConstMethod(method)); } -TEST(FunctionReflectionTest, GetFunctionArgName) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionArgName) { std::vector Decls; std::string code = R"( void f1(int i, double d, long l, char ch) {} @@ -2244,7 +2256,7 @@ TEST(FunctionReflectionTest, GetFunctionArgName) { EXPECT_EQ(Cpp::GetFunctionArgName(Decls[4], 3), "l"); } -TEST(FunctionReflectionTest, GetFunctionArgDefault) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetFunctionArgDefault) { std::vector Decls; std::string code = R"( void f1(int i, double d = 4.0, const char *s = "default", char ch = 'c') {} @@ -2308,7 +2320,7 @@ TEST(FunctionReflectionTest, GetFunctionArgDefault) { EXPECT_EQ(Cpp::GetFunctionArgDefault(fn, 1), "S()"); } -TEST(FunctionReflectionTest, Construct) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestConstruct) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -2319,6 +2331,8 @@ TEST(FunctionReflectionTest, Construct) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; std::vector interpreter_args = {"-include", "new"}; std::vector Decls, SubDecls; @@ -2389,7 +2403,7 @@ TEST(FunctionReflectionTest, Construct) { } // Test zero initialization of PODs and default initialization cases -TEST(FunctionReflectionTest, ConstructPOD) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestConstructPOD) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -2400,8 +2414,10 @@ TEST(FunctionReflectionTest, ConstructPOD) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Interp->declare(R"( namespace PODS { @@ -2432,7 +2448,7 @@ TEST(FunctionReflectionTest, ConstructPOD) { } // Test nested constructor calls -TEST(FunctionReflectionTest, ConstructNested) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestConstructNested) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -2443,9 +2459,11 @@ TEST(FunctionReflectionTest, ConstructNested) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Interp->declare(R"( #include @@ -2494,7 +2512,7 @@ TEST(FunctionReflectionTest, ConstructNested) { output.clear(); } -TEST(FunctionReflectionTest, ConstructArray) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestConstructArray) { #if defined(EMSCRIPTEN) GTEST_SKIP() << "Test fails for Emscripten builds"; #endif @@ -2503,8 +2521,10 @@ TEST(FunctionReflectionTest, ConstructArray) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); Interp->declare(R"( #include @@ -2546,7 +2566,7 @@ TEST(FunctionReflectionTest, ConstructArray) { output.clear(); } -TEST(FunctionReflectionTest, Destruct) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestDestruct) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -2556,9 +2576,11 @@ TEST(FunctionReflectionTest, Destruct) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Interp->declare(R"( #include @@ -2602,7 +2624,7 @@ TEST(FunctionReflectionTest, Destruct) { clang_Interpreter_takeInterpreterAsPtr(I); clang_Interpreter_dispose(I); - // Failure test, this wrapper should not compile since we explicitly delete + // Failure Test, FunctionReflectionTestthis wrapper should not compile since we explicitly delete // the destructor Interp->declare(R"( class D { @@ -2617,7 +2639,7 @@ TEST(FunctionReflectionTest, Destruct) { EXPECT_FALSE(Cpp::Destruct(object, scope)); } -TEST(FunctionReflectionTest, DestructArray) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestDestructArray) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -2627,9 +2649,11 @@ TEST(FunctionReflectionTest, DestructArray) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Interp->declare(R"( #include @@ -2694,14 +2718,14 @@ TEST(FunctionReflectionTest, DestructArray) { output.clear(); } -TEST(FunctionReflectionTest, UndoTest) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestUndoTest) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #else - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); EXPECT_EQ(Cpp::Process("int a = 5;"), 0); EXPECT_EQ(Cpp::Process("int b = 10;"), 0); EXPECT_EQ(Cpp::Process("int x = 5;"), 0); @@ -2721,11 +2745,14 @@ TEST(FunctionReflectionTest, UndoTest) { #endif } -TEST(FunctionReflectionTest, FailingTest1) { +TYPED_TEST(CppInterOpTest, FunctionReflectionTestFailingTest1) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif - Cpp::CreateInterpreter(); +#ifdef EMSCRIPTEN_SHARED_LIBRARY + GTEST_SKIP() << "Test fails for Emscipten shared library builds"; +#endif + TestFixture::CreateInterpreter(); EXPECT_FALSE(Cpp::Declare(R"( class WithOutEqualOp1 {}; class WithOutEqualOp2 {}; diff --git a/unittests/CppInterOp/InterpreterTest.cpp b/unittests/CppInterOp/InterpreterTest.cpp index e9b82ea1d..bcaf6868e 100644 --- a/unittests/CppInterOp/InterpreterTest.cpp +++ b/unittests/CppInterOp/InterpreterTest.cpp @@ -27,16 +27,23 @@ using ::testing::StartsWith; -TEST(InterpreterTest, Version) { +class InterpreterTest : public ::testing::TestWithParam { +protected: + void SetUp() override { + TestUtils::current_config = GetParam(); + } +}; + +TYPED_TEST(CppInterOpTest, InterpreterTestVersion) { EXPECT_THAT(Cpp::GetVersion(), StartsWith("CppInterOp version")); } #ifdef NDEBUG -TEST(InterpreterTest, DISABLED_DebugFlag) { +TYPED_TEST(CppInterOpTest, DISABLED_InterpreterTestDebugFlag) { #else -TEST(InterpreterTest, DebugFlag) { +TYPED_TEST(CppInterOpTest, InterpreterTestDebugFlag) { #endif // NDEBUG - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); EXPECT_FALSE(Cpp::IsDebugOutputEnabled()); std::string cerrs; testing::internal::CaptureStderr(); @@ -58,7 +65,7 @@ TEST(InterpreterTest, DebugFlag) { EXPECT_STREQ(cerrs.c_str(), ""); } -TEST(InterpreterTest, Evaluate) { +TYPED_TEST(CppInterOpTest, InterpreterTestEvaluate) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -67,10 +74,13 @@ TEST(InterpreterTest, Evaluate) { #endif if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; // EXPECT_TRUE(Cpp::Evaluate(I, "") == 0); //EXPECT_TRUE(Cpp::Evaluate(I, "__cplusplus;") == 201402); // Due to a deficiency in the clang-repl implementation to get the value we // always must omit the ; + TestFixture::CreateInterpreter(); EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201402); bool HadError; @@ -80,10 +90,12 @@ TEST(InterpreterTest, Evaluate) { EXPECT_FALSE(HadError) ; } -TEST(InterpreterTest, DeleteInterpreter) { - auto* I1 = Cpp::CreateInterpreter(); - auto* I2 = Cpp::CreateInterpreter(); - auto* I3 = Cpp::CreateInterpreter(); +TYPED_TEST(CppInterOpTest, InterpreterTestDeleteInterpreter) { + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + auto* I1 = TestFixture::CreateInterpreter(); + auto* I2 = TestFixture::CreateInterpreter(); + auto* I3 = TestFixture::CreateInterpreter(); EXPECT_TRUE(I1 && I2 && I3) << "Failed to create interpreters"; EXPECT_EQ(I3, Cpp::GetInterpreter()) << "I3 is not active"; @@ -98,14 +110,16 @@ TEST(InterpreterTest, DeleteInterpreter) { EXPECT_EQ(I2, Cpp::GetInterpreter()) << "I2 is not active"; } -TEST(InterpreterTest, ActivateInterpreter) { +TYPED_TEST(CppInterOpTest, InterpreterTestActivateInterpreter) { #ifdef EMSCRIPTEN_STATIC_LIBRARY GTEST_SKIP() << "Test fails for Emscipten static library build"; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; EXPECT_FALSE(Cpp::ActivateInterpreter(nullptr)); - auto* Cpp14 = Cpp::CreateInterpreter({"-std=c++14"}); - auto* Cpp17 = Cpp::CreateInterpreter({"-std=c++17"}); - auto* Cpp20 = Cpp::CreateInterpreter({"-std=c++20"}); + auto* Cpp14 = TestFixture::CreateInterpreter({"-std=c++14"}); + auto* Cpp17 = TestFixture::CreateInterpreter({"-std=c++17"}); + auto* Cpp20 = TestFixture::CreateInterpreter({"-std=c++20"}); EXPECT_TRUE(Cpp14 && Cpp17 && Cpp20); EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 202002L) @@ -124,17 +138,19 @@ TEST(InterpreterTest, ActivateInterpreter) { EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201703L); } -TEST(InterpreterTest, Process) { +TYPED_TEST(CppInterOpTest, InterpreterTestProcess) { #ifdef EMSCRIPTEN_STATIC_LIBRARY GTEST_SKIP() << "Test fails for Emscipten static library build"; #endif #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector interpreter_args = { "-include", "new" }; - auto* I = Cpp::CreateInterpreter(interpreter_args); + auto* I = TestFixture::CreateInterpreter(interpreter_args); EXPECT_TRUE(Cpp::Process("") == 0); EXPECT_TRUE(Cpp::Process("int a = 12;") == 0); EXPECT_FALSE(Cpp::Process("error_here;") == 0); @@ -152,9 +168,10 @@ TEST(InterpreterTest, Process) { clang_Interpreter_dispose(CXI); } -TEST(InterpreterTest, EmscriptenExceptionHandling) { +TYPED_TEST(CppInterOpTest, InterpreterTestEmscriptenExceptionHandling) { #ifndef EMSCRIPTEN - GTEST_SKIP() << "This test is intended to check exception handling for Emscripten builds."; + GTEST_SKIP() << "This test is intended to check exception handling for " + "Emscripten builds."; #endif std::vector Args = { @@ -179,8 +196,8 @@ TEST(InterpreterTest, EmscriptenExceptionHandling) { EXPECT_TRUE(Cpp::Process(tryCatchCode) == 0); } -TEST(InterpreterTest, CreateInterpreter) { - auto* I = Cpp::CreateInterpreter(); +TYPED_TEST(CppInterOpTest, InterpreterTestCreateInterpreter) { + auto* I = TestFixture::CreateInterpreter(); EXPECT_TRUE(I); // Check if the default standard is c++14 @@ -192,7 +209,7 @@ TEST(InterpreterTest, CreateInterpreter) { EXPECT_TRUE(Cpp::GetNamed("cpp14")); EXPECT_FALSE(Cpp::GetNamed("cppUnknown")); - I = Cpp::CreateInterpreter({"-std=c++17"}); + I = TestFixture::CreateInterpreter({"-std=c++17"}); Cpp::Declare("#if __cplusplus==201703L\n" "int cpp17() { return 2017; }\n" "#else\n" @@ -215,7 +232,7 @@ TEST(InterpreterTest, CreateInterpreter) { } #ifndef CPPINTEROP_USE_CLING -TEST(InterpreterTest, CreateInterpreterCAPI) { +TYPED_TEST(CppInterOpTest, InterpreterTestCreateInterpreterCAPI) { const char* argv[] = {"-std=c++17"}; auto *CXI = clang_createInterpreter(argv, 1); auto CLI = clang_Interpreter_getClangInterpreter(CXI); @@ -223,7 +240,7 @@ TEST(InterpreterTest, CreateInterpreterCAPI) { clang_Interpreter_dispose(CXI); } -TEST(InterpreterTest, CreateInterpreterCAPIFailure) { +TYPED_TEST(CppInterOpTest, InterpreterTestCreateInterpreterCAPIFailure) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif @@ -234,17 +251,17 @@ TEST(InterpreterTest, CreateInterpreterCAPIFailure) { #endif #ifdef LLVM_BINARY_DIR -TEST(InterpreterTest, DetectResourceDir) { +TYPED_TEST(CppInterOpTest, InterpreterTestDetectResourceDir) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif #else -TEST(InterpreterTest, DISABLED_DetectResourceDir) { +TYPED_TEST(CppInterOpTest, InterpreterTestDISABLED_DetectResourceDir) { #endif // LLVM_BINARY_DIR #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); EXPECT_STRNE(Cpp::DetectResourceDir().c_str(), Cpp::GetResourceDir()); llvm::SmallString<256> Clang(LLVM_BINARY_DIR); llvm::sys::path::append(Clang, "bin", "clang"); @@ -256,7 +273,7 @@ TEST(InterpreterTest, DISABLED_DetectResourceDir) { EXPECT_STREQ(DetectedPath.c_str(), Cpp::GetResourceDir()); } -TEST(InterpreterTest, DetectSystemCompilerIncludePaths) { +TYPED_TEST(CppInterOpTest, InterpreterTestDetectSystemCompilerIncludePaths) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -268,7 +285,9 @@ TEST(InterpreterTest, DetectSystemCompilerIncludePaths) { EXPECT_FALSE(includes.empty()); } -TEST(InterpreterTest, IncludePaths) { +TYPED_TEST(CppInterOpTest, InterpreterTestIncludePaths) { + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; std::vector includes; Cpp::GetIncludePaths(includes); EXPECT_FALSE(includes.empty()); @@ -292,9 +311,9 @@ TEST(InterpreterTest, IncludePaths) { std::end(includes)); } -TEST(InterpreterTest, CodeCompletion) { +TYPED_TEST(CppInterOpTest, InterpreterTestCodeCompletion) { #if CLANG_VERSION_MAJOR >= 18 || defined(CPPINTEROP_USE_CLING) - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); std::vector cc; Cpp::Declare("int foo = 12;"); Cpp::CodeComplete(cc, "f", 1, 2); @@ -312,7 +331,7 @@ TEST(InterpreterTest, CodeCompletion) { #endif } -TEST(InterpreterTest, ExternalInterpreterTest) { +TYPED_TEST(CppInterOpTest, InterpreterTestExternalInterpreterTest) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; diff --git a/unittests/CppInterOp/JitTest.cpp b/unittests/CppInterOp/JitTest.cpp index a1b6909b7..0137095c1 100644 --- a/unittests/CppInterOp/JitTest.cpp +++ b/unittests/CppInterOp/JitTest.cpp @@ -11,7 +11,7 @@ static int printf_jit(const char* format, ...) { return 0; } -TEST(JitTest, InsertOrReplaceJitSymbol) { +TYPED_TEST(CppInterOpTest, JitTestInsertOrReplaceJitSymbol) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -20,6 +20,8 @@ TEST(JitTest, InsertOrReplaceJitSymbol) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; std::vector Decls; std::string code = R"( extern "C" int printf(const char*,...); @@ -39,7 +41,9 @@ TEST(JitTest, InsertOrReplaceJitSymbol) { EXPECT_TRUE(Cpp::InsertOrReplaceJitSymbol("non_existent", 0)); } -TEST(Streams, StreamRedirect) { +TYPED_TEST(CppInterOpTest, JitTestStreamRedirect) { + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; // printf and etc are fine here. // NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) Cpp::BeginStdStreamCapture(Cpp::kStdOut); @@ -70,3 +74,39 @@ TEST(Streams, StreamRedirect) { EXPECT_STREQ(cerrs.c_str(), "Err\nStdErr\n"); // NOLINTEND(cppcoreguidelines-pro-type-vararg) } + +TYPED_TEST(CppInterOpTest, JitTestStreamRedirectJIT) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif +#ifdef CPPINTEROP_USE_CLING + GTEST_SKIP() << "Test fails for cling builds"; +#endif + TestFixture::CreateInterpreter(); + Interp->process(R"( + #include + printf("%s\n", "Hello World"); + fprintf(stderr, "%s\n", "Hello Err"); + fflush(nullptr); + )"); + + Cpp::BeginStdStreamCapture(Cpp::kStdOut); + Cpp::BeginStdStreamCapture(Cpp::kStdErr); + Interp->process(R"( + #include + printf("%s\n", "Hello World"); + fprintf(stderr, "%s\n", "Hello Err"); + fflush(nullptr); + )"); + std::string CapturedStringErr = Cpp::EndStdStreamCapture(); + std::string CapturedStringOut = Cpp::EndStdStreamCapture(); + + EXPECT_STREQ(CapturedStringOut.c_str(), "Hello World\n"); + EXPECT_STREQ(CapturedStringErr.c_str(), "Hello Err\n"); +} + diff --git a/unittests/CppInterOp/ScopeReflectionTest.cpp b/unittests/CppInterOp/ScopeReflectionTest.cpp index 53b2ee894..62a472554 100644 --- a/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -25,7 +25,53 @@ using namespace TestUtils; using namespace llvm; using namespace clang; -TEST(ScopeReflectionTest, Demangle) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsEnumScope) { + std::vector Decls; + std::vector SubDecls; + std::string code = R"( + enum Switch { + OFF, + ON + }; + + Switch s = Switch::OFF; + + int i = Switch::ON; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + EXPECT_TRUE(Cpp::IsEnumScope(Decls[0])); + EXPECT_FALSE(Cpp::IsEnumScope(Decls[1])); + EXPECT_FALSE(Cpp::IsEnumScope(Decls[2])); + EXPECT_FALSE(Cpp::IsEnumScope(SubDecls[0])); + EXPECT_FALSE(Cpp::IsEnumScope(SubDecls[1])); +} + +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsEnumConstant) { + std::vector Decls; + std::vector SubDecls; + std::string code = R"( + enum Switch { + OFF, + ON + }; + + Switch s = Switch::OFF; + + int i = Switch::ON; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + EXPECT_FALSE(Cpp::IsEnumConstant(Decls[0])); + EXPECT_FALSE(Cpp::IsEnumConstant(Decls[1])); + EXPECT_FALSE(Cpp::IsEnumConstant(Decls[2])); + EXPECT_TRUE(Cpp::IsEnumConstant(SubDecls[0])); + EXPECT_TRUE(Cpp::IsEnumConstant(SubDecls[1])); +} + +TYPED_TEST(CppInterOpTest, ScopeReflectionTestDemangle) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -55,7 +101,7 @@ TEST(ScopeReflectionTest, Demangle) { std::string::npos); } -TEST(ScopeReflectionTest, IsAggregate) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsAggregate) { std::vector Decls; std::string code = R"( char cv[4] = {}; @@ -81,7 +127,7 @@ TEST(ScopeReflectionTest, IsAggregate) { } // Check that the CharInfo table has been constructed reasonably. -TEST(ScopeReflectionTest, IsNamespace) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsNamespace) { std::vector Decls; GetAllTopLevelDecls("namespace N {} class C{}; int I;", Decls); EXPECT_TRUE(Cpp::IsNamespace(Decls[0])); @@ -89,7 +135,7 @@ TEST(ScopeReflectionTest, IsNamespace) { EXPECT_FALSE(Cpp::IsNamespace(Decls[2])); } -TEST(ScopeReflectionTest, IsClass) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsClass) { std::vector Decls; GetAllTopLevelDecls("namespace N {} class C{}; int I;", Decls); EXPECT_FALSE(Cpp::IsClass(Decls[0])); @@ -97,7 +143,7 @@ TEST(ScopeReflectionTest, IsClass) { EXPECT_FALSE(Cpp::IsClass(Decls[2])); } -TEST(ScopeReflectionTest, IsClassPolymorphic) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsClassPolymorphic) { std::vector Decls; GetAllTopLevelDecls(R"( namespace N {} @@ -119,7 +165,7 @@ TEST(ScopeReflectionTest, IsClassPolymorphic) { EXPECT_FALSE(Cpp::IsClassPolymorphic(Decls[3])); } -TEST(ScopeReflectionTest, IsComplete) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsComplete) { std::vector Decls; std::string code = R"( namespace N {} @@ -144,7 +190,7 @@ TEST(ScopeReflectionTest, IsComplete) { EXPECT_FALSE(Cpp::IsComplete(nullptr)); } -TEST(ScopeReflectionTest, SizeOf) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestSizeOf) { std::vector Decls; std::string code = R"(namespace N {} class C{}; int I; struct S; enum E : int; union U{}; class Size4{int i;}; @@ -163,7 +209,7 @@ TEST(ScopeReflectionTest, SizeOf) { } -TEST(ScopeReflectionTest, IsBuiltin) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsBuiltin) { #if CLANG_VERSION_MAJOR == 18 && defined(CPPINTEROP_USE_CLING) && \ defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) GTEST_SKIP() << "Test fails with Cling on Windows on ARM"; @@ -176,7 +222,7 @@ TEST(ScopeReflectionTest, IsBuiltin) { std::vector interpreter_args = { "-include", "new" }; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); ASTContext &C = Interp->getCI()->getASTContext(); EXPECT_TRUE(Cpp::IsBuiltin(C.BoolTy.getAsOpaquePtr())); EXPECT_TRUE(Cpp::IsBuiltin(C.CharTy.getAsOpaquePtr())); @@ -199,7 +245,7 @@ TEST(ScopeReflectionTest, IsBuiltin) { EXPECT_TRUE(Cpp::IsBuiltin(C.getTypeDeclType(CTSD).getAsOpaquePtr())); } -TEST(ScopeReflectionTest, IsTemplate) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsTemplate) { std::vector Decls; std::string code = R"(template class A{}; @@ -225,7 +271,7 @@ TEST(ScopeReflectionTest, IsTemplate) { EXPECT_FALSE(Cpp::IsTemplate(Decls[3])); } -TEST(ScopeReflectionTest, IsTemplateSpecialization) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsTemplateSpecialization) { std::vector Decls; std::string code = R"( template @@ -241,7 +287,7 @@ TEST(ScopeReflectionTest, IsTemplateSpecialization) { Cpp::GetScopeFromType(Cpp::GetVariableType(Decls[1])))); } -TEST(ScopeReflectionTest, IsTypedefed) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsTypedefed) { std::vector Decls; std::string code = R"( typedef int I; @@ -255,7 +301,7 @@ TEST(ScopeReflectionTest, IsTypedefed) { EXPECT_FALSE(Cpp::IsTypedefed(Decls[2])); } -TEST(ScopeReflectionTest, IsAbstract) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsAbstract) { std::vector Decls; std::string code = R"( class A {}; @@ -275,7 +321,7 @@ TEST(ScopeReflectionTest, IsAbstract) { EXPECT_FALSE(Cpp::IsAbstract(Decls[2])); } -TEST(ScopeReflectionTest, IsVariable) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsVariable) { std::vector Decls; std::string code = R"( int i; @@ -299,7 +345,7 @@ TEST(ScopeReflectionTest, IsVariable) { EXPECT_TRUE(Cpp::IsVariable(SubDecls[3])); } -TEST(ScopeReflectionTest, GetName) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetName) { std::vector Decls; std::string code = R"(namespace N {} class C{}; int I; struct S; enum E : int; union U{}; class Size4{int i;}; @@ -317,7 +363,7 @@ TEST(ScopeReflectionTest, GetName) { EXPECT_EQ(Cpp::GetName(nullptr), ""); } -TEST(ScopeReflectionTest, GetCompleteName) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetCompleteName) { std::vector Decls; std::string code = R"(namespace N {} class C{}; @@ -363,7 +409,7 @@ TEST(ScopeReflectionTest, GetCompleteName) { EXPECT_EQ(Cpp::GetCompleteName(fn), "fn"); } -TEST(ScopeReflectionTest, GetQualifiedName) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetQualifiedName) { std::vector Decls; std::string code = R"(namespace N { class C { @@ -383,7 +429,7 @@ TEST(ScopeReflectionTest, GetQualifiedName) { EXPECT_EQ(Cpp::GetQualifiedName(Decls[4]), "N::C::E"); } -TEST(ScopeReflectionTest, GetQualifiedCompleteName) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetQualifiedCompleteName) { std::vector Decls; std::string code = R"(namespace N { class C { @@ -408,7 +454,7 @@ TEST(ScopeReflectionTest, GetQualifiedCompleteName) { EXPECT_EQ(Cpp::GetQualifiedCompleteName(Decls[6]), "N::C::E"); } -TEST(ScopeReflectionTest, GetUsingNamespaces) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetUsingNamespaces) { std::vector Decls, Decls1; std::string code = R"( namespace abc { @@ -441,12 +487,12 @@ TEST(ScopeReflectionTest, GetUsingNamespaces) { EXPECT_EQ(usingNamespaces1.size(), 0); } -TEST(ScopeReflectionTest, GetGlobalScope) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetGlobalScope) { EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetGlobalScope()), ""); EXPECT_EQ(Cpp::GetName(Cpp::GetGlobalScope()), ""); } -TEST(ScopeReflectionTest, GetUnderlyingScope) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetUnderlyingScope) { std::vector Decls; std::string code = R"( namespace N { @@ -465,7 +511,7 @@ TEST(ScopeReflectionTest, GetUnderlyingScope) { EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetUnderlyingScope(nullptr)), ""); } -TEST(ScopeReflectionTest, GetScope) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetScope) { std::string code = R"(namespace N { class C { int i; @@ -476,7 +522,7 @@ TEST(ScopeReflectionTest, GetScope) { typedef N::C T; )"; - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); Interp->declare(code); Cpp::TCppScope_t tu = Cpp::GetScope("", 0); Cpp::TCppScope_t ns_N = Cpp::GetScope("N", 0); @@ -491,7 +537,7 @@ TEST(ScopeReflectionTest, GetScope) { EXPECT_EQ(Cpp::GetQualifiedName(non_existent), ""); } -TEST(ScopeReflectionTest, GetScopefromCompleteName) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetScopefromCompleteName) { std::string code = R"(namespace N1 { namespace N2 { class C { @@ -501,7 +547,7 @@ TEST(ScopeReflectionTest, GetScopefromCompleteName) { } )"; - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); Interp->declare(code); EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromCompleteName("N1")), "N1"); @@ -510,7 +556,7 @@ TEST(ScopeReflectionTest, GetScopefromCompleteName) { EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromCompleteName("N1::N2::C::S")), "N1::N2::C::S"); } -TEST(ScopeReflectionTest, GetNamed) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetNamed) { #if CLANG_VERSION_MAJOR == 18 && defined(CPPINTEROP_USE_CLING) && \ defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) GTEST_SKIP() << "Test fails with Cling on Windows on ARM"; @@ -528,7 +574,7 @@ TEST(ScopeReflectionTest, GetNamed) { std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Interp->declare(code); Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1", nullptr); @@ -555,7 +601,7 @@ TEST(ScopeReflectionTest, GetNamed) { EXPECT_EQ(Cpp::GetQualifiedName(std_string_npos_var), "std::basic_string::npos"); } -TEST(ScopeReflectionTest, GetParentScope) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetParentScope) { std::string code = R"(namespace N1 { namespace N2 { class C { @@ -567,7 +613,7 @@ TEST(ScopeReflectionTest, GetParentScope) { } )"; - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); Interp->declare(code); Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1"); @@ -587,7 +633,7 @@ TEST(ScopeReflectionTest, GetParentScope) { EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetParentScope(en_B)), "N1::N2::C::E"); } -TEST(ScopeReflectionTest, GetScopeFromType) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetScopeFromType) { std::vector Decls; std::string code = R"( namespace N { @@ -631,7 +677,7 @@ TEST(ScopeReflectionTest, GetScopeFromType) { "N::C"); } -TEST(ScopeReflectionTest, GetNumBases) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetNumBases) { std::vector Decls; std::string code = R"( class A {}; @@ -662,7 +708,7 @@ TEST(ScopeReflectionTest, GetNumBases) { EXPECT_EQ(Cpp::GetNumBases(Cpp::GetUnderlyingScope(Decls[7])), 1); } -TEST(ScopeReflectionTest, GetBaseClass) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetBaseClass) { std::vector Decls; std::string code = R"( class A {}; @@ -711,7 +757,7 @@ TEST(ScopeReflectionTest, GetBaseClass) { EXPECT_EQ(Cpp::GetCompleteName(A_class), "A"); } -TEST(ScopeReflectionTest, IsSubclass) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIsSubclass) { std::vector Decls; std::string code = R"( class A {}; @@ -753,7 +799,7 @@ TEST(ScopeReflectionTest, IsSubclass) { EXPECT_FALSE(Cpp::IsSubclass(Decls[4], nullptr)); } -TEST(ScopeReflectionTest, GetBaseClassOffset) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetBaseClassOffset) { std::vector Decls; #define Stringify(s) Stringifyx(s) #define Stringifyx(...) #__VA_ARGS__ @@ -790,7 +836,7 @@ CODE; EXPECT_EQ(Cpp::GetBaseClassOffset(Decls[6], Decls[0]), (char *)(A*)g - (char *)g); } -TEST(ScopeReflectionTest, GetAllCppNames) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetAllCppNames) { std::vector Decls; std::string code = R"( class A { int a; }; @@ -832,7 +878,7 @@ TEST(ScopeReflectionTest, GetAllCppNames) { test_get_all_cpp_names(Decls[5], {}); } -TEST(ScopeReflectionTest, InstantiateNNTPClassTemplate) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestInstantiateNNTPClassTemplate) { std::vector Decls; std::string code = R"( template @@ -865,7 +911,7 @@ TEST(ScopeReflectionTest, InstantiateNNTPClassTemplate) { clang_Interpreter_dispose(I); } -TEST(ScopeReflectionTest, InstantiateVarTemplate) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestInstantiateVarTemplate) { std::vector Decls; std::string code = R"( template constexpr T pi = T(3.1415926535897932385L); @@ -889,7 +935,7 @@ template constexpr T pi = T(3.1415926535897932385L); EXPECT_TRUE(TA1.getAsType()->isIntegerType()); } -TEST(ScopeReflectionTest, InstantiateFunctionTemplate) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestInstantiateFunctionTemplate) { std::vector Decls; std::string code = R"( template T TrivialFnTemplate() { return T(); } @@ -909,7 +955,7 @@ template T TrivialFnTemplate() { return T(); } EXPECT_TRUE(TA1.getAsType()->isIntegerType()); } -TEST(ScopeReflectionTest, InstantiateTemplateFunctionFromString) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestInstantiateTemplateFunctionFromString) { #if CLANG_VERSION_MAJOR == 18 && defined(CPPINTEROP_USE_CLING) && \ defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) GTEST_SKIP() << "Test fails with Cling on Windows on ARM"; @@ -917,7 +963,7 @@ TEST(ScopeReflectionTest, InstantiateTemplateFunctionFromString) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); std::string code = R"(#include )"; Interp->process(code); const char* str = "std::make_unique"; @@ -925,7 +971,7 @@ TEST(ScopeReflectionTest, InstantiateTemplateFunctionFromString) { EXPECT_TRUE(Instance1); } -TEST(ScopeReflectionTest, InstantiateTemplate) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestInstantiateTemplate) { std::vector Decls; std::string code = R"( template @@ -1016,7 +1062,7 @@ TEST(ScopeReflectionTest, InstantiateTemplate) { EXPECT_TRUE(TA4_1.getAsIntegral() == 3); } -TEST(ScopeReflectionTest, GetClassTemplateInstantiationArgs) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetClassTemplateInstantiationArgs) { std::vector Decls; std::string code = R"( template struct __Cppyy_AppendTypesSlow {}; @@ -1054,7 +1100,7 @@ TEST(ScopeReflectionTest, GetClassTemplateInstantiationArgs) { } -TEST(ScopeReflectionTest, IncludeVector) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestIncludeVector) { #if CLANG_VERSION_MAJOR == 18 && defined(CPPINTEROP_USE_CLING) && \ defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) GTEST_SKIP() << "Test fails with Cling on Windows on ARM"; @@ -1066,15 +1112,15 @@ TEST(ScopeReflectionTest, IncludeVector) { #include )"; std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Interp->declare(code); } -TEST(ScopeReflectionTest, GetOperator) { +TYPED_TEST(CppInterOpTest, ScopeReflectionTestGetOperator) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); std::string code = R"( class MyClass { diff --git a/unittests/CppInterOp/TypeReflectionTest.cpp b/unittests/CppInterOp/TypeReflectionTest.cpp index 215d30f10..2da308f2a 100644 --- a/unittests/CppInterOp/TypeReflectionTest.cpp +++ b/unittests/CppInterOp/TypeReflectionTest.cpp @@ -17,7 +17,7 @@ using namespace TestUtils; using namespace llvm; using namespace clang; -TEST(TypeReflectionTest, GetTypeAsString) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestGetTypeAsString) { std::vector Decls; std::string code = R"( namespace N { @@ -57,7 +57,7 @@ TEST(TypeReflectionTest, GetTypeAsString) { EXPECT_EQ(Cpp::GetTypeAsString(QT7.getAsOpaquePtr()), "char[4]"); } -TEST(TypeReflectionTest, GetSizeOfType) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestGetSizeOfType) { std::vector Decls; std::string code = R"( struct S { @@ -85,7 +85,7 @@ TEST(TypeReflectionTest, GetSizeOfType) { sizeof(intptr_t)); } -TEST(TypeReflectionTest, GetCanonicalType) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestGetCanonicalType) { std::vector Decls; std::string code = R"( typedef int I; @@ -108,8 +108,8 @@ TEST(TypeReflectionTest, GetCanonicalType) { EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetCanonicalType(D4)), "NULL TYPE"); } -TEST(TypeReflectionTest, GetType) { - Cpp::CreateInterpreter(); +TYPED_TEST(CppInterOpTest, TypeReflectionTestGetType) { + TestFixture::CreateInterpreter(); std::string code = R"( class A {}; @@ -133,7 +133,7 @@ TEST(TypeReflectionTest, GetType) { EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("struct")),"NULL TYPE"); } -TEST(TypeReflectionTest, IsRecordType) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestIsRecordType) { std::vector Decls; std::string code = R"( @@ -200,7 +200,7 @@ TEST(TypeReflectionTest, IsRecordType) { EXPECT_FALSE(is_var_of_record_ty(Decls[24])); } -TEST(TypeReflectionTest, GetUnderlyingType) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestGetUnderlyingType) { std::vector Decls; std::string code = R"( @@ -278,7 +278,7 @@ TEST(TypeReflectionTest, GetUnderlyingType) { EXPECT_EQ(get_underly_var_type_as_str(Decls[30]), "E"); } -TEST(TypeReflectionTest, IsUnderlyingTypeRecordType) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestIsUnderlyingTypeRecordType) { std::vector Decls; std::string code = R"( @@ -345,8 +345,8 @@ TEST(TypeReflectionTest, IsUnderlyingTypeRecordType) { EXPECT_TRUE(is_var_of_underly_record_ty(Decls[24])); } -TEST(TypeReflectionTest, GetComplexType) { - Cpp::CreateInterpreter(); +TYPED_TEST(CppInterOpTest, TypeReflectionTestGetComplexType) { + TestFixture::CreateInterpreter(); auto get_complex_type_as_string = [&](const std::string &element_type) { auto ElementQT = Cpp::GetType(element_type); @@ -379,7 +379,7 @@ TEST(TypeReflectionTest, GetComplexType) { clang_Interpreter_dispose(I); } -TEST(TypeReflectionTest, GetTypeFromScope) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestGetTypeFromScope) { std::vector Decls; std::string code = R"( @@ -396,7 +396,7 @@ TEST(TypeReflectionTest, GetTypeFromScope) { EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetTypeFromScope(nullptr)), "NULL TYPE"); } -TEST(TypeReflectionTest, IsTypeDerivedFrom) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestIsTypeDerivedFrom) { std::vector Decls; std::string code = R"( @@ -433,7 +433,7 @@ TEST(TypeReflectionTest, IsTypeDerivedFrom) { EXPECT_FALSE(Cpp::IsTypeDerivedFrom(type_A, type_E)); } -TEST(TypeReflectionTest, GetDimensions) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestGetDimensions) { std::vector Decls, SubDecls; std::string code = R"( @@ -528,7 +528,7 @@ TEST(TypeReflectionTest, GetDimensions) { } } -TEST(TypeReflectionTest, IsPODType) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestIsPODType) { std::vector Decls; std::string code = R"( @@ -550,7 +550,7 @@ TEST(TypeReflectionTest, IsPODType) { EXPECT_FALSE(Cpp::IsPODType(0)); } -TEST(TypeReflectionTest, IsSmartPtrType) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestIsSmartPtrType) { #if CLANG_VERSION_MAJOR == 18 && defined(CPPINTEROP_USE_CLING) && \ defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) GTEST_SKIP() << "Test fails with Cling on Windows on ARM"; @@ -559,7 +559,7 @@ TEST(TypeReflectionTest, IsSmartPtrType) { GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Interp->declare(R"( #include @@ -596,9 +596,9 @@ TEST(TypeReflectionTest, IsSmartPtrType) { EXPECT_FALSE(Cpp::IsSmartPtrType(get_type_from_varname("object"))); } -TEST(TypeReflectionTest, IsFunctionPointerType) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestIsFunctionPointerType) { std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Interp->declare(R"( typedef int (*int_func)(int, int); @@ -613,7 +613,7 @@ TEST(TypeReflectionTest, IsFunctionPointerType) { Cpp::IsFunctionPointerType(Cpp::GetVariableType(Cpp::GetNamed("i")))); } -TEST(TypeReflectionTest, OperatorSpelling) { +TYPED_TEST(CppInterOpTest, TypeReflectionTestOperatorSpelling) { EXPECT_EQ(Cpp::GetSpellingFromOperator(Cpp::OP_Less), "<"); EXPECT_EQ(Cpp::GetSpellingFromOperator(Cpp::OP_Plus), "+"); EXPECT_EQ(Cpp::GetOperatorFromSpelling("->"), Cpp::OP_Arrow); @@ -621,8 +621,8 @@ TEST(TypeReflectionTest, OperatorSpelling) { EXPECT_EQ(Cpp::GetOperatorFromSpelling("invalid"), Cpp::OP_None); } -TEST(TypeReflectionTest, TypeQualifiers) { - Cpp::CreateInterpreter(); +TYPED_TEST(CppInterOpTest, TypeReflectionTestTypeQualifiers) { + TestFixture::CreateInterpreter(); Cpp::Declare(R"( int *a; int *__restrict__ b; diff --git a/unittests/CppInterOp/Utils.cpp b/unittests/CppInterOp/Utils.cpp index 623e9fd12..39b9a166a 100644 --- a/unittests/CppInterOp/Utils.cpp +++ b/unittests/CppInterOp/Utils.cpp @@ -6,6 +6,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/Basic/Version.h" #include "clang/Frontend/CompilerInstance.h" +#include "clang/Interpreter/PartialTranslationUnit.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Sema.h" @@ -18,6 +19,18 @@ using namespace clang; using namespace llvm; +namespace TestUtils { +TestConfig current_config; +std::vector GetInterpreterArgs( + const std::vector& base_args) { + auto args = base_args; + if (current_config.use_oop_jit) { + args.push_back("--use-oop-jit"); + } + return args; +} +} + void TestUtils::GetAllTopLevelDecls( const std::string& code, std::vector& Decls, bool filter_implicitGenerated /* = false */, diff --git a/unittests/CppInterOp/Utils.h b/unittests/CppInterOp/Utils.h index 3e5bd12a8..cb15e0ec4 100644 --- a/unittests/CppInterOp/Utils.h +++ b/unittests/CppInterOp/Utils.h @@ -5,21 +5,42 @@ #include "clang-c/CXCppInterOp.h" #include "clang-c/CXString.h" +#include "CppInterOp/CppInterOp.h" #include "llvm/Support/Valgrind.h" #include #include +#include #include +#include "gtest/gtest.h" using namespace clang; using namespace llvm; namespace clang { - class Decl; +class Decl; } #define Interp (static_cast(Cpp::GetInterpreter())) namespace TestUtils { + +struct TestConfig { + std::string name; + bool use_oop_jit; + + TestConfig(bool oop_jit, const std::string& n) + : name(std::move(n)), use_oop_jit(oop_jit) {} + + TestConfig() + : name("InProcessJIT"), use_oop_jit(false) {} +}; + +extern TestConfig current_config; + +// Helper to get interpreter args with current config +std::vector +GetInterpreterArgs(const std::vector& base_args = {}); + void GetAllTopLevelDecls(const std::string& code, std::vector& Decls, bool filter_implicitGenerated = false, @@ -36,4 +57,54 @@ CXScope make_scope(const clang::Decl* D, const CXInterpreter I); bool IsTargetX86(); +// Define type tags for each configuration +struct InProcessJITConfig { + static constexpr bool isOutOfProcess = false; + static constexpr const char* name = "InProcessJIT"; +}; + +#ifdef LLVM_BUILT_WITH_OOP_JIT +struct OutOfProcessJITConfig { + static constexpr bool isOutOfProcess = true; + static constexpr const char* name = "OutOfProcessJIT"; +}; +#endif + +// Define typed test fixture +template +class CppInterOpTest : public ::testing::Test { +protected: + void SetUp() override { + TestUtils::current_config = + TestUtils::TestConfig{Config::isOutOfProcess, Config::name}; + } + +public: + static TInterp_t CreateInterpreter(const std::vector& Args = {}, + const std::vector& GpuArgs = {}) { + auto mergedArgs = TestUtils::GetInterpreterArgs(Args); + return Cpp::CreateInterpreter(mergedArgs, GpuArgs); + } + + bool IsOutOfProcess() { + return Config::isOutOfProcess; + } +}; + +struct JITConfigNameGenerator { + template + static std::string GetName(int) { + return T::name; + } +}; + +#ifdef LLVM_BUILT_WITH_OOP_JIT +using CppInterOpTestTypes = ::testing::Types; +#else +using CppInterOpTestTypes = ::testing::Types; +#endif + +TYPED_TEST_SUITE(CppInterOpTest, CppInterOpTestTypes, JITConfigNameGenerator); + + #endif // CPPINTEROP_UNITTESTS_LIBCPPINTEROP_UTILS_H diff --git a/unittests/CppInterOp/VariableReflectionTest.cpp b/unittests/CppInterOp/VariableReflectionTest.cpp index e6bde0e8d..15bfef163 100644 --- a/unittests/CppInterOp/VariableReflectionTest.cpp +++ b/unittests/CppInterOp/VariableReflectionTest.cpp @@ -16,7 +16,7 @@ using namespace TestUtils; using namespace llvm; using namespace clang; -TEST(VariableReflectionTest, GetDatamembers) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestGetDatamembers) { std::vector Decls; std::string code = R"( class C { @@ -113,7 +113,7 @@ TEST(VariableReflectionTest, GetDatamembers) { CODE -TEST(VariableReflectionTest, DatamembersWithAnonymousStructOrUnion) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestDatamembersWithAnonymousStructOrUnion) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -162,7 +162,7 @@ TEST(VariableReflectionTest, DatamembersWithAnonymousStructOrUnion) { #endif } -TEST(VariableReflectionTest, GetTypeAsString) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestGetTypeAsString) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -180,7 +180,7 @@ TEST(VariableReflectionTest, GetTypeAsString) { } )"; - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); EXPECT_EQ(Cpp::Declare(code.c_str()), 0); Cpp::TCppScope_t wrapper = @@ -195,7 +195,7 @@ TEST(VariableReflectionTest, GetTypeAsString) { "my_namespace::Container"); } -TEST(VariableReflectionTest, LookupDatamember) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestLookupDatamember) { std::vector Decls; std::string code = R"( class C { @@ -219,7 +219,7 @@ TEST(VariableReflectionTest, LookupDatamember) { EXPECT_EQ(Cpp::GetQualifiedName(Cpp::LookupDatamember("k", Decls[0])), ""); } -TEST(VariableReflectionTest, GetVariableType) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestGetVariableType) { std::vector Decls; std::string code = R"( class C {}; @@ -268,7 +268,7 @@ TEST(VariableReflectionTest, GetVariableType) { CODE -TEST(VariableReflectionTest, GetVariableOffset) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestGetVariableOffset) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -379,7 +379,7 @@ TEST(VariableReflectionTest, GetVariableOffset) { CODE -TEST(VariableReflectionTest, VariableOffsetsWithInheritance) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestVariableOffsetsWithInheritance) { #if CLANG_VERSION_MAJOR == 18 && defined(CPPINTEROP_USE_CLING) && \ defined(_WIN32) && (defined(_M_ARM) || defined(_M_ARM64)) GTEST_SKIP() << "Test fails with Cling on Windows on ARM"; @@ -388,7 +388,7 @@ TEST(VariableReflectionTest, VariableOffsetsWithInheritance) { GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Cpp::Declare("#include"); @@ -435,7 +435,7 @@ TEST(VariableReflectionTest, VariableOffsetsWithInheritance) { ((intptr_t)&(my_k.s)) - ((intptr_t)&(my_k))); } -TEST(VariableReflectionTest, IsPublicVariable) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestIsPublicVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -458,7 +458,7 @@ TEST(VariableReflectionTest, IsPublicVariable) { EXPECT_FALSE(Cpp::IsPublicVariable(SubDecls[7])); } -TEST(VariableReflectionTest, IsProtectedVariable) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestIsProtectedVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -479,7 +479,7 @@ TEST(VariableReflectionTest, IsProtectedVariable) { EXPECT_TRUE(Cpp::IsProtectedVariable(SubDecls[6])); } -TEST(VariableReflectionTest, IsPrivateVariable) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestIsPrivateVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -500,7 +500,7 @@ TEST(VariableReflectionTest, IsPrivateVariable) { EXPECT_FALSE(Cpp::IsPrivateVariable(SubDecls[6])); } -TEST(VariableReflectionTest, IsStaticVariable) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestIsStaticVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -516,7 +516,7 @@ TEST(VariableReflectionTest, IsStaticVariable) { EXPECT_TRUE(Cpp::IsStaticVariable(SubDecls[2])); } -TEST(VariableReflectionTest, IsConstVariable) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestIsConstVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -533,7 +533,7 @@ TEST(VariableReflectionTest, IsConstVariable) { EXPECT_TRUE(Cpp::IsConstVariable(SubDecls[2])); } -TEST(VariableReflectionTest, DISABLED_GetArrayDimensions) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestDISABLED_GetArrayDimensions) { std::vector Decls; std::string code = R"( int a; @@ -557,7 +557,7 @@ TEST(VariableReflectionTest, DISABLED_GetArrayDimensions) { // EXPECT_TRUE(is_vec_eq(Cpp::GetArrayDimensions(Decls[2]), {1,2})); } -TEST(VariableReflectionTest, StaticConstExprDatamember) { +TYPED_TEST(CppInterOpTest, VariableReflectionTestStaticConstExprDatamember) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -565,7 +565,7 @@ TEST(VariableReflectionTest, StaticConstExprDatamember) { GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); Cpp::Declare(R"( class MyClass { @@ -636,8 +636,8 @@ TEST(VariableReflectionTest, StaticConstExprDatamember) { EXPECT_EQ(2, *(size_t*)offset); } -TEST(VariableReflectionTest, GetEnumConstantDatamembers) { - Cpp::CreateInterpreter(); +TYPED_TEST(CppInterOpTest, VariableReflectionTestGetEnumConstantDatamembers) { + TestFixture::CreateInterpreter(); Cpp::Declare(R"( class MyEnumClass { @@ -660,8 +660,8 @@ TEST(VariableReflectionTest, GetEnumConstantDatamembers) { EXPECT_EQ(datamembers2.size(), 6); } -TEST(VariableReflectionTest, Is_Get_Pointer) { - Cpp::CreateInterpreter(); +TYPED_TEST(CppInterOpTest, VariableReflectionTestIs_Get_Pointer) { + TestFixture::CreateInterpreter(); std::vector Decls; std::string code = R"( class A {}; @@ -692,8 +692,8 @@ TEST(VariableReflectionTest, Is_Get_Pointer) { EXPECT_FALSE(Cpp::GetPointeeType(Cpp::GetVariableType(Decls[5]))); } -TEST(VariableReflectionTest, Is_Get_Reference) { - Cpp::CreateInterpreter(); +TYPED_TEST(CppInterOpTest, VariableReflectionTestIs_Get_Reference) { + TestFixture::CreateInterpreter(); std::vector Decls; std::string code = R"( class A {}; @@ -730,8 +730,8 @@ TEST(VariableReflectionTest, Is_Get_Reference) { Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1]), true))); } -TEST(VariableReflectionTest, GetPointerType) { - Cpp::CreateInterpreter(); +TYPED_TEST(CppInterOpTest, VariableReflectionTestGetPointerType) { + TestFixture::CreateInterpreter(); std::vector Decls; std::string code = R"( class A {};