From e9a85cd3c994269a8ddacc38e3cbe9793458b43b Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 23 Dec 2025 13:07:55 +0100 Subject: [PATCH 1/2] [interop] Upgrade CppInterOp to v1.8.0 --- interpreter/CppInterOp/CMakeLists.txt | 30 +- .../Emscripten-build-instructions.md | 83 +- interpreter/CppInterOp/README.md | 60 +- interpreter/CppInterOp/VERSION | 2 +- .../CppInterOp/cmake/modules/GoogleTest.cmake | 9 +- interpreter/CppInterOp/cppinterop-version.tag | 2 +- .../docs/DevelopersDocumentation.rst | 45 +- .../docs/Emscripten-build-instructions.rst | 83 +- .../CppInterOp/docs/InstallationAndUsage.rst | 45 +- interpreter/CppInterOp/docs/ReleaseNotes.md | 10 +- .../CppInterOp/docs/UsingCppInterOp.rst | 38 + interpreter/CppInterOp/environment-wasm.yml | 8 +- .../CppInterOp/etc/clang20-valgrind.supp | 18 + .../include/CppInterOp/CppInterOp.h | 42 +- .../CppInterOp/include/CppInterOp/Dispatch.h | 294 ++++++ .../CppInterOp/include/clang-c/CXCppInterOp.h | 9 +- .../CppInterOp/lib/CppInterOp/CMakeLists.txt | 165 +-- .../lib/CppInterOp/CXCppInterOp.cpp | 31 +- .../CppInterOp/lib/CppInterOp/Compatibility.h | 107 +- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 359 ++++++- .../lib/CppInterOp/CppInterOpInterpreter.h | 143 ++- .../CppInterOp/lib/CppInterOp/Dispatch.cpp | 26 + .../lib/CppInterOp/DynamicLibraryManager.cpp | 13 +- .../lib/CppInterOp/DynamicLibraryManager.h | 4 +- .../DynamicLibraryManagerSymbol.cpp | 27 +- .../CppInterOp/lib/CppInterOp/Paths.cpp | 9 +- interpreter/CppInterOp/lib/CppInterOp/Paths.h | 11 +- .../CppInterOp/lib/CppInterOp/exports.ld | 4 +- .../llvm/clang20-1-out-of-process.patch | 966 ++++++++++++++++++ ...1-1-shift-temporary-files-to-tmp-dir.patch | 15 + ...-clang21-2-enable_exception_handling.patch | 100 ++ ...ebassembly_target_machine_reordering.patch | 14 + .../CppInterOp/unittests/CMakeLists.txt | 68 +- .../unittests/CppInterOp/CMakeLists.txt | 71 +- .../unittests/CppInterOp/CUDATest.cpp | 10 +- .../CppInterOp/CommentReflectionTest.cpp | 84 ++ .../unittests/CppInterOp/DispatchTest.cpp | 71 ++ .../CppInterOp/DynamicLibraryManagerTest.cpp | 13 +- .../CppInterOp/EnumReflectionTest.cpp | 64 +- .../CppInterOp/FunctionReflectionTest.cpp | 641 ++++++++---- .../unittests/CppInterOp/InterpreterTest.cpp | 141 +-- .../unittests/CppInterOp/JitTest.cpp | 43 +- .../CppInterOp/ScopeReflectionTest.cpp | 219 ++-- .../CppInterOp/TestSharedLib/CMakeLists.txt | 9 +- .../CppInterOp/TypeReflectionTest.cpp | 74 +- .../CppInterOp/unittests/CppInterOp/Utils.cpp | 51 +- .../CppInterOp/unittests/CppInterOp/Utils.h | 95 +- .../CppInterOp/VariableReflectionTest.cpp | 170 +-- 48 files changed, 3761 insertions(+), 835 deletions(-) create mode 100644 interpreter/CppInterOp/etc/clang20-valgrind.supp create mode 100644 interpreter/CppInterOp/include/CppInterOp/Dispatch.h mode change 100644 => 100755 interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp create mode 100644 interpreter/CppInterOp/lib/CppInterOp/Dispatch.cpp create mode 100644 interpreter/CppInterOp/patches/llvm/clang20-1-out-of-process.patch create mode 100644 interpreter/CppInterOp/patches/llvm/emscripten-clang21-1-shift-temporary-files-to-tmp-dir.patch create mode 100644 interpreter/CppInterOp/patches/llvm/emscripten-clang21-2-enable_exception_handling.patch create mode 100644 interpreter/CppInterOp/patches/llvm/emscripten-clang21-3-webassembly_target_machine_reordering.patch create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/CommentReflectionTest.cpp create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/DispatchTest.cpp diff --git a/interpreter/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/CMakeLists.txt index 1a167fe1de3e8..8a6c9eb79692a 100644 --- a/interpreter/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/CMakeLists.txt @@ -12,6 +12,9 @@ set(CMAKE_CXX_EXTENSIONS NO) option(CPPINTEROP_USE_CLING "Use Cling as backend" OFF) option(CPPINTEROP_USE_REPL "Use clang-repl as backend" ON) option(CPPINTEROP_ENABLE_TESTING "Enable the CppInterOp testing infrastructure." ON) +if(EMSCRIPTEN) + set(CPPINTEROP_EXTRA_WASM_FLAGS "-sSUPPORT_LONGJMP=wasm -fwasm-exceptions" CACHE STRING "Extra flags for wasm") +endif() if (CPPINTEROP_USE_CLING AND CPPINTEROP_USE_REPL) message(FATAL_ERROR "We can only use Cling (${CPPINTEROP_USE_CLING}=On) or Repl (CPPINTEROP_USE_REPL=On), but not both of them.") @@ -68,14 +71,14 @@ include(GNUInstallDirs) ## Define supported version of clang and llvm set(CLANG_MIN_SUPPORTED 18.0) - set(CLANG_MAX_SUPPORTED "20.1.x") - set(CLANG_VERSION_UPPER_BOUND 21.0.0) + set(CLANG_MAX_SUPPORTED "21.1.x") + set(CLANG_VERSION_UPPER_BOUND 22.0.0) set(LLD_MIN_SUPPORTED 18.0) - set(LLD_MAX_SUPPORTED "20.1.x") - set(LLD_VERSION_UPPER_BOUND 21.0.0) + set(LLD_MAX_SUPPORTED "21.1.x") + set(LLD_VERSION_UPPER_BOUND 22.0.0) set(LLVM_MIN_SUPPORTED 18.0) - set(LLVM_MAX_SUPPORTED "20.1.x") - set(LLVM_VERSION_UPPER_BOUND 21.0.0) + set(LLVM_MAX_SUPPORTED "21.1.x") + set(LLVM_VERSION_UPPER_BOUND 22.0.0) ## Set Cmake packages search order @@ -298,6 +301,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_BINARY_LIB_DIR "${LLVM_DIR}") +add_definitions(-DLLVM_BINARY_LIB_DIR="${LLVM_BINARY_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 MacOS 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 @@ -347,6 +362,9 @@ endif() if (LLVM_COMPILER_IS_GCC_COMPATIBLE) if (APPLE OR EMSCRIPTEN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-common -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings") + if (EMSCRIPTEN) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CPPINTEROP_EXTRA_WASM_FLAGS}") + endif () else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-common -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings") endif () diff --git a/interpreter/CppInterOp/Emscripten-build-instructions.md b/interpreter/CppInterOp/Emscripten-build-instructions.md index 0854fed2dc6b0..87b235eefdd4a 100644 --- a/interpreter/CppInterOp/Emscripten-build-instructions.md +++ b/interpreter/CppInterOp/Emscripten-build-instructions.md @@ -19,16 +19,16 @@ cd ./CppInterOp-wasm ``` To create a wasm build of CppInterOp we make use of the emsdk toolchain. This can be installed by executing (we only currently -support version 3.1.73) +support version 4.0.9) ```bash git clone https://github.com/emscripten-core/emsdk.git -./emsdk/emsdk install 3.1.73 +./emsdk/emsdk install 4.0.9 ``` and to activate the emsdk environment on Linux and osx execute (we are defining SYSROOT_PATH for use later) ```bash -./emsdk/emsdk activate 3.1.73 +./emsdk/emsdk activate 4.0.9 source ./emsdk/emsdk_env.sh export SYSROOT_PATH=$PWD/emsdk/upstream/emscripten/cache/sysroot ``` @@ -36,17 +36,17 @@ export SYSROOT_PATH=$PWD/emsdk/upstream/emscripten/cache/sysroot and on Windows execute in Powershell ```powershell -.\emsdk\emsdk activate 3.1.73 +.\emsdk\emsdk activate 4.0.9 .\emsdk\emsdk_env.ps1 $env:PWD_DIR= $PWD.Path $env:SYSROOT_PATH="$env:EMSDK/upstream/emscripten/cache/sysroot" ``` -Now clone the 20.x release of the LLVM project repository and CppInterOp (the building of the emscripten version of llvm can be +Now clone the 21.x release of the LLVM project repository and CppInterOp (the building of the emscripten version of llvm can be avoided by executing micromamba install llvm -c and setting the LLVM_BUILD_DIR/$env:LLVM_BUILD_DIR appropriately) ```bash -git clone --depth=1 --branch release/20.x https://github.com/llvm/llvm-project.git +git clone --depth=1 --branch release/21.x https://github.com/llvm/llvm-project.git git clone --depth=1 https://github.com/compiler-research/CppInterOp.git ``` @@ -55,16 +55,17 @@ executing ```bash cd ./llvm-project/ -git apply -v ../CppInterOp/patches/llvm/emscripten-clang20-*.patch +git apply -v ../CppInterOp/patches/llvm/emscripten-clang21-*.patch ``` On Windows execute the following ```powershell cd .\llvm-project\ -cp -r ..\patches\llvm\emscripten-clang20* -git apply -v emscripten-clang20-2-shift-temporary-files-to-tmp-dir.patch -git apply -v emscripten-clang20-3-enable_exception_handling.patch +cp -r ..\patches\llvm\emscripten-clang21* +git apply -v emscripten-clang21-1-shift-temporary-files-to-tmp-dir.patch +git apply -v emscripten-clang21-2-enable_exception_handling.patch +git apply -v emscripten-clang21-3-webassembly_target_machine_reordering.patch ``` We are now in a position to build an emscripten build of llvm by executing the following on Linux @@ -101,9 +102,7 @@ emcmake cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" \ -DLLVM_ENABLE_LTO=Full \ ../llvm -emmake make libclang -j $(nproc --all) -emmake make clangInterpreter clangStaticAnalyzerCore -j $(nproc --all) -emmake make lldWasm -j $(nproc --all) +EMCC_CFLAGS="-sSUPPORT_LONGJMP=wasm -fwasm-exceptions" emmake make libclang clangInterpreter clangStaticAnalyzerCore -j $(nproc --all) ``` or executing @@ -142,7 +141,9 @@ emcmake cmake -DCMAKE_BUILD_TYPE=Release ` -DCMAKE_CXX_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" ` -DLLVM_ENABLE_LTO=Full ` ..\llvm -emmake ninja libclang clangInterpreter clangStaticAnalyzerCore lldWasm +$env:EMCC_CFLAGS="-sSUPPORT_LONGJMP=wasm -fwasm-exceptions" +emmake ninja libclang clangInterpreter clangStaticAnalyzerCore +$env:EMCC_CFLAGS="" ``` on Windows. Once this finishes building we need to take note of where we built our llvm build. This can be done by executing the following on Linux and osx @@ -163,7 +164,7 @@ by executing (assumes you have micromamba installed and that your shell is initi ```bash cd ../../CppInterOp/ -micromamba create -f environment-wasm.yml --platform=emscripten-wasm32 +micromamba create -f environment-wasm.yml --platform=emscripten-wasm32 -c https://prefix.dev/emscripten-forge-4x -c https://prefix.dev/conda-forge micromamba activate CppInterOp-wasm ``` @@ -178,7 +179,7 @@ export CMAKE_SYSTEM_PREFIX_PATH=$PREFIX and ```powershell -$env:PREFIX="%CONDA_PREFIX%/envs/CppInterOp-wasm" +$env:PREFIX="$env:MAMBA_ROOT_PREFIX/envs/CppInterOp-wasm" $env:CMAKE_PREFIX_PATH=$env:PREFIX $env:CMAKE_SYSTEM_PREFIX_PATH=$env:PREFIX ``` @@ -337,13 +338,13 @@ emmake make -j $(nproc --all) install ## Xeus-cpp-lite Wasm Build Instructions A project which makes use of the wasm build of CppInterOp is xeus-cpp. xeus-cpp is a C++ Jupyter kernel. Assuming you are in -the CppInterOp build folder, you can build the wasm version of xeus-cpp by executing (replace LLVM_VERSION with the version +the CppInterOp build folder, you can build the wasm version of xeus-cpp on Linux/MacOS by executing (replace LLVM_VERSION with the version of llvm you are building against) ```bash cd ../.. git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git -export LLVM_VERSION=20 +export LLVM_VERSION=21 cd ./xeus-cpp mkdir build cd build @@ -356,20 +357,58 @@ emcmake cmake \ -DXEUS_CPP_RESOURCE_DIR="$LLVM_BUILD_DIR/lib/clang/$LLVM_VERSION" \ -DSYSROOT_PATH=$SYSROOT_PATH \ .. - emmake make -j $(nproc --all) install +EMCC_CFLAGS="-sSUPPORT_LONGJMP=wasm -fwasm-exceptions" emmake make -j $(nproc --all) install ``` -To build and test Jupyter Lite with this kernel locally you can execute the following +and on Windows by executing + +```powershell +cd ..\.. +git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git +$env:LLVM_VERSION=21 +cd .\xeus-cpp +mkdir build +cd build +emcmake cmake ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_PREFIX_PATH="$env:PREFIX" ` + -DCMAKE_INSTALL_PREFIX="$env:PREFIX" ` + -DXEUS_CPP_EMSCRIPTEN_WASM_BUILD=ON ` + -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON ` + -DXEUS_CPP_RESOURCE_DIR="$env:LLVM_BUILD_DIR/lib/clang/$env:LLVM_VERSION" ` + -DSYSROOT_PATH="$env:SYSROOT_PATH" ` + .. +$env:EMCC_CFLAGS="-sSUPPORT_LONGJMP=wasm -fwasm-exceptions" +emmake make -j $(nproc --all) install +$env:EMCC_CFLAGS="" +``` + +To build and test Jupyter Lite with this kernel locally on Linux/MacOS you can execute the following ```bash cd ../.. -micromamba create -n xeus-lite-host jupyterlite-core=0.6 jupyterlite-xeus jupyter_server jupyterlab notebook python-libarchive-c -c conda-forge +micromamba create -n xeus-lite-host jupyterlite-core jupyterlite-xeus jupyter_server jupyterlab notebook python-libarchive-c -c conda-forge micromamba activate xeus-lite-host jupyter lite serve --XeusAddon.prefix=$PREFIX \ --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb \ - --contents xeus-cpp/notebooks/smallpt.ipynb \ + --contents xeus-cpp/notebooks/tinyraytracer.ipynb \ --contents xeus-cpp/notebooks/images/marie.png \ --contents xeus-cpp/notebooks/audio/audio.wav \ --XeusAddon.mounts="$PREFIX/share/xeus-cpp/tagfiles:/share/xeus-cpp/tagfiles" \ --XeusAddon.mounts="$PREFIX/etc/xeus-cpp/tags.d:/etc/xeus-cpp/tags.d" ``` + +and on Windows execute + +```powershell +cd ..\.. +micromamba create -n xeus-lite-host jupyterlite-core jupyterlite-xeus jupyter_server jupyterlab notebook python-libarchive-c -c conda-forge +micromamba activate xeus-lite-host +jupyter lite serve --XeusAddon.prefix="$env:PREFIX" ` + --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb ` + --contents xeus-cpp/notebooks/tinyraytracer.ipynb ` + --contents xeus-cpp/notebooks/images/marie.png ` + --contents xeus-cpp/notebooks/audio/audio.wav ` + --XeusAddon.mounts="$env:PREFIX/share/xeus-cpp/tagfiles:/share/xeus-cpp/tagfiles" ` + --XeusAddon.mounts="$env:PREFIX/etc/xeus-cpp/tags.d:/etc/xeus-cpp/tags.d" +``` diff --git a/interpreter/CppInterOp/README.md b/interpreter/CppInterOp/README.md index e14a94d94eff2..8e93d600ff9dd 100644 --- a/interpreter/CppInterOp/README.md +++ b/interpreter/CppInterOp/README.md @@ -104,13 +104,20 @@ git clone --depth=1 https://github.com/compiler-research/cppyy-backend.git #### Setup Clang-REPL -Clone the 20.x release of the LLVM project repository. +Clone the 21.x release of the LLVM project repository. ```bash -git clone --depth=1 --branch release/20.x https://github.com/llvm/llvm-project.git +git clone --depth=1 --branch release/21.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 arm64 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 arm64 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 arm64 + +```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 @@ -163,10 +209,14 @@ Now CppInterOp can be built. This can be done by executing ```bash mkdir CppInterOp/build/ cd CppInterOp/build/ -cmake -DBUILD_SHARED_LIBS=ON -DCPPINTEROP_USE_CLING=ON -DCPPINTEROP_USE_REPL=Off -DCling_DIR=$LLVM_DIR/build/tools/cling -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. +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) ``` +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 @@ -406,10 +456,10 @@ git clone --depth=1 https://github.com/compiler-research/cppyy-backend.git #### Setup Clang-REPL -Clone the 20.x release of the LLVM project repository. +Clone the 21.x release of the LLVM project repository. ```bash -git clone --depth=1 --branch release/20.x https://github.com/llvm/llvm-project.git +git clone --depth=1 --branch release/21.x https://github.com/llvm/llvm-project.git cd llvm-project ``` diff --git a/interpreter/CppInterOp/VERSION b/interpreter/CppInterOp/VERSION index 9dd863f5f629b..4d12e404b1810 100644 --- a/interpreter/CppInterOp/VERSION +++ b/interpreter/CppInterOp/VERSION @@ -1 +1 @@ -1.8.0;dev +1.9.0;dev diff --git a/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake b/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake index e6753c30c9787..c2b7c2ef75915 100644 --- a/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake +++ b/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake @@ -20,6 +20,9 @@ endif() include(ExternalProject) if (EMSCRIPTEN) + # FIXME: -sSUPPORT_LONGJMP=wasm in the default option causes a warning in the Emscripten build of Googletest + # and as we treat warnings as errors in the ci, it causes the ci to fail. + string(REPLACE "-sSUPPORT_LONGJMP=wasm" "" GOOGLETEST_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") set(config_cmd emcmake${EMCC_SUFFIX} cmake) if(CMAKE_GENERATOR STREQUAL "Ninja") set(build_cmd emmake${EMCC_SUFFIX} ninja) @@ -34,8 +37,8 @@ endif() ExternalProject_Add( googletest GIT_REPOSITORY https://github.com/google/googletest.git - GIT_SHALLOW 1 - GIT_TAG v1.17.0 + GIT_SHALLOW FALSE + GIT_TAG fa8438ae6b70c57010177de47a9f13d7041a6328 UPDATE_COMMAND "" # # Force separate output paths for debug and release builds to allow easy # # identification of correct lib in subsequent TARGET_LINK_LIBRARIES commands @@ -49,7 +52,7 @@ ExternalProject_Add( -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} + -DCMAKE_CXX_FLAGS=${GOOGLETEST_CMAKE_CXX_FLAGS} -DCMAKE_AR=${CMAKE_AR} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} ${EXTRA_GTEST_OPTS} diff --git a/interpreter/CppInterOp/cppinterop-version.tag b/interpreter/CppInterOp/cppinterop-version.tag index bb4a3565c8603..f1e2afb3be837 100644 --- a/interpreter/CppInterOp/cppinterop-version.tag +++ b/interpreter/CppInterOp/cppinterop-version.tag @@ -1 +1 @@ -33a3e7e7629c0818bd92420568671325b016d6b2 \ No newline at end of file +c211948a266d7d42466a3ea5a3ebbf81548486e5 \ No newline at end of file diff --git a/interpreter/CppInterOp/docs/DevelopersDocumentation.rst b/interpreter/CppInterOp/docs/DevelopersDocumentation.rst index 0be22db9010f9..77e89238ad3c8 100644 --- a/interpreter/CppInterOp/docs/DevelopersDocumentation.rst +++ b/interpreter/CppInterOp/docs/DevelopersDocumentation.rst @@ -34,13 +34,22 @@ library Setup Clang-REPL ****************** -Clone the 20.x release of the LLVM project repository. +Clone the 21.x release of the LLVM project repository. .. code:: bash - git clone --depth=1 --branch release/20.x https://github.com/llvm/llvm-project.git + git clone --depth=1 --branch release/21.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 arm64 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/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst b/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst index 89e9be0d2c1bb..f7fbd054f5349 100644 --- a/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst +++ b/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst @@ -32,19 +32,19 @@ Now move into this directory using the following command To create a wasm build of CppInterOp we make use of the emsdk toolchain. This can be installed by executing (we only currently support version -3.1.73) +4.0.9) .. code:: bash git clone https://github.com/emscripten-core/emsdk.git - ./emsdk/emsdk install 3.1.73 + ./emsdk/emsdk install 4.0.9 and to activate the emsdk environment on Linux and osx execute (we are defining SYSROOT_PATH for use later) .. code:: bash - ./emsdk/emsdk activate 3.1.73 + ./emsdk/emsdk activate 4.0.9 source ./emsdk/emsdk_env.sh export SYSROOT_PATH=$PWD/emsdk/upstream/emscripten/cache/sysroot @@ -52,12 +52,12 @@ and on Windows execute in Powershell .. code:: powershell - .\emsdk\emsdk activate 3.1.73 + .\emsdk\emsdk activate 4.0.9 .\emsdk\emsdk_env.ps1 $env:PWD_DIR= $PWD.Path $env:SYSROOT_PATH="$env:EMSDK/upstream/emscripten/cache/sysroot" -Now clone the 20.x release of the LLVM project repository and CppInterOp +Now clone the 21.x release of the LLVM project repository and CppInterOp (the building of the emscripten version of llvm can be avoided by executing micromamba install llvm -c and setting the LLVM_BUILD_DIR/$env:LLVM_BUILD_DIR @@ -65,7 +65,7 @@ appropriately) .. code:: bash - git clone --depth=1 --branch release/20.x https://github.com/llvm/llvm-project.git + git clone --depth=1 --branch release/21.x https://github.com/llvm/llvm-project.git git clone --depth=1 https://github.com/compiler-research/CppInterOp.git Now move into the cloned llvm-project folder and apply the required patches. On Linux and osx this @@ -74,16 +74,17 @@ executing .. code:: bash cd ./llvm-project/ - git apply -v ../CppInterOp/patches/llvm/emscripten-clang20-*.patch + git apply -v ../CppInterOp/patches/llvm/emscripten-clang21-*.patch On Windows execute the following .. code:: powershell cd .\llvm-project\ - cp -r ..\patches\llvm\emscripten-clang20* - git apply -v emscripten-clang20-2-shift-temporary-files-to-tmp-dir.patch - git apply -v emscripten-clang20-3-enable_exception_handling.patch + cp -r ..\patches\llvm\emscripten-clang21* + git apply -v emscripten-clang21-1-shift-temporary-files-to-tmp-dir.patch + git apply -v emscripten-clang21-2-enable_exception_handling.patch + git apply -v emscripten-clang21-3-webassembly_target_machine_reordering.patch We are now in a position to build an emscripten build of llvm by executing the following on Linux and osx @@ -121,9 +122,7 @@ and osx -DCMAKE_CXX_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" \ -DLLVM_ENABLE_LTO=Full \ ../llvm - emmake make libclang -j $(nproc --all) - emmake make clangInterpreter clangStaticAnalyzerCore -j $(nproc --all) - emmake make lldWasm -j $(nproc --all) + EMCC_CFLAGS="-sSUPPORT_LONGJMP=wasm -fwasm-exceptions" emmake make libclang clangInterpreter clangStaticAnalyzerCore -j $(nproc --all) or executing @@ -162,7 +161,9 @@ or executing -DCMAKE_CXX_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" ` -DLLVM_ENABLE_LTO=Full ` ..\llvm - emmake ninja libclang clangInterpreter clangStaticAnalyzerCore lldWasm + $env:EMCC_CFLAGS="-sSUPPORT_LONGJMP=wasm -fwasm-exceptions" + emmake ninja libclang clangInterpreter clangStaticAnalyzerCore + $env:EMCC_CFLAGS="" on Windows. Once this finishes building we need to take note of where we built our llvm build. This can be done by executing the following on Linux and osx @@ -188,7 +189,7 @@ initialised for the micromamba install) .. code:: bash cd ../../CppInterOp/ - micromamba create -f environment-wasm.yml --platform=emscripten-wasm32 + micromamba create -f environment-wasm.yml --platform=emscripten-wasm32 -c https://prefix.dev/emscripten-forge-4x -c https://prefix.dev/conda-forge micromamba activate CppInterOp-wasm You will also want to set a few environment variables. On Linux and osx you define them as follows @@ -203,7 +204,7 @@ and .. code:: powershell - $env:PREFIX="%CONDA_PREFIX%/envs/CppInterOp-wasm" + $env:PREFIX="$env:MAMBA_ROOT_PREFIX/envs/CppInterOp-wasm" $env:CMAKE_PREFIX_PATH=$env:PREFIX $env:CMAKE_SYSTEM_PREFIX_PATH=$env:PREFIX @@ -358,14 +359,14 @@ Assuming it passes all test you can install by executing the following. A project which makes use of the wasm build of CppInterOp is xeus-cpp. xeus-cpp is a C++ Jupyter kernel. Assuming you are in the CppInterOp -build folder, you can build the wasm version of xeus-cpp by executing -(replace LLVM_VERSION with the version of llvm you are building against) +build folder, you can build the wasm version of xeus-cpp on Linux/MacOS +by executing (replace LLVM_VERSION with the version of llvm you are building against) .. code:: bash cd ../.. git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git - export LLVM_VERSION=20 + export LLVM_VERSION=21 cd ./xeus-cpp mkdir build cd build @@ -378,19 +379,57 @@ build folder, you can build the wasm version of xeus-cpp by executing -DXEUS_CPP_RESOURCE_DIR="$LLVM_BUILD_DIR/lib/clang/$LLVM_VERSION" \ -DSYSROOT_PATH=$SYSROOT_PATH \ .. + EMCC_CFLAGS="-sSUPPORT_LONGJMP=wasm -fwasm-exceptions" emmake make -j $(nproc --all) install + +and on Windows by executing + +.. code:: powershell + + cd ..\.. + git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git + $env:LLVM_VERSION=21 + cd .\xeus-cpp + mkdir build + cd build + emcmake cmake ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_PREFIX_PATH="$env:PREFIX" ` + -DCMAKE_INSTALL_PREFIX="$env:PREFIX" ` + -DXEUS_CPP_EMSCRIPTEN_WASM_BUILD=ON ` + -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON ` + -DXEUS_CPP_RESOURCE_DIR="$env:LLVM_BUILD_DIR/lib/clang/$env:LLVM_VERSION" ` + -DSYSROOT_PATH="$env:SYSROOT_PATH" ` + .. + $env:EMCC_CFLAGS="-sSUPPORT_LONGJMP=wasm -fwasm-exceptions" emmake make -j $(nproc --all) install + $env:EMCC_CFLAGS="" -To build and test Jupyter Lite with this kernel locally you can execute the following +To build and test Jupyter Lite with this kernel locally on Linux/MacOS you can execute the following .. code:: bash cd ../.. - micromamba create -n xeus-lite-host jupyterlite-core=0.6 jupyterlite-xeus jupyter_server jupyterlab notebook python-libarchive-c -c conda-forge + micromamba create -n xeus-lite-host jupyterlite-core jupyterlite-xeus jupyter_server jupyterlab notebook python-libarchive-c -c conda-forge micromamba activate xeus-lite-host jupyter lite serve --XeusAddon.prefix=$PREFIX \ --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb \ - --contents xeus-cpp/notebooks/smallpt.ipynb \ + --contents xeus-cpp/notebooks/tinyraytracer.ipynb \ --contents xeus-cpp/notebooks/images/marie.png \ --contents xeus-cpp/notebooks/audio/audio.wav \ --XeusAddon.mounts="$PREFIX/share/xeus-cpp/tagfiles:/share/xeus-cpp/tagfiles" \ --XeusAddon.mounts="$PREFIX/etc/xeus-cpp/tags.d:/etc/xeus-cpp/tags.d" + +and on Windows execute + +.. code:: powershell + + cd ..\.. + micromamba create -n xeus-lite-host jupyterlite-core jupyterlite-xeus jupyter_server jupyterlab notebook python-libarchive-c -c conda-forge + micromamba activate xeus-lite-host + jupyter lite serve --XeusAddon.prefix="$env:PREFIX" ` + --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb ` + --contents xeus-cpp/notebooks/tinyraytracer.ipynb ` + --contents xeus-cpp/notebooks/images/marie.png ` + --contents xeus-cpp/notebooks/audio/audio.wav ` + --XeusAddon.mounts="$env:PREFIX/share/xeus-cpp/tagfiles:/share/xeus-cpp/tagfiles" ` + --XeusAddon.mounts="$env:PREFIX/etc/xeus-cpp/tags.d:/etc/xeus-cpp/tags.d" diff --git a/interpreter/CppInterOp/docs/InstallationAndUsage.rst b/interpreter/CppInterOp/docs/InstallationAndUsage.rst index 3ec6470b44361..911281c25fcf1 100644 --- a/interpreter/CppInterOp/docs/InstallationAndUsage.rst +++ b/interpreter/CppInterOp/docs/InstallationAndUsage.rst @@ -34,13 +34,22 @@ library Setup Clang-REPL ****************** -Clone the 20.x release of the LLVM project repository. +Clone the 21.x release of the LLVM project repository. .. code:: bash - git clone --depth=1 --branch release/20.x https://github.com/llvm/llvm-project.git + git clone --depth=1 --branch release/21.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 arm64 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/interpreter/CppInterOp/docs/ReleaseNotes.md b/interpreter/CppInterOp/docs/ReleaseNotes.md index 811519deada86..334ee3057a1bb 100644 --- a/interpreter/CppInterOp/docs/ReleaseNotes.md +++ b/interpreter/CppInterOp/docs/ReleaseNotes.md @@ -1,7 +1,7 @@ # Introduction This document contains the release notes for the language interoperability -library CppInterOp, release 1.8.0. CppInterOp is built on top of +library CppInterOp, release 1.9.0. CppInterOp is built on top of [Clang](http://clang.llvm.org) and [LLVM](http://llvm.org%3E) compiler infrastructure. Here we describe the status of CppInterOp in some detail, including major improvements from the previous release and new feature work. @@ -16,7 +16,7 @@ interoperability on the fly. In such scenarios CppInterOp can be used to provide the necessary introspection information to the other side helping the language cross talk. -## What's New in CppInterOp 1.8.0? +## What's New in CppInterOp 1.9.0? Some of the major new features and improvements to CppInterOp are listed here. Generic improvements to CppInterOp as a whole or to its underlying @@ -25,7 +25,7 @@ infrastructure are described first. ## External Dependencies - CppInterOp now works with: - - llvm20 + - llvm21 ## Introspection @@ -48,7 +48,7 @@ infrastructure are described first. [XXX](https://github.com/compiler-research/CppInterOp/issues/XXX) ## Special Kudos @@ -61,6 +61,6 @@ FirstName LastName (#commits) A B (N) diff --git a/interpreter/CppInterOp/docs/UsingCppInterOp.rst b/interpreter/CppInterOp/docs/UsingCppInterOp.rst index 90722326d6000..feee8a0f71fff 100644 --- a/interpreter/CppInterOp/docs/UsingCppInterOp.rst +++ b/interpreter/CppInterOp/docs/UsingCppInterOp.rst @@ -24,6 +24,44 @@ classes and members that are being used. The interoperability layer helps us with the instantiation of templates, diagnostic interaction, creation of objects, and many more things. +CppInterOp API Dispatch Mechanism +============================== +The standard way of using CppInterOp as a library is to link against the `clangCppInterOp` target at compile time. However, +CppInterOp also supports an API dispatch mechanism that allows clients to load the shared library at runtime in `RTLD_LOCAL` mode. +This alternate approach is particularly useful in scenarios where CppInterOp is loaded dynamically alongside other tools/libraries/packages that also +rely on LLVM. With cppyy, importing tools like `numba` or `pygame` that come bundled with a statically linked copy of LLVM, +leads to symbol interposition and crashes. The dispatch mechanism solves this by solely relying on a C interface, which returns a pointer to the address of a requested function, which can be cast to the appropriate API type and +invoked as needed. An example can be found in `unittests/CppInterOp/DispatchTest.cpp`. For a user, this system is completely automated via the `Dispatch.h` header file, which maintains a symbol-address map for a fast look-up through a C symbol (`CppGetProcAddress`) which is fetched via `dlsym`. The provided macro system then handles all the declarations on the client end +with the inclusion of the header: + +```cpp +#include // instead of the standard +``` + +At this point, the expansions already provide the same API surface as the standard CppInterOp library, except that the declarations require to be added to a translation unit for the client application. +This can be done by adding the following preprocessor directive in one of the source files: + +```cpp +#define DISPATCH_API(name, type) CppAPIType::name Cpp::name = nullptr; +CPPINTEROP_API_TABLE +#undef DISPATCH_API +``` + +This creates the necessary function pointers in the `Cpp` namespace, which can then be used as normal CppInterOp API functions. +To load the API, invoke: + +```cpp +Cpp::LoadDispatchAPI("path/to/libclangCppInterOp.so"); +``` + +Now your application can use the CppInterOp API via the `Cpp::` namespace, with all functions being dispatched through the function pointers. +This is a one-to-one replacement for linking and using directly, with a few drawbacks: +- This system currently does not support overloaded public API +- The function pointers are not initialized until `LoadDispatchAPI` is called, which means that any attempt to use the API before loading will result in a null pointer dereference. +- You lose the ability to assume default arguments in API calls, which must now be provided explicitly since we rely on function pointers. + +The entire unittest suite has been configured to run in both standard and dispatch modes, ensuring that the mechanism is thoroughly tested and validated to support all public API. + Using LLVM as external library ============================== diff --git a/interpreter/CppInterOp/environment-wasm.yml b/interpreter/CppInterOp/environment-wasm.yml index a7ea101b2eb64..c09a876366d09 100644 --- a/interpreter/CppInterOp/environment-wasm.yml +++ b/interpreter/CppInterOp/environment-wasm.yml @@ -1,9 +1,11 @@ name: CppInterOp-wasm channels: - - https://repo.prefix.dev/emscripten-forge-dev + - https://prefix.dev/emscripten-forge-4x + - https://prefix.dev/conda-forge dependencies: - - emscripten-abi==3.1.73 - - nlohmann_json=3.12.0 + - emscripten-abi==4.0.9 + - nlohmann_json + - nlohmann_json-abi - xeus-lite - xeus - cpp-argparse diff --git a/interpreter/CppInterOp/etc/clang20-valgrind.supp b/interpreter/CppInterOp/etc/clang20-valgrind.supp new file mode 100644 index 0000000000000..9261befb2b6a9 --- /dev/null +++ b/interpreter/CppInterOp/etc/clang20-valgrind.supp @@ -0,0 +1,18 @@ +{ + Clang 20 Out of Process + Memcheck:Param + write(buf) + fun:__libc_write + fun:write + fun:_ZN4llvm3orc26FDSimpleRemoteEPCTransport11sendMessageENS0_21SimpleRemoteEPCOpcodeEmNS0_12ExecutorAddrENS_8ArrayRefIcEE + fun:_ZN4llvm3orc15SimpleRemoteEPC11sendMessageENS0_21SimpleRemoteEPCOpcodeEmNS0_12ExecutorAddrENS_8ArrayRefIcEE + fun:_ZN4llvm3orc15SimpleRemoteEPC16callWrapperAsyncENS0_12ExecutorAddrENS0_22ExecutorProcessControl18IncomingWFRHandlerENS_8ArrayRefIcEE + fun:_ZN4llvm3orc30EPCGenericJITLinkMemoryManager13InFlightAlloc8finalizeENS_15unique_functionIFvNS_8ExpectedINS_7jitlink20JITLinkMemoryManager14FinalizedAllocEEEEEE + fun:_ZN4llvm7jitlink13JITLinkerBase10linkPhase3ESt10unique_ptrIS1_St14default_deleteIS1_EENS_8ExpectedINS_8DenseMapINS_3orc15SymbolStringPtrENS8_17ExecutorSymbolDefENS_12DenseMapInfoIS9_vEENS_6detail12DenseMapPairIS9_SA_EEEEEE + fun:_ZZN4llvm7jitlink24createLookupContinuationIZNS0_13JITLinkerBase10linkPhase2ESt10unique_ptrIS2_St14default_deleteIS2_EENS_8ExpectedIS3_INS0_20JITLinkMemoryManager13InFlightAllocES4_IS9_EEEEEUlNS7_INS_8DenseMapINS_3orc15SymbolStringPtrENSE_17ExecutorSymbolDefENS_12DenseMapInfoISF_vEENS_6detail12DenseMapPairISF_SG_EEEEEEE_EES3_INS0_30JITLinkAsyncLookupContinuationES4_ISP_EET_EN4Impl3runESN_ + fun:_ZZN4llvm3orc21LinkGraphLinkingLayer10JITLinkCtx6lookupERKNS_8DenseMapINS0_15SymbolStringPtrENS_7jitlink17SymbolLookupFlagsENS_12DenseMapInfoIS4_vEENS_6detail12DenseMapPairIS4_S6_EEEESt10unique_ptrINS5_30JITLinkAsyncLookupContinuationESt14default_deleteISG_EEENUlNS_8ExpectedINS3_IS4_NS0_17ExecutorSymbolDefES8_NSA_IS4_SL_EEEEEEE0_clESO_ + fun:_ZN4llvm6detail18UniqueFunctionBaseIvJNS_8ExpectedINS_8DenseMapINS_3orc15SymbolStringPtrENS4_17ExecutorSymbolDefENS_12DenseMapInfoIS5_vEENS0_12DenseMapPairIS5_S6_EEEEEEEE8CallImplIZNS4_21LinkGraphLinkingLayer10JITLinkCtx6lookupERKNS3_IS5_NS_7jitlink17SymbolLookupFlagsES8_NS9_IS5_SI_EEEESt10unique_ptrINSH_30JITLinkAsyncLookupContinuationESt14default_deleteISO_EEEUlSC_E0_EEvPvRSC_ + fun:_ZZN4llvm3orc23AsynchronousSymbolQuery14handleCompleteERNS0_16ExecutionSessionEEN20RunQueryCompleteTask3runEv + fun:_ZNSt6thread11_State_implINS_8_InvokerISt5tupleIJZN4llvm3orc31DynamicThreadPoolTaskDispatcher8dispatchESt10unique_ptrINS4_4TaskESt14default_deleteIS7_EEEUlvE_EEEEE6_M_runEv + obj:/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30 +} diff --git a/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h index bcbe3e5e1f9fe..b0f7466b05838 100644 --- a/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h +++ b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h @@ -19,6 +19,7 @@ #include #include #include +#include #include // The cross-platform CPPINTEROP_API macro definition @@ -32,7 +33,7 @@ #endif #endif -namespace Cpp { +namespace CppImpl { using TCppIndex_t = size_t; using TCppScope_t = void*; using TCppConstScope_t = const void*; @@ -106,6 +107,12 @@ inline QualKind operator|(QualKind a, QualKind b) { static_cast(b)); } +enum class ValueKind : std::uint8_t { + None, + LValue, + RValue, +}; + /// A class modeling function calls for functions produced by the interpreter /// in compiled code. It provides an information if we are calling a standard /// function, constructor or destructor. @@ -385,6 +392,14 @@ CPPINTEROP_API std::string GetQualifiedName(TCppScope_t klass); /// gets the template arguments. CPPINTEROP_API std::string GetQualifiedCompleteName(TCppScope_t klass); +/// Retrieves the Doxygen documentation comment for a declaration. +/// \param[in] scope -- The declaration to get the comment for. +/// \param[in] strip_comment_markers -- If true, removes comment markers (///, +/// /**, etc.). +/// \returns The documentation comment, or empty string if none exists. +CPPINTEROP_API std::string GetDoxygenComment(TCppScope_t scope, + bool strip_comment_markers = true); + /// Gets the list of namespaces utilized in the supplied scope. CPPINTEROP_API std::vector GetUsingNamespaces(TCppScope_t scope); @@ -541,6 +556,9 @@ CPPINTEROP_API bool IsDestructor(TCppConstFunction_t method); /// Checks if the provided parameter is a 'Static' method. CPPINTEROP_API bool IsStaticMethod(TCppConstFunction_t method); +/// Checks if the provided constructor or conversion operator is explicit +CPPINTEROP_API bool IsExplicit(TCppConstFunction_t method); + ///\returns the address of the function given its potentially mangled name. CPPINTEROP_API TCppFuncAddr_t GetFunctionAddress(const char* mangled_name); @@ -614,11 +632,8 @@ CPPINTEROP_API TCppType_t GetPointeeType(TCppType_t type); /// Checks if type is a reference CPPINTEROP_API bool IsReferenceType(TCppType_t type); -/// Checks if type is a LValue reference -CPPINTEROP_API bool IsLValueReferenceType(TCppType_t type); - -/// Checks if type is a LValue reference -CPPINTEROP_API bool IsRValueReferenceType(TCppType_t type); +/// Get if lvalue or rvalue reference +CPPINTEROP_API ValueKind GetValueKind(TCppType_t type); /// Get the type that the reference refers to CPPINTEROP_API TCppType_t GetNonReferenceType(TCppType_t type); @@ -891,12 +906,13 @@ CPPINTEROP_API TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr, /// Destroys one or more objects of a class /// \param[in] This this pointer of the object to destruct. Can also be the /// starting address of an array of objects +/// \param[in] scope Class to destruct /// \param[in] withFree if true, we call operator delete/free, else just the /// destructor /// \param[in] count indicate the number of objects to destruct, if \c This /// points to an array of objects /// \returns true if wrapper generation and invocation succeeded. -CPPINTEROP_API bool Destruct(TCppObject_t This, TCppConstScope_t type, +CPPINTEROP_API bool Destruct(TCppObject_t This, TCppConstScope_t scope, bool withFree = true, TCppIndex_t count = 0UL); /// @name Stream Redirection @@ -934,6 +950,16 @@ CPPINTEROP_API void CodeComplete(std::vector& Results, ///\returns 0 on success, non-zero on failure. CPPINTEROP_API int Undo(unsigned N = 1); -} // end namespace Cpp +#ifndef _WIN32 +/// Returns the process ID of the executor process. +/// \returns the PID of the executor process. +CPPINTEROP_API pid_t GetExecutorPID(); +#endif + +} // namespace CppImpl +#ifndef CPPINTEROP_DISPATCH_H +// NOLINTNEXTLINE(misc-unused-alias-decls) +namespace Cpp = CppImpl; +#endif #endif // CPPINTEROP_CPPINTEROP_H diff --git a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h new file mode 100644 index 0000000000000..d9f0e9dd291f7 --- /dev/null +++ b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h @@ -0,0 +1,294 @@ +//===--- Dispatch.h - CppInterOp's API Dispatch Mechanism ---*- C++ -*-===// +// +// Part of the compiler-research project, under the Apache License v2.0 with +// LLVM Exceptions. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines the mechanism which enables dispatching of the CppInterOp API +// without linking, preventing any LLVM or Clang symbols from being leaked +// into the client application. This is achieved using a symbol-address table +// and an address lookup through a C symbol allowing clients to dlopen +// CppInterOp with RTLD_LOCAL, and automatically assign the API during runtime. +// +//===----------------------------------------------------------------------===// + +#ifndef CPPINTEROP_DISPATCH_H +#define CPPINTEROP_DISPATCH_H + +#ifdef CPPINTEROP_CPPINTEROP_H +#error "To use the Dispatch mechanism, do not include CppInterOp.h directly." +#endif + +#include + +#include +#include +#include + +#ifdef _WIN32 +#include +#undef LoadLibrary +#else +#include +#endif + +using CppFnPtrTy = void (*)(); +///\param[in] procname - the name of the FunctionEntry in the symbol lookup +/// table. +/// +///\returns the function address of the requested API, or nullptr if not found +extern "C" CPPINTEROP_API CppFnPtrTy CppGetProcAddress(const char* procname); + +// macro that allows declaration and loading of all CppInterOp API functions in +// a consistent way. This is used as our dispatched API list, along with the +// name-address pair table +#define CPPINTEROP_API_TABLE \ + DISPATCH_API(CreateInterpreter, decltype(&CppImpl::CreateInterpreter)) \ + DISPATCH_API(GetInterpreter, decltype(&CppImpl::GetInterpreter)) \ + DISPATCH_API(Process, decltype(&CppImpl::Process)) \ + DISPATCH_API(GetResourceDir, decltype(&CppImpl::GetResourceDir)) \ + DISPATCH_API(AddIncludePath, decltype(&CppImpl::AddIncludePath)) \ + DISPATCH_API(LoadLibrary, decltype(&CppImpl::LoadLibrary)) \ + DISPATCH_API(Declare, decltype(&CppImpl::Declare)) \ + DISPATCH_API(DeleteInterpreter, decltype(&CppImpl::DeleteInterpreter)) \ + DISPATCH_API(IsNamespace, decltype(&CppImpl::IsNamespace)) \ + DISPATCH_API(ObjToString, decltype(&CppImpl::ObjToString)) \ + DISPATCH_API(GetQualifiedCompleteName, \ + decltype(&CppImpl::GetQualifiedCompleteName)) \ + DISPATCH_API(GetValueKind, decltype(&CppImpl::GetValueKind)) \ + DISPATCH_API(GetNonReferenceType, decltype(&CppImpl::GetNonReferenceType)) \ + DISPATCH_API(IsEnumType, decltype(&CppImpl::IsEnumType)) \ + DISPATCH_API(GetIntegerTypeFromEnumType, \ + decltype(&CppImpl::GetIntegerTypeFromEnumType)) \ + DISPATCH_API(GetReferencedType, decltype(&CppImpl::GetReferencedType)) \ + DISPATCH_API(IsPointerType, decltype(&CppImpl::IsPointerType)) \ + DISPATCH_API(GetPointeeType, decltype(&CppImpl::GetPointeeType)) \ + DISPATCH_API(GetPointerType, decltype(&CppImpl::GetPointerType)) \ + DISPATCH_API(IsReferenceType, decltype(&CppImpl::IsReferenceType)) \ + DISPATCH_API(GetTypeAsString, decltype(&CppImpl::GetTypeAsString)) \ + DISPATCH_API(GetCanonicalType, decltype(&CppImpl::GetCanonicalType)) \ + DISPATCH_API(HasTypeQualifier, decltype(&CppImpl::HasTypeQualifier)) \ + DISPATCH_API(RemoveTypeQualifier, decltype(&CppImpl::RemoveTypeQualifier)) \ + DISPATCH_API(GetUnderlyingType, decltype(&CppImpl::GetUnderlyingType)) \ + DISPATCH_API(IsRecordType, decltype(&CppImpl::IsRecordType)) \ + DISPATCH_API(IsFunctionPointerType, \ + decltype(&CppImpl::IsFunctionPointerType)) \ + DISPATCH_API(GetVariableType, decltype(&CppImpl::GetVariableType)) \ + DISPATCH_API(GetNamed, decltype(&CppImpl::GetNamed)) \ + DISPATCH_API(GetScopeFromType, decltype(&CppImpl::GetScopeFromType)) \ + DISPATCH_API(GetClassTemplateInstantiationArgs, \ + decltype(&CppImpl::GetClassTemplateInstantiationArgs)) \ + DISPATCH_API(IsClass, decltype(&CppImpl::IsClass)) \ + DISPATCH_API(GetType, decltype(&CppImpl::GetType)) \ + DISPATCH_API(GetTypeFromScope, decltype(&CppImpl::GetTypeFromScope)) \ + DISPATCH_API(GetComplexType, decltype(&CppImpl::GetComplexType)) \ + DISPATCH_API(GetIntegerTypeFromEnumScope, \ + decltype(&CppImpl::GetIntegerTypeFromEnumScope)) \ + DISPATCH_API(GetUnderlyingScope, decltype(&CppImpl::GetUnderlyingScope)) \ + DISPATCH_API(GetScope, decltype(&CppImpl::GetScope)) \ + DISPATCH_API(GetGlobalScope, decltype(&CppImpl::GetGlobalScope)) \ + DISPATCH_API(GetScopeFromCompleteName, \ + decltype(&CppImpl::GetScopeFromCompleteName)) \ + DISPATCH_API(InstantiateTemplate, decltype(&CppImpl::InstantiateTemplate)) \ + DISPATCH_API(GetParentScope, decltype(&CppImpl::GetParentScope)) \ + DISPATCH_API(IsTemplate, decltype(&CppImpl::IsTemplate)) \ + DISPATCH_API(IsTemplateSpecialization, \ + decltype(&CppImpl::IsTemplateSpecialization)) \ + DISPATCH_API(IsTypedefed, decltype(&CppImpl::IsTypedefed)) \ + DISPATCH_API(IsClassPolymorphic, decltype(&CppImpl::IsClassPolymorphic)) \ + DISPATCH_API(Demangle, decltype(&CppImpl::Demangle)) \ + DISPATCH_API(SizeOf, decltype(&CppImpl::SizeOf)) \ + DISPATCH_API(GetSizeOfType, decltype(&CppImpl::GetSizeOfType)) \ + DISPATCH_API(IsBuiltin, decltype(&CppImpl::IsBuiltin)) \ + DISPATCH_API(IsComplete, decltype(&CppImpl::IsComplete)) \ + DISPATCH_API(Allocate, decltype(&CppImpl::Allocate)) \ + DISPATCH_API(Deallocate, decltype(&CppImpl::Deallocate)) \ + DISPATCH_API(Construct, decltype(&CppImpl::Construct)) \ + DISPATCH_API(Destruct, decltype(&CppImpl::Destruct)) \ + DISPATCH_API(IsAbstract, decltype(&CppImpl::IsAbstract)) \ + DISPATCH_API(IsEnumScope, decltype(&CppImpl::IsEnumScope)) \ + DISPATCH_API(IsEnumConstant, decltype(&CppImpl::IsEnumConstant)) \ + DISPATCH_API(IsAggregate, decltype(&CppImpl::IsAggregate)) \ + DISPATCH_API(HasDefaultConstructor, \ + decltype(&CppImpl::HasDefaultConstructor)) \ + DISPATCH_API(IsVariable, decltype(&CppImpl::IsVariable)) \ + DISPATCH_API(GetAllCppNames, decltype(&CppImpl::GetAllCppNames)) \ + DISPATCH_API(GetUsingNamespaces, decltype(&CppImpl::GetUsingNamespaces)) \ + DISPATCH_API(GetCompleteName, decltype(&CppImpl::GetCompleteName)) \ + DISPATCH_API(GetDestructor, decltype(&CppImpl::GetDestructor)) \ + DISPATCH_API(IsVirtualMethod, decltype(&CppImpl::IsVirtualMethod)) \ + DISPATCH_API(GetNumBases, decltype(&CppImpl::GetNumBases)) \ + DISPATCH_API(GetName, decltype(&CppImpl::GetName)) \ + DISPATCH_API(GetBaseClass, decltype(&CppImpl::GetBaseClass)) \ + DISPATCH_API(IsSubclass, decltype(&CppImpl::IsSubclass)) \ + DISPATCH_API(GetOperator, decltype(&CppImpl::GetOperator)) \ + DISPATCH_API(GetFunctionReturnType, \ + decltype(&CppImpl::GetFunctionReturnType)) \ + DISPATCH_API(GetBaseClassOffset, decltype(&CppImpl::GetBaseClassOffset)) \ + DISPATCH_API(GetClassMethods, decltype(&CppImpl::GetClassMethods)) \ + DISPATCH_API(GetFunctionsUsingName, \ + decltype(&CppImpl::GetFunctionsUsingName)) \ + DISPATCH_API(GetFunctionNumArgs, decltype(&CppImpl::GetFunctionNumArgs)) \ + DISPATCH_API(GetFunctionRequiredArgs, \ + decltype(&CppImpl::GetFunctionRequiredArgs)) \ + DISPATCH_API(GetFunctionArgName, decltype(&CppImpl::GetFunctionArgName)) \ + DISPATCH_API(GetFunctionArgType, decltype(&CppImpl::GetFunctionArgType)) \ + DISPATCH_API(GetFunctionArgDefault, \ + decltype(&CppImpl::GetFunctionArgDefault)) \ + DISPATCH_API(IsConstMethod, decltype(&CppImpl::IsConstMethod)) \ + DISPATCH_API(GetFunctionTemplatedDecls, \ + decltype(&CppImpl::GetFunctionTemplatedDecls)) \ + DISPATCH_API(ExistsFunctionTemplate, \ + decltype(&CppImpl::ExistsFunctionTemplate)) \ + DISPATCH_API(IsTemplatedFunction, decltype(&CppImpl::IsTemplatedFunction)) \ + DISPATCH_API(IsStaticMethod, decltype(&CppImpl::IsStaticMethod)) \ + DISPATCH_API(GetClassTemplatedMethods, \ + decltype(&CppImpl::GetClassTemplatedMethods)) \ + DISPATCH_API(BestOverloadFunctionMatch, \ + decltype(&CppImpl::BestOverloadFunctionMatch)) \ + DISPATCH_API(GetOperatorFromSpelling, \ + decltype(&CppImpl::GetOperatorFromSpelling)) \ + DISPATCH_API(IsFunctionDeleted, decltype(&CppImpl::IsFunctionDeleted)) \ + DISPATCH_API(IsPublicMethod, decltype(&CppImpl::IsPublicMethod)) \ + DISPATCH_API(IsProtectedMethod, decltype(&CppImpl::IsProtectedMethod)) \ + DISPATCH_API(IsPrivateMethod, decltype(&CppImpl::IsPrivateMethod)) \ + DISPATCH_API(IsConstructor, decltype(&CppImpl::IsConstructor)) \ + DISPATCH_API(IsDestructor, decltype(&CppImpl::IsDestructor)) \ + DISPATCH_API(GetDatamembers, decltype(&CppImpl::GetDatamembers)) \ + DISPATCH_API(GetStaticDatamembers, decltype(&CppImpl::GetStaticDatamembers)) \ + DISPATCH_API(GetEnumConstantDatamembers, \ + decltype(&CppImpl::GetEnumConstantDatamembers)) \ + DISPATCH_API(LookupDatamember, decltype(&CppImpl::LookupDatamember)) \ + DISPATCH_API(IsLambdaClass, decltype(&CppImpl::IsLambdaClass)) \ + DISPATCH_API(GetQualifiedName, decltype(&CppImpl::GetQualifiedName)) \ + DISPATCH_API(GetVariableOffset, decltype(&CppImpl::GetVariableOffset)) \ + DISPATCH_API(IsPublicVariable, decltype(&CppImpl::IsPublicVariable)) \ + DISPATCH_API(IsProtectedVariable, decltype(&CppImpl::IsProtectedVariable)) \ + DISPATCH_API(IsPrivateVariable, decltype(&CppImpl::IsPrivateVariable)) \ + DISPATCH_API(IsStaticVariable, decltype(&CppImpl::IsStaticVariable)) \ + DISPATCH_API(IsConstVariable, decltype(&CppImpl::IsConstVariable)) \ + DISPATCH_API(GetDimensions, decltype(&CppImpl::GetDimensions)) \ + DISPATCH_API(GetEnumConstants, decltype(&CppImpl::GetEnumConstants)) \ + DISPATCH_API(GetEnumConstantType, decltype(&CppImpl::GetEnumConstantType)) \ + DISPATCH_API(GetEnumConstantValue, decltype(&CppImpl::GetEnumConstantValue)) \ + DISPATCH_API(DumpScope, decltype(&CppImpl::DumpScope)) \ + DISPATCH_API(AddSearchPath, decltype(&CppImpl::AddSearchPath)) \ + DISPATCH_API(Evaluate, decltype(&CppImpl::Evaluate)) \ + DISPATCH_API(IsDebugOutputEnabled, decltype(&CppImpl::IsDebugOutputEnabled)) \ + DISPATCH_API(EnableDebugOutput, decltype(&CppImpl::EnableDebugOutput)) \ + DISPATCH_API(BeginStdStreamCapture, \ + decltype(&CppImpl::BeginStdStreamCapture)) \ + DISPATCH_API(GetDoxygenComment, decltype(&CppImpl::GetDoxygenComment)) \ + DISPATCH_API(IsExplicit, decltype(&CppImpl::IsExplicit)) \ + DISPATCH_API(MakeFunctionCallable, \ + CppImpl::JitCall (*)(CppImpl::TCppConstFunction_t)) \ + DISPATCH_API(GetFunctionAddress, \ + CppImpl::TCppFuncAddr_t (*)(CppImpl::TCppFunction_t)) \ + /*DISPATCH_API(API_name, fnptr_ty)*/ + +// TODO: implement overload that takes an existing opened DL handle +inline void* dlGetProcAddress(const char* name, + const char* customLibPath = nullptr) { + if (!name) + return nullptr; + + static std::once_flag init; + static void* (*getProc)(const char*) = nullptr; + + // this is currently not tested in a multiple thread/process setup + std::call_once(init, [customLibPath]() { + const char* path = + customLibPath ? customLibPath : std::getenv("CPPINTEROP_LIBRARY_PATH"); + if (!path) + return; + +#ifdef _WIN32 + HMODULE h = LoadLibraryA(path); + if (h) { + getProc = reinterpret_cast( + GetProcAddress(h, "CppGetProcAddress")); + if (!getProc) + FreeLibrary(h); + } +#else + void* handle = dlopen(path, RTLD_LOCAL | RTLD_NOW); + if (handle) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + getProc = reinterpret_cast( + dlsym(handle, "CppGetProcAddress")); + if (!getProc) dlclose(handle); + } +#endif + }); + + return getProc ? getProc(name) : nullptr; +} + +// CppAPIType is used for the extern clauses below +// FIXME: drop the using clauses +namespace CppAPIType { +// NOLINTBEGIN(bugprone-macro-parentheses) +#define DISPATCH_API(name, type) using name = type; +CPPINTEROP_API_TABLE +#undef DISPATCH_API +// NOLINTEND(bugprone-macro-parentheses) +} // end namespace CppAPIType + +namespace CppInternal::Dispatch { + +// FIXME: This is required for the types, but we should move the types +// into a separate namespace and only use that scope (CppImpl::Types) +using namespace CppImpl; + +// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) +#define DISPATCH_API(name, type) extern CppAPIType::name name; +CPPINTEROP_API_TABLE +#undef DISPATCH_API +// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) + +/// Initialize all CppInterOp API from the dynamically loaded library +/// (RTLD_LOCAL) +/// \param[in] customLibPath Optional custom path to libclangCppInterOp +/// \returns true if initialization succeeded, false otherwise +inline bool LoadDispatchAPI(const char* customLibPath = nullptr) { + std::cout << "[CppInterOp Dispatch] Loading CppInterOp API from " + << (customLibPath ? customLibPath : "default library path") << '\n'; + if (customLibPath) { + void* test = dlGetProcAddress("GetInterpreter", customLibPath); + if (!test) { + std::cerr << "[CppInterOp Dispatch] Failed to load API from: " + << customLibPath << '\n'; + return false; + } + } + +// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) +#define DISPATCH_API(name, type) \ + name = reinterpret_cast(dlGetProcAddress(#name)); + CPPINTEROP_API_TABLE +#undef DISPATCH_API + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) + + // Sanity check to verify that critical (and consequently all) functions are + // loaded + if (!GetInterpreter || !CreateInterpreter) { + std::cerr << "[CppInterOp Dispatch] Failed to load critical functions\n"; + return false; + } + + return true; +} + +// Unload all CppInterOp API functions +inline void UnloadDispatchAPI() { +#define DISPATCH_API(name, type) name = nullptr; + CPPINTEROP_API_TABLE +#undef DISPATCH_API +} +} // namespace CppInternal::Dispatch + +// NOLINTNEXTLINE(misc-unused-alias-decls) +namespace Cpp = CppInternal::Dispatch; +#endif // CPPINTEROP_DISPATCH_H diff --git a/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h b/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h index ecd57262fc75a..6570c03389883 100644 --- a/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h +++ b/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h @@ -256,6 +256,13 @@ CINDEX_LINKAGE CXScope clang_getDestructor(CXScope S); */ CINDEX_LINKAGE CXString clang_getFunctionSignature(CXScope func); +/** + * Returns the Doxygen documentation comment for a declaration, or an empty + * string if no documentation comment exists. + */ +CINDEX_LINKAGE CXString clang_getDoxygenComment(CXScope S, + bool strip_comment_markers); + /** * Checks if a function is a templated function. */ @@ -374,4 +381,4 @@ CINDEX_LINKAGE bool clang_destruct(CXObject This, CXScope S, LLVM_CLANG_C_EXTERN_C_END #endif // LLVM_CLANG_C_CXCPPINTEROP_H - // NOLINTEND() \ No newline at end of file + // NOLINTEND() diff --git a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt index d5670bca170a3..0383055cd2481 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt @@ -18,25 +18,22 @@ else() list(APPEND LLVM_LINK_COMPONENTS OrcDebugging) endif() endif() - set(DLM - DynamicLibraryManager.cpp - DynamicLibraryManagerSymbol.cpp - Paths.cpp - ) - if (CPPINTEROP_USE_CLING) - set(LLVM_OPTIONAL_SOURCES ${LLVM_OPTIONAL_SOURCES} ${DLM}) - set(DLM) - endif(CPPINTEROP_USE_CLING) - if (CPPINTEROP_USE_REPL) - #Use DML optional sources - endif(CPPINTEROP_USE_REPL) - if (CPPINTEROP_USE_CLING) - set(cling_clang_interp clingInterpreter) - endif() - if (CPPINTEROP_USE_REPL) - set(cling_clang_interp clangInterpreter) - endif() +set(DLM + DynamicLibraryManager.cpp + DynamicLibraryManagerSymbol.cpp + Paths.cpp +) + +# Set sources based on whether Cling or Clang-REPL is used +if (CPPINTEROP_USE_CLING) + set(LLVM_OPTIONAL_SOURCES ${LLVM_OPTIONAL_SOURCES} ${DLM}) + set(DLM) + set(cling_clang_interp clingInterpreter) +endif() +if (CPPINTEROP_USE_REPL) + set(cling_clang_interp clangInterpreter) +endif() if(EMSCRIPTEN) set(link_libs @@ -53,68 +50,69 @@ else() ) endif() - if(NOT WIN32 AND NOT EMSCRIPTEN) - list(APPEND link_libs dl) - endif() +if(NOT WIN32 AND NOT EMSCRIPTEN) + list(APPEND link_libs dl) +endif() - # Get rid of libLLVM-X.so which is appended to the list of static libraries. - if (LLVM_LINK_LLVM_DYLIB) - set(new_libs ${link_libs}) - set(libs ${new_libs}) - while(NOT "${new_libs}" STREQUAL "") - foreach(lib ${new_libs}) - if(TARGET ${lib}) - get_target_property(transitive_libs ${lib} INTERFACE_LINK_LIBRARIES) - if (NOT transitive_libs) +# Get rid of libLLVM-X.so which is appended to the list of static libraries. +if (LLVM_LINK_LLVM_DYLIB) + set(new_libs ${link_libs}) + set(libs ${new_libs}) + while(NOT "${new_libs}" STREQUAL "") + foreach(lib ${new_libs}) + if(TARGET ${lib}) + get_target_property(transitive_libs ${lib} INTERFACE_LINK_LIBRARIES) + if (NOT transitive_libs) + continue() + endif() + foreach(transitive_lib ${transitive_libs}) + if(NOT TARGET ${transitive_lib}) continue() endif() - foreach(transitive_lib ${transitive_libs}) - if(NOT TARGET ${transitive_lib}) - continue() - endif() - get_target_property(lib_type ${transitive_lib} TYPE) - if("${lib_type}" STREQUAL "STATIC_LIBRARY") - list(APPEND static_transitive_libs ${transitive_lib}) - else() - # Filter our libLLVM.so and friends. - continue() - endif() - if(NOT ${transitive_lib} IN_LIST libs) - list(APPEND newer_libs ${transitive_lib}) - list(APPEND libs ${transitive_lib}) - endif() - endforeach(transitive_lib) - # Update the target properties with the list of only static libraries. - set_target_properties(${lib} PROPERTIES INTERFACE_LINK_LIBRARIES "${static_transitive_libs}") - set(static_transitive_libs "") - endif() - endforeach(lib) - set(new_libs ${newer_libs}) - set(newer_libs "") - endwhile() - # We just got rid of the libLLVM.so and other components shipped as shared - # libraries, we need to make up for the missing dependency. - list(APPEND LLVM_LINK_COMPONENTS - Coverage - FrontendHLSL - LTO - ) - # We will need to append the missing dependencies to pull in the right - # LLVM library dependencies. - list(APPEND link_libs - clangCodeGen - clangStaticAnalyzerCore - ) - endif(LLVM_LINK_LLVM_DYLIB) - add_llvm_library(clangCppInterOp BUILDTREE_ONLY - DISABLE_LLVM_LINK_LLVM_DYLIB - CppInterOp.cpp - CXCppInterOp.cpp - ${DLM} - LINK_LIBS - ${link_libs} - ) + get_target_property(lib_type ${transitive_lib} TYPE) + if("${lib_type}" STREQUAL "STATIC_LIBRARY") + list(APPEND static_transitive_libs ${transitive_lib}) + else() + # Filter our libLLVM.so and friends. + continue() + endif() + if(NOT ${transitive_lib} IN_LIST libs) + list(APPEND newer_libs ${transitive_lib}) + list(APPEND libs ${transitive_lib}) + endif() + endforeach(transitive_lib) + # Update the target properties with the list of only static libraries. + set_target_properties(${lib} PROPERTIES INTERFACE_LINK_LIBRARIES "${static_transitive_libs}") + set(static_transitive_libs "") + endif() + endforeach(lib) + set(new_libs ${newer_libs}) + set(newer_libs "") + endwhile() + # We just got rid of the libLLVM.so and other components shipped as shared + # libraries, we need to make up for the missing dependency. + list(APPEND LLVM_LINK_COMPONENTS + Coverage + FrontendHLSL + LTO + ) + # We will need to append the missing dependencies to pull in the right + # LLVM library dependencies. + list(APPEND link_libs + clangCodeGen + clangStaticAnalyzerCore + ) +endif(LLVM_LINK_LLVM_DYLIB) +add_llvm_library(clangCppInterOp BUILDTREE_ONLY + DISABLE_LLVM_LINK_LLVM_DYLIB + CppInterOp.cpp + Dispatch.cpp + CXCppInterOp.cpp + ${DLM} + LINK_LIBS + ${link_libs} +) if(EMSCRIPTEN) if(BUILD_SHARED_LIBS) @@ -133,6 +131,7 @@ if(EMSCRIPTEN) target_compile_options(clangCppInterOp PRIVATE "SHELL: -Oz" PRIVATE "SHELL: -flto" + PRIVATE "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" ) target_link_options(clangCppInterOp PRIVATE "SHELL: -s WASM_BIGINT" @@ -140,20 +139,24 @@ if(EMSCRIPTEN) PRIVATE "SHELL: ${SYMBOLS_LIST}" PRIVATE "SHELL: -Oz" PRIVATE "SHELL: -flto" + PRIVATE "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" + ) + else() + target_compile_options(clangCppInterOp + PRIVATE "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" ) -else() target_link_options(clangCppInterOp - PRIVATE "SHELL: -s WASM_BIGINT" + PRIVATE "SHELL: -s WASM_BIGINT" + PRIVATE "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" ) -endif() + endif(BUILD_SHARED_LIBS) if (CPPINTEROP_ENABLE_TESTING) # When compiling Emscripten tests the CppInterOp library it links to is expected to be in the same folder as the compiled Javascript add_custom_command(TARGET clangCppInterOp POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_BINARY_DIR}/unittests/CppInterOp/ ) endif(CPPINTEROP_ENABLE_TESTING) - -endif() +endif(EMSCRIPTEN) target_compile_definitions(clangCppInterOp PUBLIC "_CINDEX_LIB_") # workaround for the use of `CINDEX_LINKAGE` diff --git a/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp index 00ecdf4392a87..a7f7008baf355 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp @@ -323,9 +323,9 @@ void clang_Interpreter_addIncludePath(CXInterpreter I, const char* dir) { getInterpreter(I)->AddIncludePath(dir); } -namespace Cpp { +namespace CppImpl { int Declare(compat::Interpreter& interp, const char* code, bool silent); -} // namespace Cpp +} // namespace CppImpl enum CXErrorCode clang_Interpreter_declare(CXInterpreter I, const char* code, bool silent) { @@ -406,11 +406,11 @@ CXString clang_Interpreter_searchLibrariesForSymbol(CXInterpreter I, mangled_name, search_system)); } -namespace Cpp { +namespace CppImpl { bool InsertOrReplaceJitSymbol(compat::Interpreter& I, const char* linker_mangled_name, uint64_t address); -} // namespace Cpp +} // namespace CppImpl bool clang_Interpreter_insertOrReplaceJitSymbol(CXInterpreter I, const char* linker_mangled_name, @@ -519,6 +519,15 @@ CXString clang_getFunctionSignature(CXScope func) { return makeCXString(""); } +CXString clang_getDoxygenComment(CXScope S, bool strip_comment_markers) { + if (isNull(S)) + return makeCXString(""); + + auto* D = getDecl(S); + return makeCXString(Cpp::GetDoxygenComment(static_cast(D), + strip_comment_markers)); +} + bool clang_isTemplatedFunction(CXScope func) { auto* D = getDecl(func); if (llvm::isa_and_nonnull(D)) @@ -543,7 +552,7 @@ bool clang_existsFunctionTemplate(const char* name, CXScope parent) { const auto* Within = llvm::dyn_cast(getDecl(parent)); auto& S = getInterpreter(parent)->getSema(); - auto* ND = Cpp::Cpp_utils::Lookup::Named(&S, name, Within); + auto* ND = CppInternal::utils::Lookup::Named(&S, name, Within); if (!ND) return false; @@ -556,12 +565,12 @@ bool clang_existsFunctionTemplate(const char* name, CXScope parent) { return true; } -namespace Cpp { +namespace CppImpl { TCppScope_t InstantiateTemplate(compat::Interpreter& I, TCppScope_t tmpl, const TemplateArgInfo* template_args, size_t template_args_size, bool instantiate_body = false); -} // namespace Cpp +} // namespace CppImpl CXScope clang_instantiateTemplate(CXScope tmpl, CXTemplateArgInfo* template_args, @@ -584,10 +593,10 @@ CXObject clang_allocate(unsigned int n) { return ::operator new(n); } void clang_deallocate(CXObject address) { ::operator delete(address); } -namespace Cpp { +namespace CppImpl { void* Construct(compat::Interpreter& interp, TCppScope_t scope, void* arena /*=nullptr*/, TCppIndex_t count); -} // namespace Cpp +} // namespace CppImpl CXObject clang_construct(CXScope scope, void* arena, size_t count) { return Cpp::Construct(*getInterpreter(scope), @@ -600,10 +609,10 @@ void clang_invoke(CXScope func, void* result, void** args, size_t n, .Invoke(result, {args, n}, self); } -namespace Cpp { +namespace CppImpl { bool Destruct(compat::Interpreter& interp, TCppObject_t This, const clang::Decl* Class, bool withFree, size_t nary); -} // namespace Cpp +} // namespace CppImpl bool clang_destruct(CXObject This, CXScope S, bool withFree, size_t nary) { return Cpp::Destruct(*getInterpreter(S), This, getDecl(S), withFree, nary); diff --git a/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h b/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h index 24c822decd0f9..e8bd64864d6cd 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h +++ b/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h @@ -31,6 +31,23 @@ static inline char* GetEnv(const char* Var_Name) { #endif } +#if CLANG_VERSION_MAJOR < 21 +#define Print_Canonical_Types PrintCanonicalTypes +#else +#define Print_Canonical_Types PrintAsCanonical +#endif + +#if CLANG_VERSION_MAJOR < 21 +#define clang_LookupResult_Found clang::LookupResult::Found +#define clang_LookupResult_Not_Found clang::LookupResult::NotFound +#define clang_LookupResult_Found_Overloaded clang::LookupResult::FoundOverloaded +#else +#define clang_LookupResult_Found clang::LookupResultKind::Found +#define clang_LookupResult_Not_Found clang::LookupResultKind::NotFound +#define clang_LookupResult_Found_Overloaded \ + clang::LookupResultKind::FoundOverloaded +#endif + #if CLANG_VERSION_MAJOR < 19 #define Template_Deduction_Result Sema::TemplateDeductionResult #define Template_Deduction_Result_Success \ @@ -63,6 +80,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,9 +107,10 @@ static inline char* GetEnv(const char* Var_Name) { #include "cling/Utils/AST.h" #include +#include -namespace Cpp { -namespace Cpp_utils = cling::utils; +namespace CppInternal { +namespace utils = cling::utils; } namespace compat { @@ -202,10 +223,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 +276,62 @@ createClangInterpreter(std::vector& args) { (*ciOrErr)->LoadRequestedPlugins(); if (CudaEnabled) DeviceCI->LoadRequestedPlugins(); + + bool outOfProcess; +#if defined(_WIN32) || !defined(LLVM_BUILT_WITH_OOP_JIT) + outOfProcess = false; +#else + outOfProcess = std::any_of(args.begin(), args.end(), [](const char* arg) { + return llvm::StringRef(arg).trim() == "--use-oop-jit"; + }); +#endif + +#ifdef LLVM_BUILT_WITH_OOP_JIT + + clang::Interpreter::JITConfig OutOfProcessConfig; + if (outOfProcess) { + OutOfProcessConfig.IsOutOfProcess = true; + OutOfProcessConfig.OOPExecutor = + LLVM_BINARY_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_BINARY_LIB_DIR "/lib/clang/" STRINGIFY( + LLVM_VERSION_MAJOR) "/lib/darwin/liborc_rt_osx.a"; +#else + std::string OrcRuntimePath = LLVM_BINARY_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:"); @@ -367,12 +451,8 @@ inline void codeComplete(std::vector& Results, #include "CppInterOpInterpreter.h" -namespace Cpp { -namespace Cpp_utils = Cpp::utils; -} - namespace compat { -using Interpreter = Cpp::Interpreter; +using Interpreter = CppInternal::Interpreter; class SynthesizingCodeRAII { private: @@ -387,7 +467,7 @@ class SynthesizingCodeRAII { "Failed to generate PTU:"); } }; -} +} // namespace compat #endif // CPPINTEROP_USE_REPL @@ -412,11 +492,14 @@ inline void InstantiateClassTemplateSpecialization( #if CLANG_VERSION_MAJOR < 20 interp.getSema().InstantiateClassTemplateSpecialization( clang::SourceLocation::getFromRawEncoding(1), CTSD, - clang::TemplateSpecializationKind::TSK_Undeclared, /*Complain=*/true); + + clang::TemplateSpecializationKind::TSK_ExplicitInstantiationDefinition, + /*Complain=*/true); #else interp.getSema().InstantiateClassTemplateSpecialization( clang::SourceLocation::getFromRawEncoding(1), CTSD, - clang::TemplateSpecializationKind::TSK_Undeclared, /*Complain=*/true, + clang::TemplateSpecializationKind::TSK_ExplicitInstantiationDefinition, + /*Complain=*/true, /*PrimaryHasMatchedPackOnParmToNonPackOnArg=*/false); #endif } diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp old mode 100644 new mode 100755 index b6cf555e8d524..8c247b1736179 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -13,6 +13,7 @@ #include "clang/AST/Attrs.inc" #include "clang/AST/CXXInheritance.h" +#include "clang/AST/Comment.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclAccessPair.h" #include "clang/AST/DeclBase.h" @@ -24,6 +25,7 @@ #include "clang/AST/Mangle.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/QualTypeNames.h" +#include "clang/AST/RawCommentList.h" #include "clang/AST/RecordLayout.h" #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" @@ -31,6 +33,7 @@ #include "clang/Basic/Linkage.h" #include "clang/Basic/OperatorKinds.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" #include "clang/Basic/Version.h" #include "clang/Frontend/CompilerInstance.h" @@ -48,6 +51,11 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Demangle/Demangle.h" +#if CLANG_VERSION_MAJOR >= 20 +#include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h" +#include "llvm/ExecutionEngine/Orc/CoreContainers.h" +#endif +#include "llvm/ExecutionEngine/Orc/Core.h" #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" #include "llvm/IR/GlobalValue.h" #include "llvm/Support/Casting.h" @@ -57,11 +65,17 @@ #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/Host.h" +#include "llvm/TargetParser/Triple.h" #include #include #include +#include +#include #include +#include +#include #include #include #include @@ -69,13 +83,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)) @@ -86,7 +104,34 @@ #include #endif // WIN32 -namespace Cpp { +// Runtime symbols required if the library using JIT (Cpp::Evaluate) does +// not link to llvm +#if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) +struct __clang_Interpreter_NewTag { +} __ci_newtag; +#if CLANG_VERSION_MAJOR > 21 +extern "C" void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, + void* OpaqueType); +#else +void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, + void* OpaqueType); +#endif + +#if CLANG_VERSION_MAJOR > 18 +extern "C" void __clang_Interpreter_SetValueNoAlloc(void* This, void* OutVal, + void* OpaqueType, ...); +#else +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, void*); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, float); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, double); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, long double); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, + unsigned long long); +#endif +#endif // CPPINTEROP_USE_CLING + +namespace CppImpl { using namespace clang; using namespace llvm; @@ -633,6 +678,28 @@ std::string GetQualifiedCompleteName(TCppType_t klass) { return ""; } +std::string GetDoxygenComment(TCppScope_t scope, bool strip_comment_markers) { + auto* D = static_cast(scope); + if (!D) + return ""; + + D = D->getCanonicalDecl(); + ASTContext& C = D->getASTContext(); + + const RawComment* RC = C.getRawCommentForAnyRedecl(D); + if (!RC) + return ""; + + (void)C.getCommentForDecl(D, /*PP=*/nullptr); + + const SourceManager& SM = C.getSourceManager(); + + if (!strip_comment_markers) + return RC->getRawText(SM).str(); + + return RC->getFormattedText(SM, C.getDiagnostics()); +} + std::vector GetUsingNamespaces(TCppScope_t scope) { auto* D = (clang::Decl*)scope; @@ -729,8 +796,12 @@ TCppScope_t GetNamed(const std::string& name, D = GetUnderlyingScope(D); Within = llvm::dyn_cast(D); } - - auto* ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); +#ifdef CPPINTEROP_USE_CLING + if (Within) + Within->getPrimaryContext()->buildLookup(); + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif + auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if (ND && ND != (clang::NamedDecl*)-1) { return (TCppScope_t)(ND->getCanonicalDecl()); } @@ -986,7 +1057,7 @@ std::vector GetFunctionsUsingName(TCppScope_t scope, clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, For_Visible_Redeclaration); - Cpp_utils::Lookup::Named(&S, R, Decl::castToDeclContext(D)); + CppInternal::utils::Lookup::Named(&S, R, Decl::castToDeclContext(D)); if (R.empty()) return funcs; @@ -1130,7 +1201,7 @@ bool ExistsFunctionTemplate(const std::string& name, TCppScope_t parent) { Within = llvm::dyn_cast(D); } - auto* ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); + auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if ((intptr_t)ND == (intptr_t)0) return false; @@ -1175,18 +1246,18 @@ bool GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, For_Visible_Redeclaration); auto* DC = clang::Decl::castToDeclContext(D); - Cpp_utils::Lookup::Named(&S, R, DC); + CppInternal::utils::Lookup::Named(&S, R, DC); - if (R.getResultKind() == clang::LookupResult::NotFound && funcs.empty()) + if (R.getResultKind() == clang_LookupResult_Not_Found && funcs.empty()) return false; // Distinct match, single Decl - else if (R.getResultKind() == clang::LookupResult::Found) { + else if (R.getResultKind() == clang_LookupResult_Found) { if (IsTemplatedFunction(R.getFoundDecl())) funcs.push_back(R.getFoundDecl()); } // Loop over overload set - else if (R.getResultKind() == clang::LookupResult::FoundOverloaded) { + else if (R.getResultKind() == clang_LookupResult_Found_Overloaded) { for (auto* Found : R) if (IsTemplatedFunction(Found)) funcs.push_back(Found); @@ -1228,7 +1299,9 @@ BestOverloadFunctionMatch(const std::vector& candidates, size_t idx = 0; for (auto i : arg_types) { QualType Type = QualType::getFromOpaquePtr(i.m_Type); - ExprValueKind ExprKind = ExprValueKind::VK_PRValue; + // XValue is an object that can be "moved" whereas PRValue is temporary + // value. This enables overloads that require the object to be moved + ExprValueKind ExprKind = ExprValueKind::VK_XValue; if (Type->isLValueReferenceType()) ExprKind = ExprValueKind::VK_LValue; @@ -1336,6 +1409,27 @@ bool IsStaticMethod(TCppConstFunction_t method) { return false; } +bool IsExplicit(TCppConstFunction_t method) { + if (!method) + return false; + + const auto* D = static_cast(method); + + if (const auto* FTD = llvm::dyn_cast_or_null(D)) + D = FTD->getTemplatedDecl(); + + if (const auto* CD = llvm::dyn_cast_or_null(D)) + return CD->isExplicit(); + + if (const auto* CD = llvm::dyn_cast_or_null(D)) + return CD->isExplicit(); + + if (const auto* DGD = llvm::dyn_cast_or_null(D)) + return DGD->isExplicit(); + + return false; +} + TCppFuncAddr_t GetFunctionAddress(const char* mangled_name) { auto& I = getInterp(); auto FDAorErr = compat::getSymbolAddress(I, mangled_name); @@ -1467,7 +1561,7 @@ TCppScope_t LookupDatamember(const std::string& name, TCppScope_t parent) { Within = llvm::dyn_cast(D); } - auto* ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); + auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if (ND && ND != (clang::NamedDecl*)-1) { if (llvm::isa_and_nonnull(ND)) { return (TCppScope_t)ND; @@ -1691,14 +1785,13 @@ bool IsReferenceType(TCppType_t type) { return QT->isReferenceType(); } -bool IsLValueReferenceType(TCppType_t type) { - QualType QT = QualType::getFromOpaquePtr(type); - return QT->isLValueReferenceType(); -} - -bool IsRValueReferenceType(TCppType_t type) { +ValueKind GetValueKind(TCppType_t type) { QualType QT = QualType::getFromOpaquePtr(type); - return QT->isRValueReferenceType(); + if (QT->isRValueReferenceType()) + return ValueKind::RValue; + if (QT->isLValueReferenceType()) + return ValueKind::LValue; + return ValueKind::None; } TCppType_t GetPointerType(TCppType_t type) { @@ -1954,7 +2047,7 @@ static void GetDeclName(const clang::Decl* D, ASTContext& Context, PrintingPolicy Policy(Context.getPrintingPolicy()); Policy.SuppressTagKeyword = true; Policy.SuppressUnwrittenScope = true; - Policy.PrintCanonicalTypes = true; + Policy.Print_Canonical_Types = true; if (const TypeDecl* TD = dyn_cast(D)) { // This is a class, struct, or union member. QualType QT; @@ -3169,6 +3262,29 @@ CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func) { } namespace { +#if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) +bool DefineAbsoluteSymbol(compat::Interpreter& I, + const char* linker_mangled_name, uint64_t address) { + using namespace llvm; + using namespace llvm::orc; + + llvm::orc::LLJIT& Jit = *compat::getExecutionEngine(I); + llvm::orc::ExecutionSession& ES = Jit.getExecutionSession(); + JITDylib& DyLib = *Jit.getProcessSymbolsJITDylib().get(); + + llvm::orc::SymbolMap InjectedSymbols{ + {ES.intern(linker_mangled_name), + ExecutorSymbolDef(ExecutorAddr(address), JITSymbolFlags::Exported)}}; + + if (Error Err = DyLib.define(absoluteSymbols(InjectedSymbols))) { + logAllUnhandledErrors(std::move(Err), errs(), + "DefineAbsoluteSymbol error: "); + return true; + } + return false; +} +#endif + static std::string MakeResourcesPath() { StringRef Dir; #ifdef LLVM_BINARY_DIR @@ -3192,12 +3308,40 @@ static std::string MakeResourcesPath() { CLANG_VERSION_MAJOR_STRING); return std::string(P.str()); } + +void AddLibrarySearchPaths(const std::string& ResourceDir, + compat::Interpreter* I) { + // the resource-dir can be of the form + // /prefix/lib/clang/XX or /prefix/lib/llvm-XX/lib/clang/XX + // where XX represents version + // the corresponing path we want to add are + // /prefix/lib/clang/XX/lib, /prefix/lib/, and + // /prefix/lib/llvm-XX/lib/clang/XX/lib, /prefix/lib/llvm-XX/lib/, + // /prefix/lib/ + std::string path1 = ResourceDir + "/lib"; + I->getDynamicLibraryManager()->addSearchPath(path1, false, false); + size_t pos = ResourceDir.rfind("/llvm-"); + if (pos != std::string::npos) { + I->getDynamicLibraryManager()->addSearchPath(ResourceDir.substr(0, pos), + false, false); + } + pos = ResourceDir.rfind("/clang"); + if (pos != std::string::npos) { + I->getDynamicLibraryManager()->addSearchPath(ResourceDir.substr(0, pos), + false, false); + } +} } // namespace TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, const std::vector& GpuArgs /*={}*/) { std::string MainExecutableName = sys::fs::getMainExecutable(nullptr, nullptr); std::string ResourceDir = MakeResourcesPath(); + llvm::Triple T(llvm::sys::getProcessTriple()); + namespace fs = std::filesystem; + if ((!fs::is_directory(ResourceDir)) && (T.isOSDarwin() || T.isOSLinux())) + ResourceDir = DetectResourceDir(); + std::vector ClingArgv = {"-resource-dir", ResourceDir.c_str(), "-std=c++14"}; ClingArgv.insert(ClingArgv.begin(), MainExecutableName.c_str()); @@ -3238,8 +3382,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(); @@ -3260,6 +3405,9 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); } + if (!T.isWasm()) + AddLibrarySearchPaths(ResourceDir, I); + I->declare(R"( namespace __internal_CppInterOp { template @@ -3273,6 +3421,88 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, sInterpreters->emplace_back(I, /*Owned=*/true); +// Define runtime symbols in the JIT dylib for clang-repl +#if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) + DefineAbsoluteSymbol(*I, "__ci_newtag", + reinterpret_cast(&__ci_newtag)); +// llvm >= 21 has this defined as a C symbol that does not require mangling +#if CLANG_VERSION_MAJOR >= 21 + DefineAbsoluteSymbol( + *I, "__clang_Interpreter_SetValueWithAlloc", + reinterpret_cast(&__clang_Interpreter_SetValueWithAlloc)); +#else + // obtain mangled name + auto* D = static_cast( + Cpp::GetNamed("__clang_Interpreter_SetValueWithAlloc")); + if (auto* FD = llvm::dyn_cast(D)) { + auto GD = GlobalDecl(FD); + std::string mangledName; + compat::maybeMangleDeclName(GD, mangledName); + DefineAbsoluteSymbol( + *I, mangledName.c_str(), + reinterpret_cast(&__clang_Interpreter_SetValueWithAlloc)); + } +#endif +// llvm < 19 has multiple overloads of __clang_Interpreter_SetValueNoAlloc +#if CLANG_VERSION_MAJOR < 19 + // obtain all 6 candidates, and obtain the correct Decl for each overload + // using BestOverloadFunctionMatch. We then map the decl to the correct + // function pointer (force the compiler to find the right declarion by casting + // to the corresponding function pointer signature) and then register it. + const std::vector Methods = Cpp::GetFunctionsUsingName( + Cpp::GetGlobalScope(), "__clang_Interpreter_SetValueNoAlloc"); + std::string mangledName; + ASTContext& Ctxt = I->getSema().getASTContext(); + auto* TAI = Ctxt.VoidPtrTy.getAsOpaquePtr(); + + // possible parameter lists for __clang_Interpreter_SetValueNoAlloc overloads + // in LLVM 18 + const std::vector> a_params = { + {TAI, TAI, TAI}, + {TAI, TAI, TAI, TAI}, + {TAI, TAI, TAI, Ctxt.FloatTy.getAsOpaquePtr()}, + {TAI, TAI, TAI, Ctxt.DoubleTy.getAsOpaquePtr()}, + {TAI, TAI, TAI, Ctxt.LongDoubleTy.getAsOpaquePtr()}, + {TAI, TAI, TAI, Ctxt.UnsignedLongLongTy.getAsOpaquePtr()}}; + + using FP0 = void (*)(void*, void*, void*); + using FP1 = void (*)(void*, void*, void*, void*); + using FP2 = void (*)(void*, void*, void*, float); + using FP3 = void (*)(void*, void*, void*, double); + using FP4 = void (*)(void*, void*, void*, long double); + using FP5 = void (*)(void*, void*, void*, unsigned long long); + + const std::vector func_pointers = { + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc))}; + + // these symbols are not externed, so we need to mangle their names + for (size_t i = 0; i < a_params.size(); ++i) { + auto* decl = static_cast( + Cpp::BestOverloadFunctionMatch(Methods, {}, a_params[i])); + if (auto* fd = llvm::dyn_cast(decl)) { + auto gd = clang::GlobalDecl(fd); + compat::maybeMangleDeclName(gd, mangledName); + DefineAbsoluteSymbol(*I, mangledName.c_str(), + reinterpret_cast(func_pointers[i])); + } + } +#else + DefineAbsoluteSymbol( + *I, "__clang_Interpreter_SetValueNoAlloc", + reinterpret_cast(&__clang_Interpreter_SetValueNoAlloc)); +#endif +#endif return I; } @@ -3704,6 +3934,10 @@ void GetAllCppNames(TCppScope_t scope, std::set& names) { clang::DeclContext* DC; clang::DeclContext::decl_iterator decl; +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif + if (auto* TD = dyn_cast_or_null(D)) { DC = clang::TagDecl::castToDeclContext(TD); decl = DC->decls_begin(); @@ -3970,12 +4204,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 +4222,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 +4259,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 +4267,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 +4281,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 +4310,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 +4359,15 @@ int Undo(unsigned N) { #endif } -} // end namespace Cpp +#ifndef _WIN32 +pid_t GetExecutorPID() { +#ifdef LLVM_BUILT_WITH_OOP_JIT + auto& I = getInterp(); + return I.getOutOfProcessExecutorPID(); +#endif + return getpid(); +} + +#endif + +} // namespace CppImpl diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h index d87eb33e15827..83f17df7d3dbf 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h +++ b/interpreter/CppInterOp/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 @@ -59,7 +67,7 @@ template static D* LookupResult2Decl(clang::LookupResult& R) { } } // namespace -namespace Cpp { +namespace CppInternal { namespace utils { namespace Lookup { @@ -133,17 +141,68 @@ inline clang::NamedDecl* Named(clang::Sema* S, const char* Name, } // namespace Lookup } // namespace utils -} // namespace Cpp +} // namespace CppInternal -namespace Cpp { +namespace CppInternal { /// 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 = 0; + int stdout_fd = 1; + int stderr_fd = 2; + + // 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"; + stdin_fd = -1; + stdout_fd = -1; + stderr_fd = -1; + } + } + stdin_fd = fileno(io_ctx->stdin_file.get()); + stdout_fd = fileno(io_ctx->stdout_file.get()); + stderr_fd = fileno(io_ctx->stderr_file.get()); + + 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 @@ -155,16 +214,47 @@ class Interpreter { llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargets(); llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmParsers(); llvm::InitializeAllAsmPrinters(); std::vector vargs(argv + 1, argv + argc); - auto CI = compat::createClangInterpreter(vargs); + + int stdin_fd = 0; + int stdout_fd = 1; + int stderr_fd = 2; + auto io_ctx = std::make_unique(); + bool outOfProcess = false; + +#if defined(_WIN32) || !defined(LLVM_BUILT_WITH_OOP_JIT) + outOfProcess = false; +#else + outOfProcess = std::any_of(vargs.begin(), vargs.end(), [](const char* arg) { + return llvm::StringRef(arg).trim() == "--use-oop-jit"; + }); +#endif + + if (outOfProcess) { + std::tie(stdin_fd, stdout_fd, stderr_fd) = + initAndGetFileDescriptors(vargs, io_ctx); + + if (stdin_fd == -1 || stdout_fd == -1 || stderr_fd == -1) { + llvm::errs() + << "Redirection files creation failed for Out-Of-Process JIT\n"; + return nullptr; + } + } + + // Currently, we can't pass IOContext in `createClangInterpreter`, that's + // why fd's are passed. This should be refactored later. + 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() {} @@ -172,6 +262,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. /// @@ -230,6 +344,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 @@ -371,7 +494,7 @@ class Interpreter { // Save the current number of entries size_t Idx = HOpts.UserEntries.size(); - Cpp::utils::AddIncludePaths(PathsStr, HOpts, Delim); + CppInternal::utils::AddIncludePaths(PathsStr, HOpts, Delim); clang::Preprocessor& PP = CI->getPreprocessor(); clang::SourceManager& SM = PP.getSourceManager(); @@ -409,8 +532,8 @@ class Interpreter { /// void GetIncludePaths(llvm::SmallVectorImpl& incpaths, bool withSystem, bool withFlags) const { - utils::CopyIncludePaths(getCI()->getHeaderSearchOpts(), incpaths, - withSystem, withFlags); + CppInternal::utils::CopyIncludePaths(getCI()->getHeaderSearchOpts(), + incpaths, withSystem, withFlags); } CompilationResult loadLibrary(const std::string& filename, bool lookup) { @@ -464,6 +587,6 @@ class Interpreter { } }; // Interpreter -} // namespace Cpp +} // namespace CppInternal #endif // CPPINTEROP_INTERPRETER_H diff --git a/interpreter/CppInterOp/lib/CppInterOp/Dispatch.cpp b/interpreter/CppInterOp/lib/CppInterOp/Dispatch.cpp new file mode 100644 index 0000000000000..e0bda99155711 --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/Dispatch.cpp @@ -0,0 +1,26 @@ +#include + +#include // for std::cerr +#include +#include + +// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast) +static const std::unordered_map DispatchMap = { +#define DISPATCH_API(name, type) \ + {#name, (CppFnPtrTy) static_cast(&CppImpl::name)}, + CPPINTEROP_API_TABLE +#undef DISPATCH_API +}; +// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast) + +CppFnPtrTy CppGetProcAddress(const char* funcName) { + auto it = DispatchMap.find(funcName); + if (it == DispatchMap.end()) { + std::cerr + << "[CppInterOp Dispatch] Failed to find API: " << funcName + << " May need to be ported to the symbol-address table in Dispatch.h\n"; + return nullptr; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast(it->second); +} diff --git a/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.cpp b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.cpp index df579aa2cccf8..a1ddd57aba156 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.cpp @@ -27,10 +27,9 @@ #include #include -namespace Cpp { +namespace CppInternal { -using namespace Cpp::utils::platform; -using namespace Cpp::utils; +using namespace utils; using namespace llvm; DynamicLibraryManager::DynamicLibraryManager() { @@ -55,8 +54,8 @@ DynamicLibraryManager::DynamicLibraryManager() { for (const char* Var : kSysLibraryEnv) { if (const char* Env = GetEnv(Var)) { SmallVector CurPaths; - SplitPaths(Env, CurPaths, utils::kPruneNonExistent, - Cpp::utils::platform::kEnvDelim); + SplitPaths(Env, CurPaths, SplitMode::kPruneNonExistent, + platform::kEnvDelim); for (const auto& Path : CurPaths) addSearchPath(Path); } @@ -66,7 +65,7 @@ DynamicLibraryManager::DynamicLibraryManager() { addSearchPath("."); SmallVector SysPaths; - Cpp::utils::platform::GetSystemLibraryPaths(SysPaths); + platform::GetSystemLibraryPaths(SysPaths); for (const std::string& P : SysPaths) addSearchPath(P, /*IsUser*/ false); @@ -506,4 +505,4 @@ bool DynamicLibraryManager::isSharedLibrary(StringRef libFullPath, return result; } -} // end namespace Cpp +} // end namespace CppInternal diff --git a/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.h b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.h index e4adfa0f15395..167bd0c7c13c7 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.h +++ b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.h @@ -17,7 +17,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/Path.h" -namespace Cpp { +namespace CppInternal { class Dyld; class InterpreterCallbacks; @@ -219,6 +219,6 @@ class DynamicLibraryManager { static bool isSharedLibrary(llvm::StringRef libFullPath, bool* exists = nullptr); }; -} // end namespace Cpp +} // end namespace CppInternal #endif // CPPINTEROP_DYNAMIC_LIBRARY_MANAGER_H diff --git a/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp index 09d65be89b57f..37ee36c2b91ba 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp @@ -458,6 +458,8 @@ static bool MayExistInElfObjectFile(llvm::object::ObjectFile* soFile, } // namespace +namespace CppInternal { + // This function isn't referenced outside its translation unit, but it // can't use the "static" keyword because its address is used for // GetMainExecutable (since some platforms don't support taking the @@ -466,10 +468,9 @@ static bool MayExistInElfObjectFile(llvm::object::ObjectFile* soFile, std::string GetExecutablePath() { // This just needs to be some symbol in the binary; C++ doesn't // allow taking the address of ::main however. - return Cpp::DynamicLibraryManager::getSymbolLocation(&GetExecutablePath); + return DynamicLibraryManager::getSymbolLocation(&GetExecutablePath); } -namespace Cpp { class Dyld { struct BasePathHashFunction { size_t operator()(const BasePath& item) const { @@ -508,7 +509,7 @@ class Dyld { bool m_UseBloomFilter = true; bool m_UseHashTable = true; - const Cpp::DynamicLibraryManager& m_DynamicLibraryManager; + const DynamicLibraryManager& m_DynamicLibraryManager; /// The basename of `/home/.../lib/libA.so`, /// m_BasePaths will contain `/home/.../lib/` @@ -548,7 +549,7 @@ class Dyld { void dumpDebugInfo() const; public: - Dyld(const Cpp::DynamicLibraryManager& DLM, + Dyld(const DynamicLibraryManager& DLM, PermanentlyIgnoreCallbackProto shouldIgnore, StringRef execFormat) : m_DynamicLibraryManager(DLM), m_ShouldPermanentlyIgnoreCallback(shouldIgnore), @@ -594,12 +595,14 @@ void HandleDynTab(const ELFFile* Elf, StringRef FileName, Deps.push_back(Data + Dyn.d_un.d_val); break; case ELF::DT_RPATH: - SplitPaths(Data + Dyn.d_un.d_val, RPath, utils::kAllowNonExistent, - Cpp::utils::platform::kEnvDelim, false); + SplitPaths(Data + Dyn.d_un.d_val, RPath, + utils::SplitMode::kAllowNonExistent, + utils::platform::kEnvDelim, false); break; case ELF::DT_RUNPATH: - SplitPaths(Data + Dyn.d_un.d_val, RunPath, utils::kAllowNonExistent, - Cpp::utils::platform::kEnvDelim, false); + SplitPaths(Data + Dyn.d_un.d_val, RunPath, + utils::SplitMode::kAllowNonExistent, + utils::platform::kEnvDelim, false); break; case ELF::DT_FLAGS_1: // Check if this is not a pie executable. @@ -757,8 +760,8 @@ void Dyld::ScanForLibraries(bool searchSystemLibraries /* = false*/) { } else if (Command.C.cmd == MachO::LC_RPATH) { MachO::rpath_command rpathCmd = Obj->getRpathCommand(Command); SplitPaths(Command.Ptr + rpathCmd.path, RPath, - utils::kAllowNonExistent, - Cpp::utils::platform::kEnvDelim, false); + utils::SplitMode::kAllowNonExistent, + utils::platform::kEnvDelim, false); } } } else if (BinObjF->isCOFF()) { @@ -1112,7 +1115,7 @@ bool Dyld::ShouldPermanentlyIgnore(StringRef FileName) const { #define DEBUG_TYPE "Dyld:" assert(!m_ExecutableFormat.empty() && "Failed to find the object format!"); - if (!Cpp::DynamicLibraryManager::isSharedLibrary(FileName)) + if (!DynamicLibraryManager::isSharedLibrary(FileName)) return true; // No need to check linked libraries, as this function is only invoked @@ -1381,4 +1384,4 @@ std::string DynamicLibraryManager::getSymbolLocation(void* func) { #endif } -} // namespace Cpp +} // namespace CppInternal diff --git a/interpreter/CppInterOp/lib/CppInterOp/Paths.cpp b/interpreter/CppInterOp/lib/CppInterOp/Paths.cpp index 424d5f90955a9..49f4c7d3821da 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/Paths.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/Paths.cpp @@ -23,7 +23,7 @@ #include #endif -namespace Cpp { +namespace CppInternal { namespace utils { namespace platform { @@ -158,7 +158,8 @@ void CopyIncludePaths(const clang::HeaderSearchOptions& Opts, /// User specified include entries. for (unsigned i = 0, e = Opts.UserEntries.size(); i != e; ++i) { const HeaderSearchOptions::Entry& E = Opts.UserEntries[i]; - if (E.IsFramework && E.Group != frontend::Angled) + if (E.IsFramework && E.Group != frontend::Angled && + E.Group != frontend::System) llvm::report_fatal_error("Invalid option set!"); switch (E.Group) { case frontend::After: @@ -361,7 +362,7 @@ bool SplitPaths(llvm::StringRef PathStr, void AddIncludePaths( llvm::StringRef PathStr, clang::HeaderSearchOptions& HOpts, - const char* Delim /* = Cpp::utils::platform::kEnvDelim */) { + const char* Delim /* = CppInternal::utils::platform::kEnvDelim */) { #define DEBUG_TYPE "AddIncludePaths" llvm::SmallVector Paths; @@ -399,4 +400,4 @@ void AddIncludePaths( } } // namespace utils -} // namespace Cpp +} // namespace CppInternal diff --git a/interpreter/CppInterOp/lib/CppInterOp/Paths.h b/interpreter/CppInterOp/lib/CppInterOp/Paths.h index 0622fc9da3d0d..46948ae87632f 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/Paths.h +++ b/interpreter/CppInterOp/lib/CppInterOp/Paths.h @@ -24,7 +24,7 @@ class HeaderSearchOptions; class FileManager; } // namespace clang -namespace Cpp { +namespace CppInternal { namespace utils { namespace platform { @@ -80,7 +80,7 @@ enum SplitMode { bool SplitPaths(llvm::StringRef PathStr, llvm::SmallVectorImpl& Paths, SplitMode Mode = kPruneNonExistent, - llvm::StringRef Delim = Cpp::utils::platform::kEnvDelim, + llvm::StringRef Delim = CppInternal::utils::platform::kEnvDelim, bool Verbose = false); ///\brief Adds multiple include paths separated by a delimiter into the @@ -92,8 +92,9 @@ bool SplitPaths(llvm::StringRef PathStr, ///\param[in] Opts - HeaderSearchOptions to add paths into ///\param[in] Delim - Delimiter to separate paths or NULL if a single path /// -void AddIncludePaths(llvm::StringRef PathStr, clang::HeaderSearchOptions& HOpts, - const char* Delim = Cpp::utils::platform::kEnvDelim); +void AddIncludePaths( + llvm::StringRef PathStr, clang::HeaderSearchOptions& HOpts, + const char* Delim = CppInternal::utils::platform::kEnvDelim); ///\brief Write to cling::errs that directory does not exist in a format /// matching what 'clang -v' would do @@ -117,6 +118,6 @@ void CopyIncludePaths(const clang::HeaderSearchOptions& Opts, bool WithSystem, bool WithFlags); } // namespace utils -} // namespace Cpp +} // namespace CppInternal #endif // CPPINTEROP_UTILS_PATHS_H diff --git a/interpreter/CppInterOp/lib/CppInterOp/exports.ld b/interpreter/CppInterOp/lib/CppInterOp/exports.ld index e22490f2a00e9..67b50dae463f9 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/exports.ld +++ b/interpreter/CppInterOp/lib/CppInterOp/exports.ld @@ -51,4 +51,6 @@ -Wl,--export=_ZNK5clang12FunctionDecl12getNumParamsEv -Wl,--export=__clang_Interpreter_SetValueNoAlloc -Wl,--export=__clang_Interpreter_SetValueWithAlloc --Wl,--export=_ZN4llvm15SmallVectorBaseIjE8set_sizeEm \ No newline at end of file +-Wl,--export=_ZN4llvm15SmallVectorBaseIjE8set_sizeEm +-Wl,--export=_ZN5clang11Interpreter6createENSt3__210unique_ptrINS_16CompilerInstanceENS1_14default_deleteIS3_EEEENS2_IN4llvm3orc12LLJITBuilderENS4_IS9_EEEE +-Wl,--export=_ZNK5clang13CXXRecordDecl19isInjectedClassNameEv diff --git a/interpreter/CppInterOp/patches/llvm/clang20-1-out-of-process.patch b/interpreter/CppInterOp/patches/llvm/clang20-1-out-of-process.patch new file mode 100644 index 0000000000000..198afb99a0530 --- /dev/null +++ b/interpreter/CppInterOp/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/interpreter/CppInterOp/patches/llvm/emscripten-clang21-1-shift-temporary-files-to-tmp-dir.patch b/interpreter/CppInterOp/patches/llvm/emscripten-clang21-1-shift-temporary-files-to-tmp-dir.patch new file mode 100644 index 0000000000000..32ac45c13f3ae --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/emscripten-clang21-1-shift-temporary-files-to-tmp-dir.patch @@ -0,0 +1,15 @@ +diff --git a/clang/lib/Interpreter/Wasm.cpp b/clang/lib/Interpreter/Wasm.cpp +index aa10b160ccf8..184867e2b55f 100644 +--- a/clang/lib/Interpreter/Wasm.cpp ++++ b/clang/lib/Interpreter/Wasm.cpp +@@ -76,8 +76,8 @@ llvm::Error WasmIncrementalExecutor::addModule(PartialTranslationUnit &PTU) { + llvm::TargetMachine *TargetMachine = Target->createTargetMachine( + PTU.TheModule->getTargetTriple(), "", "", TO, llvm::Reloc::Model::PIC_); + PTU.TheModule->setDataLayout(TargetMachine->createDataLayout()); +- std::string ObjectFileName = PTU.TheModule->getName().str() + ".o"; +- std::string BinaryFileName = PTU.TheModule->getName().str() + ".wasm"; ++ std::string ObjectFileName = "/tmp/" + PTU.TheModule->getName().str() + ".o"; ++ std::string BinaryFileName = "/tmp/" + PTU.TheModule->getName().str() + ".wasm"; + + std::error_code Error; + llvm::raw_fd_ostream ObjectFileOutput(llvm::StringRef(ObjectFileName), Error); diff --git a/interpreter/CppInterOp/patches/llvm/emscripten-clang21-2-enable_exception_handling.patch b/interpreter/CppInterOp/patches/llvm/emscripten-clang21-2-enable_exception_handling.patch new file mode 100644 index 0000000000000..9a57cec72ee3d --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/emscripten-clang21-2-enable_exception_handling.patch @@ -0,0 +1,100 @@ +diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h +index 0ae490f0e807..7a1b5875a590 100644 +--- a/clang/include/clang/Frontend/CompilerInstance.h ++++ b/clang/include/clang/Frontend/CompilerInstance.h +@@ -249,6 +249,11 @@ public: + /// Load the list of plugins requested in the \c FrontendOptions. + void LoadRequestedPlugins(); + ++ /// Parse and apply LLVM command line arguments from FrontendOptions. ++ /// This processes the LLVMArgs option that comes from -mllvm flags. ++ /// This should be called after plugins are loaded and before ExecuteAction. ++ void parseLLVMArgs(); ++ + /// @} + /// @name Compiler Invocation and Options + /// @{ +diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp +index 6f8cc01eeed8..3336be5b60ed 100644 +--- a/clang/lib/Frontend/CompilerInstance.cpp ++++ b/clang/lib/Frontend/CompilerInstance.cpp +@@ -1138,6 +1138,18 @@ void CompilerInstance::LoadRequestedPlugins() { + } + } + ++void CompilerInstance::parseLLVMArgs() { ++ if (!getFrontendOpts().LLVMArgs.empty()) { ++ unsigned NumArgs = getFrontendOpts().LLVMArgs.size(); ++ auto Args = std::make_unique(NumArgs + 2); ++ Args[0] = "clang (LLVM option parsing)"; ++ for (unsigned i = 0; i != NumArgs; ++i) ++ Args[i + 1] = getFrontendOpts().LLVMArgs[i].c_str(); ++ Args[NumArgs + 1] = nullptr; ++ llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); ++ } ++} ++ + /// Determine the appropriate source input kind based on language + /// options. + static Language getLanguageFromOptions(const LangOptions &LangOpts) { +diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +index 443eb4f1a29b..1d69654ce666 100644 +--- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp ++++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +@@ -231,15 +231,7 @@ bool ExecuteCompilerInvocation(CompilerInstance *Clang) { + // + // FIXME: Remove this, one day. + // This should happen AFTER plugins have been loaded! +- if (!Clang->getFrontendOpts().LLVMArgs.empty()) { +- unsigned NumArgs = Clang->getFrontendOpts().LLVMArgs.size(); +- auto Args = std::make_unique(NumArgs + 2); +- Args[0] = "clang (LLVM option parsing)"; +- for (unsigned i = 0; i != NumArgs; ++i) +- Args[i + 1] = Clang->getFrontendOpts().LLVMArgs[i].c_str(); +- Args[NumArgs + 1] = nullptr; +- llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); +- } ++ Clang->parseLLVMArgs(); + + #if CLANG_ENABLE_STATIC_ANALYZER + // These should happen AFTER plugins have been loaded! +diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp +index ed3bae59a144..98cd255cce5d 100644 +--- a/clang/lib/Interpreter/Interpreter.cpp ++++ b/clang/lib/Interpreter/Interpreter.cpp +@@ -374,6 +374,9 @@ Interpreter::Interpreter(std::unique_ptr Instance, + auto LLVMCtx = std::make_unique(); + TSCtx = std::make_unique(std::move(LLVMCtx)); + ++ // Honor -mllvm options (this was missing in clang-repl path) ++ CI->parseLLVMArgs(); ++ + Act = TSCtx->withContextDo([&](llvm::LLVMContext *Ctx) { + return std::make_unique(*CI, *Ctx, ErrOut, *this, + std::move(Consumer)); +@@ -601,6 +604,12 @@ Interpreter::Parse(llvm::StringRef Code) { + return std::move(Err); + } + ++ // Re-apply stored -mllvm options; wasm builds reset LLVM opts in wasm-ld. ++ // llvm::Triple Triple(getCompilerInstance()->getTargetOpts().Triple); ++ // if (Triple.isWasm()) { ++ // getCompilerInstance()->parseLLVMArgs(); ++ // } ++ + // Tell the interpreter sliently ignore unused expressions since value + // printing could cause it. + getCompilerInstance()->getDiagnostics().setSeverity( +diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp +index 1c5d21c06f5a..2f39d77cca60 100644 +--- a/lld/wasm/Driver.cpp ++++ b/lld/wasm/Driver.cpp +@@ -1307,7 +1307,7 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { + v.push_back("wasm-ld (LLVM option parsing)"); + for (auto *arg : args.filtered(OPT_mllvm)) + v.push_back(arg->getValue()); +- cl::ResetAllOptionOccurrences(); ++ // cl::ResetAllOptionOccurrences(); + cl::ParseCommandLineOptions(v.size(), v.data()); + + readConfigs(args); diff --git a/interpreter/CppInterOp/patches/llvm/emscripten-clang21-3-webassembly_target_machine_reordering.patch b/interpreter/CppInterOp/patches/llvm/emscripten-clang21-3-webassembly_target_machine_reordering.patch new file mode 100644 index 0000000000000..100e07f349d40 --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/emscripten-clang21-3-webassembly_target_machine_reordering.patch @@ -0,0 +1,14 @@ +diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +index 6827ee652794..3ad7ec5d32b6 100644 +--- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp ++++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp +@@ -227,8 +227,8 @@ WebAssemblyTargetMachine::WebAssemblyTargetMachine( + this->Options.DataSections = true; + this->Options.UniqueSectionNames = true; + +- initAsmInfo(); + basicCheckForEHAndSjLj(this); ++ initAsmInfo(); + // Note that we don't use setRequiresStructuredCFG(true). It disables + // optimizations than we're ok with, and want, such as critical edge + // splitting and tail merging. diff --git a/interpreter/CppInterOp/unittests/CMakeLists.txt b/interpreter/CppInterOp/unittests/CMakeLists.txt index 3d13205b52aa6..295f1b0a9f182 100644 --- a/interpreter/CppInterOp/unittests/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CMakeLists.txt @@ -34,30 +34,58 @@ add_dependencies(CppInterOpUnitTests clangCppInterOp) set (TIMEOUT_VALUE 2400) function(add_cppinterop_unittest name) - add_executable(${name} EXCLUDE_FROM_ALL ${ARGN}) + cmake_parse_arguments(ARG + "ENABLE_DISPATCH" + "" + "" + ${ARGN} + ) + add_executable(${name} EXCLUDE_FROM_ALL ${ARG_UNPARSED_ARGUMENTS}) add_dependencies(CppInterOpUnitTests ${name}) target_include_directories(${name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${GTEST_INCLUDE_DIR}) set_property(TARGET ${name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) -if(WIN32) - target_link_libraries(${name} PUBLIC ${ARG_LIBRARIES} ${gtest_libs}) - set_property(TARGET ${name} APPEND_STRING PROPERTY LINK_FLAGS "${MSVC_EXPORTS}") -else() - target_link_libraries(${name} PRIVATE ${ARG_LIBRARIES} ${gtest_libs} ${link_pthreads_lib}) -endif() -if(EMSCRIPTEN) - # Without this cmake will try and get node to run the html file. - # This guarantees that it runs the js file, and uses emsdks node. - add_test(NAME cppinterop-${name} COMMAND $ENV{EMSDK_NODE} ${name}.js) -else() - add_test(NAME cppinterop-${name} COMMAND ${name}) -endif() + export_executable_symbols(${name}) + + if(WIN32) + target_link_libraries(${name} PUBLIC ${ARG_LIBRARIES} ${gtest_libs}) + set_property(TARGET ${name} APPEND_STRING PROPERTY LINK_FLAGS "${MSVC_EXPORTS}") + else() + target_link_libraries(${name} PRIVATE ${ARG_LIBRARIES} ${gtest_libs} ${link_pthreads_lib}) + endif() + target_link_libraries(${name} PRIVATE clangCppInterOp) + if(EMSCRIPTEN) + if (BUILD_SHARED_LIBS) + target_compile_definitions(${name} PRIVATE "EMSCRIPTEN_SHARED_LIBRARY") + else() + target_compile_definitions(${name} PRIVATE "EMSCRIPTEN_STATIC_LIBRARY") + endif() + # Without this cmake will try and get node to run the html file. + # This guarantees that it runs the js file, and uses emsdks node. + add_test(NAME cppinterop-${name} COMMAND $ENV{EMSDK_NODE} ${name}.js) + else() + add_test(NAME cppinterop-${name} COMMAND ${name}) + set(EXTRA_PATH_TEST_BINARIES /bin/$/) + endif() + set_output_directory(${name} + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${EXTRA_PATH_TEST_BINARIES} + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${EXTRA_PATH_TEST_BINARIES} + ) + set_tests_properties(cppinterop-${name} PROPERTIES - TIMEOUT "${TIMEOUT_VALUE}" - ENVIRONMENT "CPLUS_INCLUDE_PATH=${CMAKE_BINARY_DIR}/etc" - LABELS - DEPENDS) - # FIXME: Just call gtest_add_tests this function is available. - #gtest_add_tests(${name} "${Arguments}" AUTO) + TIMEOUT "${TIMEOUT_VALUE}" + ENVIRONMENT "CPLUS_INCLUDE_PATH=${CMAKE_BINARY_DIR}/etc" + LABELS + DEPENDS) + # Auto-generate the dispatch version if shared libs are built by recursively calling + # add_cppinterop_unittest with the necessary compile definitions + # FIXME: Enable for WASM builds + if (NOT EMSCRIPTEN AND ARG_ENABLE_DISPATCH AND BUILD_SHARED_LIBS) + add_cppinterop_unittest(${name}Dispatch ${ARG_UNPARSED_ARGUMENTS} DispatchTest.cpp) + target_compile_definitions(${name}Dispatch PRIVATE + ENABLE_DISPATCH_TESTS + CPPINTEROP_LIB_PATH="${CMAKE_BINARY_DIR}/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" + ) + endif() endfunction() add_subdirectory(CppInterOp) diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt index 4b4b43bdd2936..4f0a4f98e2061 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt @@ -8,10 +8,11 @@ if (EMSCRIPTEN) 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) - set(EXTRA_PATH_TEST_BINARIES /CppInterOpTests/unittests/bin/$/) endif() add_cppinterop_unittest(CppInterOpTests + ENABLE_DISPATCH + CommentReflectionTest.cpp EnumReflectionTest.cpp FunctionReflectionTest.cpp InterpreterTest.cpp @@ -23,6 +24,16 @@ add_cppinterop_unittest(CppInterOpTests ${EXTRA_TEST_SOURCE_FILES} ) +if(NOT WIN32) + set_source_files_properties(VariableReflectionTest.cpp PROPERTIES COMPILE_FLAGS + "-Wno-pedantic" +) +endif () + +set_source_files_properties(InterpreterTest.cpp PROPERTIES COMPILE_DEFINITIONS + "LLVM_BINARY_DIR=\"${LLVM_BINARY_DIR}\"" +) + if(EMSCRIPTEN) string(REPLACE "@" "@@" ESCAPED_SYSROOT_PATH "${SYSROOT_PATH}") # Explanation of Emscripten-specific link flags for CppInterOpTests: @@ -49,8 +60,11 @@ if(EMSCRIPTEN) # --emrun # Makes it so that we run the html file created by this target, that we can capture the standard output # and output to the terminal + target_compile_options(CppInterOpTests + PUBLIC "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" + ) target_link_options(CppInterOpTests - PUBLIC "SHELL: -fexceptions" + PUBLIC "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" PUBLIC "SHELL: -s MAIN_MODULE=1" PUBLIC "SHELL: -s WASM_BIGINT" PUBLIC "SHELL: -s ALLOW_MEMORY_GROWTH=1" @@ -61,46 +75,27 @@ if(EMSCRIPTEN) ) endif() -target_link_libraries(CppInterOpTests - PRIVATE - clangCppInterOp -) - -set_output_directory(CppInterOpTests - BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${EXTRA_PATH_TEST_BINARIES} - LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${EXTRA_PATH_TEST_BINARIES} -) - -if(NOT WIN32) - set_source_files_properties(VariableReflectionTest.cpp PROPERTIES COMPILE_FLAGS - "-Wno-pedantic" -) -endif() - -set_source_files_properties(InterpreterTest.cpp PROPERTIES COMPILE_DEFINITIONS - "LLVM_BINARY_DIR=\"${LLVM_BINARY_DIR}\"" -) -export_executable_symbols(CppInterOpTests) - unset(LLVM_LINK_COMPONENTS) if (NOT EMSCRIPTEN) set(EXTRA_TEST_SOURCE_FILES "") - set(EXTRA_PATH_TEST_BINARIES /TestSharedLib/unittests/bin/$/) endif() -add_cppinterop_unittest(DynamicLibraryManagerTests DynamicLibraryManagerTest.cpp ${EXTRA_TEST_SOURCE_FILES}) - -target_link_libraries(DynamicLibraryManagerTests - PRIVATE - clangCppInterOp +add_cppinterop_unittest(DynamicLibraryManagerTests + DynamicLibraryManagerTest.cpp + Utils.cpp + ${EXTRA_TEST_SOURCE_FILES} ) if(EMSCRIPTEN) - set(TEST_SHARED_LIBRARY_PATH "${CMAKE_CURRENT_BINARY_DIR}/TestSharedLib/unittests/bin/$/") + set(TEST_SHARED_LIBRARY_PATH "${CMAKE_CURRENT_BINARY_DIR}/bin/$/") string(REPLACE "@" "@@" ESCAPED_TEST_SHARED_LIBRARY_PATH "${TEST_SHARED_LIBRARY_PATH}") # Check explanation of Emscripten-specific link flags for CppInterOpTests above for DynamicLibraryManagerTests as well. + target_compile_options(DynamicLibraryManagerTests + PUBLIC "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" + ) target_link_options(DynamicLibraryManagerTests + PUBLIC "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" PUBLIC "SHELL: -s MAIN_MODULE=1" PUBLIC "SHELL: -s WASM_BIGINT" PUBLIC "SHELL: -s ALLOW_MEMORY_GROWTH=1" @@ -112,22 +107,8 @@ if(EMSCRIPTEN) ) endif() -if (EMSCRIPTEN) - if (BUILD_SHARED_LIBS) - target_compile_definitions(CppInterOpTests PRIVATE "EMSCRIPTEN_SHARED_LIBRARY") - target_compile_definitions(DynamicLibraryManagerTests PRIVATE "EMSCRIPTEN_SHARED_LIBRARY") - else() - target_compile_definitions(CppInterOpTests PRIVATE "EMSCRIPTEN_STATIC_LIBRARY") - target_compile_definitions(DynamicLibraryManagerTests PRIVATE "EMSCRIPTEN_STATIC_LIBRARY") - endif() -endif() - -set_output_directory(DynamicLibraryManagerTests - BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${EXTRA_PATH_TEST_BINARIES} - LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${EXTRA_PATH_TEST_BINARIES} -) - add_dependencies(DynamicLibraryManagerTests TestSharedLib) #export_executable_symbols_for_plugins(TestSharedLib) add_subdirectory(TestSharedLib) + diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp index 45b41c94d55c6..fe0e904a1b376 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp @@ -6,6 +6,8 @@ #include "gtest/gtest.h" +#include + using namespace TestUtils; static bool HasCudaSDK() { @@ -17,7 +19,7 @@ static bool HasCudaSDK() { if (!Cpp::CreateInterpreter({}, {"--cuda"})) return false; return Cpp::Declare("__global__ void test_func() {}" - "test_func<<<1,1>>>();") == 0; + "test_func<<<1,1>>>();" DFLT_FALSE) == 0; }; static bool hasCuda = supportsCudaSDK(); return hasCuda; @@ -35,9 +37,9 @@ static bool HasCudaRuntime() { if (!Cpp::CreateInterpreter({}, {"--cuda"})) return false; if (Cpp::Declare("__global__ void test_func() {}" - "test_func<<<1,1>>>();")) + "test_func<<<1,1>>>();" DFLT_FALSE)) return false; - intptr_t result = Cpp::Evaluate("(bool)cudaGetLastError()"); + intptr_t result = Cpp::Evaluate("(bool)cudaGetLastError()" DFLT_NULLPTR); return !(bool)result; }; static bool hasCuda = supportsCuda(); @@ -65,7 +67,7 @@ TEST(CUDATest, CUDAH) { GTEST_SKIP() << "Skipping CUDA tests as CUDA SDK not found"; Cpp::CreateInterpreter({}, {"--cuda"}); - bool success = !Cpp::Declare("#include "); + bool success = !Cpp::Declare("#include " DFLT_FALSE); EXPECT_TRUE(success); } diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CommentReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/CommentReflectionTest.cpp new file mode 100644 index 0000000000000..7550667ed7d16 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/CommentReflectionTest.cpp @@ -0,0 +1,84 @@ +#include "Utils.h" + +#include "CppInterOp/CppInterOp.h" +#include "clang-c/CXCppInterOp.h" +#include "clang-c/CXString.h" +#include "gtest/gtest.h" + +#include +#include + +using namespace TestUtils; +using namespace llvm; +using namespace clang; + +TYPED_TEST(CPPINTEROP_TEST_MODE, CommentReflection_DoxygenBlockAndLine) { + std::vector Decls; + std::string code = R"( + /** Adds two integers. + * \param x first + * \param y second + * \return sum + */ + int add(int x, int y) { return x + y; } + /// A simple struct. + struct S { int a; }; + )"; + + GetAllTopLevelDecls(code, Decls); + ASSERT_GE(Decls.size(), 2U); + + const std::string addDoc = Cpp::GetDoxygenComment(Decls[0], /*strip=*/true); + EXPECT_NE(addDoc.find("Adds two integers."), std::string::npos); + EXPECT_NE(addDoc.find("\\param x first"), std::string::npos); + EXPECT_NE(addDoc.find("\\return sum"), std::string::npos); + + const std::string addDocRaw = + Cpp::GetDoxygenComment(Decls[0], /*strip=*/false); + EXPECT_NE(addDocRaw.find("/**"), std::string::npos); + + const std::string sDoc = Cpp::GetDoxygenComment(Decls[1], /*strip=*/true); + EXPECT_EQ(sDoc, "A simple struct."); + + auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); + CXScope S = make_scope(Decls[1], I); + CXString Doc = clang_getDoxygenComment(S, /*strip_comment_markers=*/true); + EXPECT_STREQ(get_c_string(Doc), sDoc.c_str()); + dispose_string(Doc); + clang_Interpreter_takeInterpreterAsPtr(I); + clang_Interpreter_dispose(I); +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, CommentReflection_DoxygenNullScope) { + CXScope NullS{}; + CXString Doc = clang_getDoxygenComment(NullS, /*strip_comment_markers=*/true); + EXPECT_STREQ(get_c_string(Doc), ""); + dispose_string(Doc); +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, CommentReflection_DoxygenNullPtr) { + EXPECT_EQ(Cpp::GetDoxygenComment(nullptr, /*strip=*/true), ""); +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, CommentReflection_DoxygenNoComment) { + std::vector Decls; + std::string code = R"( + int foo() { return 42; } + )"; + + GetAllTopLevelDecls(code, Decls); + ASSERT_GE(Decls.size(), 1U); + EXPECT_EQ(Cpp::GetDoxygenComment(Decls[0], /*strip=*/true), ""); +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, CommentReflection_DoxygenNonDoxygenComment) { + std::vector Decls; + std::string code = R"( + // A regular non-doxygen comment. + int bar() { return 7; } + )"; + + GetAllTopLevelDecls(code, Decls); + ASSERT_GE(Decls.size(), 1U); + EXPECT_EQ(Cpp::GetDoxygenComment(Decls[0], /*strip=*/true), ""); +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/DispatchTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/DispatchTest.cpp new file mode 100644 index 0000000000000..2468bdfef6dc4 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/DispatchTest.cpp @@ -0,0 +1,71 @@ +#include "Utils.h" + +#include "CppInterOp/Dispatch.h" + +#include "clang/AST/Decl.h" +#include "clang/AST/GlobalDecl.h" + +#include "gtest/gtest.h" + +#include + +using namespace TestUtils; +using namespace llvm; +using namespace clang; + +// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + +TEST(CPPINTEROP_TEST_MODE, DispatchAPI_CppGetProcAddress_Basic) { + auto IsClassFn = + reinterpret_cast(CppGetProcAddress("IsClass")); + EXPECT_NE(IsClassFn, nullptr) << "Failed to obtain API function pointer"; + std::vector Decls; + GetAllTopLevelDecls("namespace N {} class C{}; int I;", Decls); + EXPECT_FALSE(IsClassFn(Decls[0])); + EXPECT_TRUE(IsClassFn(Decls[1])); + EXPECT_FALSE(IsClassFn(Decls[2])); +} + +TEST(CPPINTEROP_TEST_MODE, DispatchAPI_CppGetProcAddress_Unimplemented) { + testing::internal::CaptureStderr(); + void (*func_ptr)() = CppGetProcAddress("NonExistentFunction"); + std::string output = testing::internal::GetCapturedStderr(); + EXPECT_EQ(output, + "[CppInterOp Dispatch] Failed to find API: NonExistentFunction May " + "need to be ported to the symbol-address table in Dispatch.h\n"); + EXPECT_EQ(func_ptr, nullptr); +} + +TEST(CPPINTEROP_TEST_MODE, DispatchAPI_CppGetProcAddress_Advanced) { + std::string code = R"( + int add(int x, int y) { return x + y; } + int add(double x, double y) { return x + y; } + )"; + + std::vector Decls; + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Decls.size(), 2); + + auto Add_int = clang::GlobalDecl(static_cast(Decls[0])); + auto Add_double = clang::GlobalDecl(static_cast(Decls[1])); + + std::string mangled_add_int; + std::string mangled_add_double; + compat::maybeMangleDeclName(Add_int, mangled_add_int); + compat::maybeMangleDeclName(Add_double, mangled_add_double); + + auto DemangleFn = + reinterpret_cast(CppGetProcAddress("Demangle")); + auto GetQualifiedCompleteNameFn = + reinterpret_cast( + CppGetProcAddress("GetQualifiedCompleteName")); + + std::string demangled_add_int = DemangleFn(mangled_add_int); + std::string demangled_add_double = DemangleFn(mangled_add_double); + + EXPECT_NE(demangled_add_int.find(GetQualifiedCompleteNameFn(Decls[0])), + std::string::npos); + EXPECT_NE(demangled_add_double.find(GetQualifiedCompleteNameFn(Decls[1])), + std::string::npos); +} +// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) diff --git a/interpreter/CppInterOp/unittests/CppInterOp/DynamicLibraryManagerTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/DynamicLibraryManagerTest.cpp index 4925af32984f3..ab3ca812581ef 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/DynamicLibraryManagerTest.cpp +++ b/interpreter/CppInterOp/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(CPPINTEROP_TEST_MODE, DynamicLibraryManager_Sanity) { #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(CPPINTEROP_TEST_MODE, DynamicLibraryManager_BasicSymbolLookup) { #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/interpreter/CppInterOp/unittests/CppInterOp/EnumReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/EnumReflectionTest.cpp index f4b3d47de6c89..19b58f3e7a15c 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/EnumReflectionTest.cpp +++ b/interpreter/CppInterOp/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(CPPINTEROP_TEST_MODE, EnumReflection_IsEnumType) { 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(CPPINTEROP_TEST_MODE, EnumReflection_GetIntegerTypeFromEnumScope) { 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(CPPINTEROP_TEST_MODE, EnumReflection_GetIntegerTypeFromEnumType) { 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(CPPINTEROP_TEST_MODE, EnumReflection_GetEnumConstants) { 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(CPPINTEROP_TEST_MODE, EnumReflection_GetEnumConstantType) { 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(CPPINTEROP_TEST_MODE, EnumReflection_GetEnumConstantValue) { 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(CPPINTEROP_TEST_MODE, EnumReflection_GetEnums) { 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); @@ -346,8 +302,8 @@ TEST(EnumReflectionTest, GetEnums) { Cpp::TCppScope_t myClass_scope = Cpp::GetScope("myClass", 0); Cpp::TCppScope_t unsupported_scope = Cpp::GetScope("myVariable", 0); - Cpp::GetEnums(globalscope,enumNames1); - Cpp::GetEnums(Animals_scope,enumNames2); + Cpp::GetEnums(globalscope, enumNames1); + Cpp::GetEnums(Animals_scope, enumNames2); Cpp::GetEnums(myClass_scope, enumNames3); Cpp::GetEnums(unsupported_scope, enumNames4); diff --git a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp index b056c77cef048..1b717064b0bc0 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -14,12 +14,18 @@ #include "gtest/gtest.h" #include +#include using namespace TestUtils; using namespace llvm; using namespace clang; -TEST(FunctionReflectionTest, GetClassMethods) { +// Reusable empty template args vector. In the dispatch mode, passing an empty +// initializer list {} does not work since the compiler cannot deduce the type +// for a function pointer +static const std::vector empty_templ_args = {}; + +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetClassMethods) { std::vector Decls; std::string code = R"( class A; @@ -171,7 +177,8 @@ TEST(FunctionReflectionTest, GetClassMethods) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, ConstructorInGetClassMethods) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_ConstructorInGetClassMethods) { std::vector Decls; std::string code = R"( struct S { @@ -195,7 +202,7 @@ TEST(FunctionReflectionTest, ConstructorInGetClassMethods) { EXPECT_TRUE(has_constructor(Decls[0])); } -TEST(FunctionReflectionTest, HasDefaultConstructor) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_HasDefaultConstructor) { std::vector Decls; std::string code = R"( class A { @@ -236,7 +243,7 @@ TEST(FunctionReflectionTest, HasDefaultConstructor) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, GetDestructor) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetDestructor) { std::vector Decls; std::string code = R"( class A { @@ -272,7 +279,7 @@ TEST(FunctionReflectionTest, GetDestructor) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, GetFunctionsUsingName) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionsUsingName) { std::vector Decls; std::string code = R"( class A { @@ -316,7 +323,7 @@ TEST(FunctionReflectionTest, GetFunctionsUsingName) { EXPECT_EQ(get_number_of_funcs_using_name(Decls[2], ""), 0); } -TEST(FunctionReflectionTest, GetClassDecls) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetClassDecls) { std::vector Decls, SubDecls; std::string code = R"( class MyTemplatedMethodClass { @@ -353,7 +360,7 @@ TEST(FunctionReflectionTest, GetClassDecls) { EXPECT_EQ(Cpp::GetName(methods[3]), Cpp::GetName(SubDecls[8])); } -TEST(FunctionReflectionTest, GetFunctionTemplatedDecls) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionTemplatedDecls) { std::vector Decls, SubDecls; std::string code = R"( class MyTemplatedMethodClass { @@ -390,7 +397,7 @@ TEST(FunctionReflectionTest, GetFunctionTemplatedDecls) { EXPECT_EQ(Cpp::GetName(template_methods[3]), Cpp::GetName(SubDecls[6])); } -TEST(FunctionReflectionTest, GetFunctionReturnType) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionReturnType) { std::vector Decls, SubDecls, TemplateSubDecls; std::string code = R"( namespace N { class C {}; } @@ -483,11 +490,12 @@ TEST(FunctionReflectionTest, GetFunctionReturnType) { std::vector args2 = {C.DoubleTy.getAsOpaquePtr()}; EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Cpp::GetNamed( - "func", Cpp::InstantiateTemplate(Decls[15], args2.data(), 1)))), + "func", Cpp::InstantiateTemplate(Decls[15], args2.data(), + 1 DFLT_FALSE)))), "double"); } -TEST(FunctionReflectionTest, GetFunctionNumArgs) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionNumArgs) { std::vector Decls, TemplateSubDecls; std::string code = R"( void f1() {} @@ -526,7 +534,7 @@ TEST(FunctionReflectionTest, GetFunctionNumArgs) { EXPECT_EQ(Cpp::GetFunctionNumArgs(TemplateSubDecls[3]), 3); } -TEST(FunctionReflectionTest, GetFunctionRequiredArgs) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionRequiredArgs) { std::vector Decls, TemplateSubDecls; std::string code = R"( void f1() {} @@ -561,7 +569,7 @@ TEST(FunctionReflectionTest, GetFunctionRequiredArgs) { EXPECT_EQ(Cpp::GetFunctionRequiredArgs(TemplateSubDecls[3]), 2); } -TEST(FunctionReflectionTest, GetFunctionArgType) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgType) { std::vector Decls; std::string code = R"( void f1(int i, double d, long l, char ch) {} @@ -581,7 +589,7 @@ TEST(FunctionReflectionTest, GetFunctionArgType) { EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[2], 0)), "NULL TYPE"); } -TEST(FunctionReflectionTest, GetFunctionSignature) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionSignature) { std::vector Decls; std::string code = R"( class C { @@ -625,7 +633,7 @@ TEST(FunctionReflectionTest, GetFunctionSignature) { EXPECT_EQ(Cpp::GetFunctionSignature(nullptr), ""); } -TEST(FunctionReflectionTest, IsTemplatedFunction) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsTemplatedFunction) { std::vector Decls; std::vector SubDeclsC1; std::string code = R"( @@ -665,7 +673,7 @@ TEST(FunctionReflectionTest, IsTemplatedFunction) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, ExistsFunctionTemplate) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ExistsFunctionTemplate) { std::vector Decls; std::string code = R"( template @@ -693,7 +701,8 @@ TEST(FunctionReflectionTest, ExistsFunctionTemplate) { clang_Interpreter_dispose(I); } -TEST(FunctionReflectionTest, InstantiateTemplateFunctionFromString) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_InstantiateTemplateFunctionFromString) { #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 +710,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 +718,8 @@ TEST(FunctionReflectionTest, InstantiateTemplateFunctionFromString) { EXPECT_TRUE(Instance1); } -TEST(FunctionReflectionTest, InstantiateFunctionTemplate) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_InstantiateFunctionTemplate) { std::vector Decls; std::string code = R"( template T TrivialFnTemplate() { return T(); } @@ -719,8 +729,9 @@ template T TrivialFnTemplate() { return T(); } ASTContext& C = Interp->getCI()->getASTContext(); std::vector args1 = {C.IntTy.getAsOpaquePtr()}; - auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(), - /*type_size*/ args1.size()); + auto Instance1 = + Cpp::InstantiateTemplate(Decls[0], args1.data(), + /*type_size*/ args1.size() DFLT_FALSE); EXPECT_TRUE(isa((Decl*)Instance1)); FunctionDecl* FD = cast((Decl*)Instance1); FunctionDecl* FnTD1 = FD->getTemplateInstantiationPattern(); @@ -729,7 +740,7 @@ template T TrivialFnTemplate() { return T(); } EXPECT_TRUE(TA1.getAsType()->isIntegerType()); } -TEST(FunctionReflectionTest, InstantiateTemplateMethod) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_InstantiateTemplateMethod) { std::vector Decls; std::string code = R"( class MyTemplatedMethodClass { @@ -747,8 +758,9 @@ TEST(FunctionReflectionTest, InstantiateTemplateMethod) { ASTContext& C = Interp->getCI()->getASTContext(); std::vector args1 = {C.IntTy.getAsOpaquePtr()}; - auto Instance1 = Cpp::InstantiateTemplate(Decls[1], args1.data(), - /*type_size*/ args1.size()); + auto Instance1 = + Cpp::InstantiateTemplate(Decls[1], args1.data(), + /*type_size*/ args1.size() DFLT_FALSE); EXPECT_TRUE(isa((Decl*)Instance1)); FunctionDecl* FD = cast((Decl*)Instance1); FunctionDecl* FnTD1 = FD->getTemplateInstantiationPattern(); @@ -757,7 +769,7 @@ TEST(FunctionReflectionTest, InstantiateTemplateMethod) { EXPECT_TRUE(TA1.getAsType()->isIntegerType()); } -TEST(FunctionReflectionTest, LookupConstructors) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_LookupConstructors) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -798,7 +810,7 @@ TEST(FunctionReflectionTest, LookupConstructors) { EXPECT_EQ(Cpp::GetFunctionSignature(ctors[3]), "MyClass::MyClass(T t)"); } -TEST(FunctionReflectionTest, GetClassTemplatedMethods) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetClassTemplatedMethods) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -856,7 +868,8 @@ TEST(FunctionReflectionTest, GetClassTemplatedMethods) { "void MyClass::templatedStaticMethod(T param)"); } -TEST(FunctionReflectionTest, GetClassTemplatedMethods_VariadicsAndOthers) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_GetClassTemplatedMethods_VariadicsAndOthers) { std::vector Decls; std::string code = R"( class MyClass { @@ -910,7 +923,8 @@ TEST(FunctionReflectionTest, GetClassTemplatedMethods_VariadicsAndOthers) { "void MyClass::staticVariadic(T t, Args ...args)"); } -TEST(FunctionReflectionTest, InstantiateVariadicFunction) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_InstantiateVariadicFunction) { std::vector Decls; std::string code = R"( class MyClass {}; @@ -927,8 +941,9 @@ TEST(FunctionReflectionTest, InstantiateVariadicFunction) { std::vector args1 = {C.DoubleTy.getAsOpaquePtr(), C.IntTy.getAsOpaquePtr()}; - auto Instance1 = Cpp::InstantiateTemplate(Decls[1], args1.data(), - /*type_size*/ args1.size()); + auto Instance1 = + Cpp::InstantiateTemplate(Decls[1], args1.data(), + /*type_size*/ args1.size() DFLT_FALSE); EXPECT_TRUE(Cpp::IsTemplatedFunction(Instance1)); EXPECT_EQ(Cpp::GetFunctionSignature(Instance1), "template<> void VariadicFn<>(double args, int args)"); @@ -973,7 +988,59 @@ TEST(FunctionReflectionTest, InstantiateVariadicFunction) { "fixedParam, MyClass args, double args)"); } -TEST(FunctionReflectionTest, BestOverloadFunctionMatch1) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_BestOverloadFunctionMatch0) { + // make sure templates are not instantiated multiple times + std::vector Decls; + std::string code = R"( + template + void Tfn(T1& t1, T2& t2) {} + )"; + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Decls.size(), 1); + + std::vector candidates; + candidates.reserve(Decls.size()); + for (auto* i : Decls) + candidates.push_back(i); + + ASTContext& C = Interp->getCI()->getASTContext(); + + std::vector args0 = { + C.getLValueReferenceType(C.DoubleTy).getAsOpaquePtr(), + C.getLValueReferenceType(C.IntTy).getAsOpaquePtr(), + }; + + std::vector explicit_args0; + std::vector explicit_args1 = { + C.DoubleTy.getAsOpaquePtr()}; + std::vector explicit_args2 = { + C.DoubleTy.getAsOpaquePtr(), + C.IntTy.getAsOpaquePtr(), + }; + + Cpp::TCppScope_t fn0 = + Cpp::BestOverloadFunctionMatch(candidates, explicit_args0, args0); + EXPECT_TRUE(fn0); + + Cpp::TCppScope_t fn = + Cpp::BestOverloadFunctionMatch(candidates, explicit_args1, args0); + EXPECT_EQ(fn, fn0); + + fn = Cpp::BestOverloadFunctionMatch(candidates, explicit_args2, args0); + EXPECT_EQ(fn, fn0); + + fn = Cpp::InstantiateTemplate(Decls[0], explicit_args1.data(), + explicit_args1.size() DFLT_FALSE); + EXPECT_EQ(fn, fn0); + + fn = Cpp::InstantiateTemplate(Decls[0], explicit_args2.data(), + explicit_args2.size() DFLT_FALSE); + EXPECT_EQ(fn, fn0); +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_BestOverloadFunctionMatch1) { std::vector Decls; std::string code = R"( class MyTemplatedMethodClass { @@ -1053,7 +1120,8 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch1) { "template<> long MyTemplatedMethodClass::get_size<1, int>(int a)"); } -TEST(FunctionReflectionTest, BestOverloadFunctionMatch2) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_BestOverloadFunctionMatch2) { std::vector Decls; std::string code = R"( template @@ -1089,12 +1157,12 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch2) { std::vector args1 = {C.IntTy.getAsOpaquePtr()}; std::vector args2 = { - Cpp::GetVariableType(Cpp::GetNamed("a"))}; + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; std::vector args3 = {C.IntTy.getAsOpaquePtr(), C.IntTy.getAsOpaquePtr()}; std::vector args4 = { - Cpp::GetVariableType(Cpp::GetNamed("a")), - Cpp::GetVariableType(Cpp::GetNamed("a"))}; + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; std::vector args5 = {C.IntTy.getAsOpaquePtr(), C.DoubleTy.getAsOpaquePtr()}; @@ -1123,7 +1191,8 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch2) { "void somefunc(int arg1, double arg2)"); } -TEST(FunctionReflectionTest, BestOverloadFunctionMatch3) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_BestOverloadFunctionMatch3) { std::vector Decls; std::string code = R"( template @@ -1161,13 +1230,14 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch3) { ASTContext& C = Interp->getCI()->getASTContext(); std::vector args1 = { - Cpp::GetVariableType(Cpp::GetNamed("a")), - Cpp::GetVariableType(Cpp::GetNamed("a"))}; + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; std::vector args2 = { - Cpp::GetVariableType(Cpp::GetNamed("a")), C.IntTy.getAsOpaquePtr()}; + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), + C.IntTy.getAsOpaquePtr()}; std::vector args3 = { - Cpp::GetVariableType(Cpp::GetNamed("a")), C.DoubleTy.getAsOpaquePtr()}; - + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), + C.DoubleTy.getAsOpaquePtr()}; std::vector explicit_args; Cpp::TCppFunction_t func1 = @@ -1178,14 +1248,14 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch3) { Cpp::BestOverloadFunctionMatch(candidates, explicit_args, args3); candidates.clear(); - Cpp::GetOperator( - Cpp::GetScopeFromType(Cpp::GetVariableType(Cpp::GetNamed("a"))), - Cpp::Operator::OP_Minus, candidates); + Cpp::GetOperator(Cpp::GetScopeFromType( + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))), + Cpp::Operator::OP_Minus, candidates DFLT_OP_ARITY); EXPECT_EQ(candidates.size(), 1); std::vector args4 = { - Cpp::GetVariableType(Cpp::GetNamed("a"))}; + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; Cpp::TCppFunction_t func4 = Cpp::BestOverloadFunctionMatch(candidates, explicit_args, args4); @@ -1201,7 +1271,8 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch3) { "template<> A A::operator-(A rhs)"); } -TEST(FunctionReflectionTest, BestOverloadFunctionMatch4) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_BestOverloadFunctionMatch4) { std::vector Decls, SubDecls; std::string code = R"( template @@ -1238,13 +1309,13 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch4) { std::vector args1 = {}; std::vector args2 = {C.IntTy.getAsOpaquePtr()}; std::vector args3 = { - Cpp::GetVariableType(Cpp::GetNamed("a"))}; + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; std::vector args4 = { - Cpp::GetVariableType(Cpp::GetNamed("a")), - Cpp::GetVariableType(Cpp::GetNamed("b"))}; + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), + Cpp::GetVariableType(Cpp::GetNamed("b" DFLT_NULLPTR))}; std::vector args5 = { - Cpp::GetVariableType(Cpp::GetNamed("a")), - Cpp::GetVariableType(Cpp::GetNamed("a"))}; + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), + Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; std::vector explicit_args1; std::vector explicit_args2 = {C.IntTy.getAsOpaquePtr(), @@ -1272,7 +1343,50 @@ TEST(FunctionReflectionTest, BestOverloadFunctionMatch4) { "template<> void B::fn(A x, A y)"); } -TEST(FunctionReflectionTest, IsPublicMethod) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + FunctionReflection_BestOverloadFunctionMatch5) { + std::vector Decls; + std::string code = R"( + template + void callme(T1 t1, T2 t2) {} + + template + void callback(F callable, Args&&... args) { + callable(args...); + } + )"; + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Decls.size(), 2); + + std::vector candidates; + candidates.push_back(Decls[1]); + + ASTContext& C = Interp->getCI()->getASTContext(); + std::vector explicit_params = { + C.DoubleTy.getAsOpaquePtr(), + C.IntTy.getAsOpaquePtr(), + }; + + Cpp::TCppScope_t callme = Cpp::InstantiateTemplate( + Decls[0], explicit_params.data(), explicit_params.size() DFLT_FALSE); + EXPECT_TRUE(callme); + + std::vector arg_types = { + Cpp::GetTypeFromScope(callme), + C.getLValueReferenceType(C.DoubleTy).getAsOpaquePtr(), + C.getLValueReferenceType(C.IntTy).getAsOpaquePtr(), + }; + + Cpp::TCppScope_t callback = + Cpp::BestOverloadFunctionMatch(candidates, empty_templ_args, arg_types); + EXPECT_TRUE(callback); + + EXPECT_EQ(Cpp::GetFunctionSignature(callback), + "template<> void callback>(void (*callable)(double, int), double &args, int &args)"); +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsPublicMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1299,7 +1413,7 @@ TEST(FunctionReflectionTest, IsPublicMethod) { EXPECT_FALSE(Cpp::IsPublicMethod(SubDecls[9])); } -TEST(FunctionReflectionTest, IsProtectedMethod) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsProtectedMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1324,7 +1438,7 @@ TEST(FunctionReflectionTest, IsProtectedMethod) { EXPECT_TRUE(Cpp::IsProtectedMethod(SubDecls[8])); } -TEST(FunctionReflectionTest, IsPrivateMethod) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsPrivateMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1349,7 +1463,7 @@ TEST(FunctionReflectionTest, IsPrivateMethod) { EXPECT_FALSE(Cpp::IsPrivateMethod(SubDecls[8])); } -TEST(FunctionReflectionTest, IsConstructor) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsConstructor) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1396,7 +1510,7 @@ TEST(FunctionReflectionTest, IsConstructor) { EXPECT_EQ(templCtorCount, 1); } -TEST(FunctionReflectionTest, IsDestructor) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsDestructor) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1421,7 +1535,7 @@ TEST(FunctionReflectionTest, IsDestructor) { EXPECT_FALSE(Cpp::IsDestructor(SubDecls[8])); } -TEST(FunctionReflectionTest, IsStaticMethod) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsStaticMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -1438,7 +1552,7 @@ TEST(FunctionReflectionTest, IsStaticMethod) { EXPECT_TRUE(Cpp::IsStaticMethod(SubDecls[2])); } -TEST(FunctionReflectionTest, GetFunctionAddress) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionAddress) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -1449,9 +1563,13 @@ 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"}; + std::vector interpreter_args = {"-include", "new", "-Xclang", "-iwithsysroot/include/compat"}; GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false, interpreter_args); @@ -1481,14 +1599,14 @@ TEST(FunctionReflectionTest, GetFunctionAddress) { ASTContext& C = Interp->getCI()->getASTContext(); std::vector argument = {C.DoubleTy.getAsOpaquePtr()}; - Cpp::TCppScope_t add1_double = - Cpp::InstantiateTemplate(funcs[0], argument.data(), argument.size()); + Cpp::TCppScope_t add1_double = Cpp::InstantiateTemplate( + funcs[0], argument.data(), argument.size() DFLT_FALSE); EXPECT_TRUE(add1_double); EXPECT_TRUE(Cpp::GetFunctionAddress(add1_double)); } -TEST(FunctionReflectionTest, IsVirtualMethod) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsVirtualMethod) { std::vector Decls, SubDecls; std::string code = R"( class A { @@ -1508,7 +1626,7 @@ TEST(FunctionReflectionTest, IsVirtualMethod) { EXPECT_FALSE(Cpp::IsVirtualMethod(Decls[0])); } -TEST(FunctionReflectionTest, JitCallAdvanced) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallAdvanced) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -1517,6 +1635,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); @@ -1540,7 +1661,7 @@ TEST(FunctionReflectionTest, JitCallAdvanced) { Ctor.Invoke(&object); EXPECT_TRUE(object) << "Failed to call the ctor."; // Building a wrapper with a typedef decl must be possible. - EXPECT_TRUE(Cpp::Destruct(object, Decls[1])); + EXPECT_TRUE(Cpp::Destruct(object, Decls[1] DFLT_TRUE DFLT_0)); // C API auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); @@ -1556,7 +1677,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(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallDebug) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -1565,6 +1686,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 { @@ -1587,7 +1711,7 @@ TEST(FunctionReflectionTest, JitCallDebug) { { JC.InvokeConstructor(/*result=*/nullptr); }, "Must pass the location of the created object!"); - void* result = Cpp::Allocate(Decls[0]); + void* result = Cpp::Allocate(Decls[0] DFLT_1); EXPECT_DEATH( { JC.InvokeConstructor(&result, 0UL); }, "Number of objects to construct should be atleast 1"); @@ -1627,8 +1751,8 @@ TEST(FunctionReflectionTest, JitCallDebug) { EXPECT_TRUE(*obj == 42); // Destructors - Cpp::TCppScope_t scope_C = Cpp::GetNamed("C"); - Cpp::TCppObject_t object_C = Cpp::Construct(scope_C); + Cpp::TCppScope_t scope_C = Cpp::GetNamed("C" DFLT_0); + Cpp::TCppObject_t object_C = Cpp::Construct(scope_C DFLT_NULLPTR DFLT_1); // Make destructor callable and pass arguments JC = Cpp::MakeFunctionCallable(SubDecls[4]); @@ -1649,7 +1773,7 @@ instantiation_in_host(); template int instantiation_in_host(); #endif -TEST(FunctionReflectionTest, GetFunctionCallWrapper) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -1658,6 +1782,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; } @@ -1688,13 +1814,13 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { Cpp::MakeFunctionCallable(Decls[0]); EXPECT_TRUE(FCI1.getKind() == Cpp::JitCall::kGenericCall); Cpp::JitCall FCI2 = - Cpp::MakeFunctionCallable(Cpp::GetNamed("f2")); + Cpp::MakeFunctionCallable(Cpp::GetNamed("f2" DFLT_NULLPTR)); EXPECT_TRUE(FCI2.getKind() == Cpp::JitCall::kGenericCall); - Cpp::JitCall FCI3 = - Cpp::MakeFunctionCallable(Cpp::GetNamed("f3", Cpp::GetNamed("NS"))); + Cpp::JitCall FCI3 = Cpp::MakeFunctionCallable( + Cpp::GetNamed("f3", Cpp::GetNamed("NS" DFLT_NULLPTR))); EXPECT_TRUE(FCI3.getKind() == Cpp::JitCall::kGenericCall); - Cpp::JitCall FCI4 = - Cpp::MakeFunctionCallable(Cpp::GetNamed("f4", Cpp::GetNamed("NS"))); + Cpp::JitCall FCI4 = Cpp::MakeFunctionCallable( + Cpp::GetNamed("f4", Cpp::GetNamed("NS" DFLT_NULLPTR))); EXPECT_TRUE(FCI4.getKind() == Cpp::JitCall::kGenericCall); int i = 9, ret1, ret3, ret4; @@ -1716,8 +1842,8 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { FCI4.Invoke(&ret4); EXPECT_EQ(ret4, 4); - Cpp::JitCall FCI5 = - Cpp::MakeFunctionCallable(Cpp::GetNamed("f5", Cpp::GetNamed("NS"))); + Cpp::JitCall FCI5 = Cpp::MakeFunctionCallable( + Cpp::GetNamed("f5", Cpp::GetNamed("NS" DFLT_NULLPTR))); EXPECT_TRUE(FCI5.getKind() == Cpp::JitCall::kGenericCall); typedef int (*int_func)(); @@ -1735,7 +1861,7 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { }; )"); - clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C"); + clang::NamedDecl* ClassC = (clang::NamedDecl*)Cpp::GetNamed("C" DFLT_NULLPTR); auto *CtorD = (clang::CXXConstructorDecl*)Cpp::GetDefaultConstructor(ClassC); auto FCI_Ctor = Cpp::MakeFunctionCallable(CtorD); @@ -1769,11 +1895,12 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { ASTContext& C = Interp->getCI()->getASTContext(); std::vector argument = {C.IntTy.getAsOpaquePtr()}; - auto Instance1 = Cpp::InstantiateTemplate(Decls1[0], argument.data(), - /*type_size*/ argument.size()); + auto Instance1 = + Cpp::InstantiateTemplate(Decls1[0], argument.data(), + /*type_size*/ argument.size() DFLT_FALSE); EXPECT_TRUE(isa((Decl*)Instance1)); auto* CTSD1 = static_cast(Instance1); - auto* Add_D = Cpp::GetNamed("Add",CTSD1); + auto* Add_D = Cpp::GetNamed("Add", CTSD1); Cpp::JitCall FCI_Add = Cpp::MakeFunctionCallable(Add_D); EXPECT_TRUE(FCI_Add.getKind() == Cpp::JitCall::kGenericCall); @@ -1790,7 +1917,7 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { } )"); - Cpp::TCppScope_t set_5 = Cpp::GetNamed("set_5"); + Cpp::TCppScope_t set_5 = Cpp::GetNamed("set_5" DFLT_NULLPTR); EXPECT_TRUE(set_5); Cpp::JitCall set_5_f = Cpp::MakeFunctionCallable(set_5); @@ -1816,7 +1943,7 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { )"); Cpp::TCppScope_t TypedefToPrivateClass = - Cpp::GetNamed("TypedefToPrivateClass"); + Cpp::GetNamed("TypedefToPrivateClass" DFLT_NULLPTR); EXPECT_TRUE(TypedefToPrivateClass); Cpp::TCppScope_t f = Cpp::GetNamed("f", TypedefToPrivateClass); @@ -1837,7 +1964,7 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { bool operator<(T t) { return true; } }; )"); - Cpp::TCppScope_t TOperator = Cpp::GetNamed("TOperator"); + Cpp::TCppScope_t TOperator = Cpp::GetNamed("TOperator" DFLT_NULLPTR); auto* TOperatorCtor = Cpp::GetDefaultConstructor(TOperator); auto FCI_TOperatorCtor = Cpp::MakeFunctionCallable(TOperatorCtor); @@ -1846,12 +1973,13 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { EXPECT_TRUE(toperator); std::vector operators; - Cpp::GetOperator(TOperator, Cpp::OP_Less, operators); + Cpp::GetOperator(TOperator, Cpp::Operator::OP_Less, operators DFLT_OP_ARITY); EXPECT_EQ(operators.size(), 1); Cpp::TCppScope_t op_templated = operators[0]; auto TAI = Cpp::TemplateArgInfo(Cpp::GetType("int")); - Cpp::TCppScope_t op = Cpp::InstantiateTemplate(op_templated, &TAI, 1); + Cpp::TCppScope_t op = + Cpp::InstantiateTemplate(op_templated, &TAI, 1 DFLT_FALSE); auto FCI_op = Cpp::MakeFunctionCallable(op); bool boolean = false; FCI_op.Invoke((void*)&boolean, {args, /*args_size=*/1}, toperator); @@ -1886,14 +2014,14 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { N1::N2::Klass1 K2; )"); - Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1")); - Cpp::TCppType_t K2 = Cpp::GetTypeFromScope(Cpp::GetNamed("K2")); + Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1" DFLT_NULLPTR)); + Cpp::TCppType_t K2 = Cpp::GetTypeFromScope(Cpp::GetNamed("K2" DFLT_NULLPTR)); operators.clear(); - Cpp::GetOperator(Cpp::GetScope("N2", Cpp::GetScope("N1")), Cpp::OP_Plus, - operators); + Cpp::GetOperator(Cpp::GetScope("N2", Cpp::GetScope("N1" DFLT_NULLPTR)), + Cpp::Operator::OP_Plus, operators DFLT_OP_ARITY); EXPECT_EQ(operators.size(), 1); Cpp::TCppFunction_t kop = - Cpp::BestOverloadFunctionMatch(operators, {}, {K1, K2}); + Cpp::BestOverloadFunctionMatch(operators, empty_templ_args, {K1, K2}); auto chrono_op_fn_callable = Cpp::MakeFunctionCallable(kop); EXPECT_EQ(chrono_op_fn_callable.getKind(), Cpp::JitCall::kGenericCall); @@ -1966,9 +2094,9 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { )"); std::vector unresolved_candidate_methods; - Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std"), + Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std" DFLT_NULLPTR), unresolved_candidate_methods); - Cpp::TCppType_t p = Cpp::GetTypeFromScope(Cpp::GetNamed("p")); + Cpp::TCppType_t p = Cpp::GetTypeFromScope(Cpp::GetNamed("p" DFLT_NULLPTR)); EXPECT_TRUE(p); Cpp::TCppScope_t fn = Cpp::BestOverloadFunctionMatch( @@ -2045,8 +2173,8 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { Cpp::TCppScope_t tuple_tuple = Cpp::BestOverloadFunctionMatch( unresolved_candidate_methods, {}, - {Cpp::GetVariableType(Cpp::GetNamed("tuple_one")), - Cpp::GetVariableType(Cpp::GetNamed("tuple_two"))}); + {Cpp::GetVariableType(Cpp::GetNamed("tuple_one" DFLT_NULLPTR)), + Cpp::GetVariableType(Cpp::GetNamed("tuple_two" DFLT_NULLPTR))}); EXPECT_TRUE(tuple_tuple); auto tuple_tuple_callable = Cpp::MakeFunctionCallable(tuple_tuple); @@ -2061,7 +2189,7 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { )"); Cpp::TCppScope_t bar = - Cpp::GetNamed("bar", Cpp::GetScope("EnumFunctionSameName")); + Cpp::GetNamed("bar", Cpp::GetScope("EnumFunctionSameName" DFLT_NULLPTR)); EXPECT_TRUE(bar); auto bar_callable = Cpp::MakeFunctionCallable(bar); @@ -2077,7 +2205,7 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { template void consume(T t) {} - )"); + )" DFLT_FALSE); unresolved_candidate_methods.clear(); Cpp::GetClassTemplatedMethods("consume", Cpp::GetGlobalScope(), @@ -2086,7 +2214,7 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { Cpp::TCppScope_t consume = Cpp::BestOverloadFunctionMatch( unresolved_candidate_methods, {}, - {Cpp::GetVariableType(Cpp::GetNamed("consumable"))}); + {Cpp::GetVariableType(Cpp::GetNamed("consumable" DFLT_NULLPTR))}); EXPECT_TRUE(consume); auto consume_callable = Cpp::MakeFunctionCallable(consume); @@ -2106,21 +2234,22 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { const Product operator*(const KlassProduct &other) const { return Product(); } }; - )"); + )" DFLT_FALSE); - Cpp::TCppScope_t KlassProduct = Cpp::GetNamed("KlassProduct"); + Cpp::TCppScope_t KlassProduct = Cpp::GetNamed("KlassProduct" DFLT_NULLPTR); EXPECT_TRUE(KlassProduct); Cpp::TCppScope_t KlassProduct_int = - Cpp::InstantiateTemplate(KlassProduct, &TAI, 1); + Cpp::InstantiateTemplate(KlassProduct, &TAI, 1 DFLT_FALSE); EXPECT_TRUE(KlassProduct_int); TAI = Cpp::TemplateArgInfo(Cpp::GetType("float")); Cpp::TCppScope_t KlassProduct_float = - Cpp::InstantiateTemplate(KlassProduct, &TAI, 1); + Cpp::InstantiateTemplate(KlassProduct, &TAI, 1 DFLT_FALSE); EXPECT_TRUE(KlassProduct_float); operators.clear(); - Cpp::GetOperator(KlassProduct_int, Cpp::OP_Star, operators); + Cpp::GetOperator(KlassProduct_int, Cpp::Operator::OP_Star, + operators DFLT_OP_ARITY); EXPECT_EQ(operators.size(), 2); op = Cpp::BestOverloadFunctionMatch( @@ -2140,43 +2269,47 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { template class TemplatedEnum {}; } - )"); + )" DFLT_FALSE); - Cpp::TCppScope_t TemplatedEnum = Cpp::GetScope("TemplatedEnum"); + Cpp::TCppScope_t TemplatedEnum = Cpp::GetScope("TemplatedEnum" DFLT_NULLPTR); EXPECT_TRUE(TemplatedEnum); - auto TAI_enum = - Cpp::TemplateArgInfo(Cpp::GetTypeFromScope(Cpp::GetNamed("MyEnum")), "1"); + auto TAI_enum = Cpp::TemplateArgInfo( + Cpp::GetTypeFromScope(Cpp::GetNamed("MyEnum" DFLT_NULLPTR)), "1"); Cpp::TCppScope_t TemplatedEnum_instantiated = - Cpp::InstantiateTemplate(TemplatedEnum, &TAI_enum, 1); + Cpp::InstantiateTemplate(TemplatedEnum, &TAI_enum, 1 DFLT_FALSE); EXPECT_TRUE(TemplatedEnum_instantiated); - Cpp::TCppObject_t obj = Cpp::Construct(TemplatedEnum_instantiated); + Cpp::TCppObject_t obj = + Cpp::Construct(TemplatedEnum_instantiated DFLT_NULLPTR DFLT_1); EXPECT_TRUE(obj); - Cpp::Destruct(obj, TemplatedEnum_instantiated); + Cpp::Destruct(obj, TemplatedEnum_instantiated DFLT_TRUE DFLT_0); obj = nullptr; Cpp::TCppScope_t MyNameSpace_TemplatedEnum = - Cpp::GetScope("TemplatedEnum", Cpp::GetScope("MyNameSpace")); + Cpp::GetScope("TemplatedEnum", Cpp::GetScope("MyNameSpace" DFLT_NULLPTR)); EXPECT_TRUE(TemplatedEnum); - TAI_enum = Cpp::TemplateArgInfo(Cpp::GetTypeFromScope(Cpp::GetNamed( - "MyEnum", Cpp::GetScope("MyNameSpace"))), - "1"); + TAI_enum = Cpp::TemplateArgInfo( + Cpp::GetTypeFromScope( + Cpp::GetNamed("MyEnum", Cpp::GetScope("MyNameSpace" DFLT_NULLPTR))), + "1"); Cpp::TCppScope_t MyNameSpace_TemplatedEnum_instantiated = - Cpp::InstantiateTemplate(MyNameSpace_TemplatedEnum, &TAI_enum, 1); + Cpp::InstantiateTemplate(MyNameSpace_TemplatedEnum, &TAI_enum, + 1 DFLT_FALSE); EXPECT_TRUE(TemplatedEnum_instantiated); - obj = Cpp::Construct(MyNameSpace_TemplatedEnum_instantiated); + obj = Cpp::Construct( + MyNameSpace_TemplatedEnum_instantiated DFLT_NULLPTR DFLT_1); EXPECT_TRUE(obj); - Cpp::Destruct(obj, MyNameSpace_TemplatedEnum_instantiated); + Cpp::Destruct(obj, MyNameSpace_TemplatedEnum_instantiated DFLT_TRUE DFLT_0); obj = nullptr; Cpp::Declare(R"( auto get_fn(int x) { return [x](int y){ return x + y; }; } - )"); + )" DFLT_FALSE); - Cpp::TCppScope_t get_fn = Cpp::GetNamed("get_fn"); + Cpp::TCppScope_t get_fn = Cpp::GetNamed("get_fn" DFLT_NULLPTR); EXPECT_TRUE(get_fn); auto get_fn_callable = Cpp::MakeFunctionCallable(get_fn); @@ -2186,7 +2319,7 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { EXPECT_FALSE(Cpp::IsLambdaClass(Cpp::GetFunctionReturnType(bar))); } -TEST(FunctionReflectionTest, IsConstMethod) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsConstMethod) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -2204,7 +2337,7 @@ TEST(FunctionReflectionTest, IsConstMethod) { EXPECT_FALSE(Cpp::IsConstMethod(method)); } -TEST(FunctionReflectionTest, GetFunctionArgName) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgName) { std::vector Decls; std::string code = R"( void f1(int i, double d, long l, char ch) {} @@ -2244,7 +2377,7 @@ TEST(FunctionReflectionTest, GetFunctionArgName) { EXPECT_EQ(Cpp::GetFunctionArgName(Decls[4], 3), "l"); } -TEST(FunctionReflectionTest, GetFunctionArgDefault) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgDefault) { std::vector Decls; std::string code = R"( void f1(int i, double d = 4.0, const char *s = "default", char ch = 'c') {} @@ -2296,7 +2429,7 @@ TEST(FunctionReflectionTest, GetFunctionArgDefault) { ASTContext& C = Interp->getCI()->getASTContext(); Cpp::TemplateArgInfo template_args[1] = {C.IntTy.getAsOpaquePtr()}; Cpp::TCppScope_t my_struct = - Cpp::InstantiateTemplate(Decls[6], template_args, 1); + Cpp::InstantiateTemplate(Decls[6], template_args, 1 DFLT_FALSE); EXPECT_TRUE(my_struct); std::vector fns = @@ -2308,7 +2441,7 @@ TEST(FunctionReflectionTest, GetFunctionArgDefault) { EXPECT_EQ(Cpp::GetFunctionArgDefault(fn, 1), "S()"); } -TEST(FunctionReflectionTest, Construct) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Construct) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -2319,6 +2452,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; @@ -2339,8 +2474,8 @@ TEST(FunctionReflectionTest, Construct) { GetAllTopLevelDecls(code, Decls, false, interpreter_args); GetAllSubDecls(Decls[1], SubDecls); testing::internal::CaptureStdout(); - Cpp::TCppScope_t scope = Cpp::GetNamed("C"); - Cpp::TCppObject_t object = Cpp::Construct(scope); + Cpp::TCppScope_t scope = Cpp::GetNamed("C" DFLT_NULLPTR); + Cpp::TCppObject_t object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); EXPECT_TRUE(object != nullptr); std::string output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Constructor Executed"); @@ -2348,28 +2483,28 @@ TEST(FunctionReflectionTest, Construct) { // Placement. testing::internal::CaptureStdout(); - void* where = Cpp::Allocate(scope); - EXPECT_TRUE(where == Cpp::Construct(scope, where)); + void* where = Cpp::Allocate(scope DFLT_1); + EXPECT_TRUE(where == Cpp::Construct(scope, where DFLT_1)); // Check for the value of x which should be at the start of the object. EXPECT_TRUE(*(int *)where == 12345); - Cpp::Deallocate(scope, where); + Cpp::Deallocate(scope, where DFLT_1); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Constructor Executed"); output.clear(); // Pass a constructor testing::internal::CaptureStdout(); - where = Cpp::Allocate(scope); - EXPECT_TRUE(where == Cpp::Construct(SubDecls[3], where)); + where = Cpp::Allocate(scope DFLT_1); + EXPECT_TRUE(where == Cpp::Construct(SubDecls[3], where DFLT_1)); EXPECT_TRUE(*(int*)where == 12345); - Cpp::Deallocate(scope, where); + Cpp::Deallocate(scope, where DFLT_1); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Constructor Executed"); output.clear(); // Pass a non-class decl, this should fail - where = Cpp::Allocate(scope); - where = Cpp::Construct(Decls[2], where); + where = Cpp::Allocate(scope DFLT_1); + where = Cpp::Construct(Decls[2], where DFLT_1); EXPECT_TRUE(where == nullptr); // C API testing::internal::CaptureStdout(); @@ -2389,7 +2524,7 @@ TEST(FunctionReflectionTest, Construct) { } // Test zero initialization of PODs and default initialization cases -TEST(FunctionReflectionTest, ConstructPOD) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructPOD) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -2400,8 +2535,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 { @@ -2414,17 +2551,17 @@ TEST(FunctionReflectionTest, ConstructPOD) { }; })"); - auto *ns = Cpp::GetNamed("PODS"); + auto* ns = Cpp::GetNamed("PODS" DFLT_NULLPTR); Cpp::TCppScope_t scope = Cpp::GetNamed("SomePOD_B", ns); EXPECT_TRUE(scope); - Cpp::TCppObject_t object = Cpp::Construct(scope); + Cpp::TCppObject_t object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); EXPECT_TRUE(object != nullptr); int* fInt = reinterpret_cast(reinterpret_cast(object)); EXPECT_TRUE(*fInt == 0); scope = Cpp::GetNamed("SomePOD_C", ns); EXPECT_TRUE(scope); - object = Cpp::Construct(scope); + object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); EXPECT_TRUE(object); auto* fDouble = reinterpret_cast(reinterpret_cast(object) + sizeof(int)); @@ -2432,7 +2569,7 @@ TEST(FunctionReflectionTest, ConstructPOD) { } // Test nested constructor calls -TEST(FunctionReflectionTest, ConstructNested) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructNested) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -2443,9 +2580,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 @@ -2469,9 +2608,9 @@ TEST(FunctionReflectionTest, ConstructNested) { )"); testing::internal::CaptureStdout(); - Cpp::TCppScope_t scope_A = Cpp::GetNamed("A"); - Cpp::TCppScope_t scope_B = Cpp::GetNamed("B"); - Cpp::TCppObject_t object = Cpp::Construct(scope_B); + Cpp::TCppScope_t scope_A = Cpp::GetNamed("A" DFLT_NULLPTR); + Cpp::TCppScope_t scope_B = Cpp::GetNamed("B" DFLT_NULLPTR); + Cpp::TCppObject_t object = Cpp::Construct(scope_B DFLT_NULLPTR DFLT_1); EXPECT_TRUE(object != nullptr); std::string output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "A Constructor Called\nB Constructor Called\n"); @@ -2479,8 +2618,8 @@ TEST(FunctionReflectionTest, ConstructNested) { // In-memory construction testing::internal::CaptureStdout(); - void* arena = Cpp::Allocate(scope_B); - EXPECT_TRUE(arena == Cpp::Construct(scope_B, arena)); + void* arena = Cpp::Allocate(scope_B DFLT_1); + EXPECT_TRUE(arena == Cpp::Construct(scope_B, arena DFLT_1)); // Check if both integers a_val and b_val were set. EXPECT_EQ(*(int*)arena, 7); @@ -2488,13 +2627,13 @@ TEST(FunctionReflectionTest, ConstructNested) { int* b_val_ptr = reinterpret_cast(reinterpret_cast(arena) + a_size); EXPECT_EQ(*b_val_ptr, 99); - Cpp::Deallocate(scope_B, arena); + Cpp::Deallocate(scope_B, arena DFLT_1); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "A Constructor Called\nB Constructor Called\n"); output.clear(); } -TEST(FunctionReflectionTest, ConstructArray) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructArray) { #if defined(EMSCRIPTEN) GTEST_SKIP() << "Test fails for Emscripten builds"; #endif @@ -2503,8 +2642,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 @@ -2518,7 +2659,7 @@ TEST(FunctionReflectionTest, ConstructArray) { }; )"); - Cpp::TCppScope_t scope = Cpp::GetNamed("C"); + Cpp::TCppScope_t scope = Cpp::GetNamed("C" DFLT_NULLPTR); std::string output; size_t a = 5; // Construct an array of 5 objects @@ -2546,7 +2687,7 @@ TEST(FunctionReflectionTest, ConstructArray) { output.clear(); } -TEST(FunctionReflectionTest, Destruct) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Destruct) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -2556,9 +2697,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 @@ -2572,20 +2715,20 @@ TEST(FunctionReflectionTest, Destruct) { )"); testing::internal::CaptureStdout(); - Cpp::TCppScope_t scope = Cpp::GetNamed("C"); - Cpp::TCppObject_t object = Cpp::Construct(scope); - EXPECT_TRUE(Cpp::Destruct(object, scope)); + Cpp::TCppScope_t scope = Cpp::GetNamed("C" DFLT_NULLPTR); + Cpp::TCppObject_t object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); + EXPECT_TRUE(Cpp::Destruct(object, scope DFLT_TRUE DFLT_0)); std::string output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Destructor Executed"); output.clear(); testing::internal::CaptureStdout(); - object = Cpp::Construct(scope); + object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); // Make sure we do not call delete by adding an explicit Deallocate. If we // called delete the Deallocate will cause a double deletion error. - EXPECT_TRUE(Cpp::Destruct(object, scope, /*withFree=*/false)); - Cpp::Deallocate(scope, object); + EXPECT_TRUE(Cpp::Destruct(object, scope, /*withFree=*/false DFLT_0)); + Cpp::Deallocate(scope, object DFLT_1); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Destructor Executed"); @@ -2602,7 +2745,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, this wrapper should not compile since we explicitly delete // the destructor Interp->declare(R"( class D { @@ -2612,12 +2755,12 @@ TEST(FunctionReflectionTest, Destruct) { }; )"); - scope = Cpp::GetNamed("D"); - object = Cpp::Construct(scope); - EXPECT_FALSE(Cpp::Destruct(object, scope)); + scope = Cpp::GetNamed("D" DFLT_NULLPTR); + object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); + EXPECT_FALSE(Cpp::Destruct(object, scope DFLT_TRUE DFLT_0)); } -TEST(FunctionReflectionTest, DestructArray) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_DestructArray) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -2627,9 +2770,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 @@ -2646,7 +2791,7 @@ TEST(FunctionReflectionTest, DestructArray) { }; )"); - Cpp::TCppScope_t scope = Cpp::GetNamed("C"); + Cpp::TCppScope_t scope = Cpp::GetNamed("C" DFLT_NULLPTR); std::string output; size_t a = 5; // Construct an array of 5 objects @@ -2679,28 +2824,29 @@ TEST(FunctionReflectionTest, DestructArray) { output.clear(); // deallocate since we call the destructor withFree = false - Cpp::Deallocate(scope, where, 5); + Cpp::Deallocate(scope, where, a); // perform the same withFree=true - where = Cpp::Allocate(scope, a); - EXPECT_TRUE(where == Cpp::Construct(scope, where, a)); + where = nullptr; + where = Cpp::Construct(scope, nullptr, a); + EXPECT_TRUE(where); testing::internal::CaptureStdout(); - // FIXME : This should work with the array of objects as well - // Cpp::Destruct(where, scope, true, 5); - EXPECT_TRUE(Cpp::Destruct(where, scope, true)); + EXPECT_TRUE(Cpp::Destruct(where, scope, true, a)); output = testing::internal::GetCapturedStdout(); - EXPECT_EQ(output, "\nDestructor Executed\n"); + EXPECT_EQ(output, + "\nDestructor Executed\n\nDestructor Executed\n\nDestructor " + "Executed\n\nDestructor Executed\n\nDestructor Executed\n"); output.clear(); } -TEST(FunctionReflectionTest, UndoTest) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_UndoTest) { #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); @@ -2720,14 +2866,14 @@ TEST(FunctionReflectionTest, UndoTest) { #endif } -TEST(FunctionReflectionTest, FailingTest1) { +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_FailingTest1) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif #ifdef EMSCRIPTEN_SHARED_LIBRARY GTEST_SKIP() << "Test fails for Emscipten shared library builds"; #endif - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); EXPECT_FALSE(Cpp::Declare(R"( class WithOutEqualOp1 {}; class WithOutEqualOp2 {}; @@ -2737,20 +2883,155 @@ TEST(FunctionReflectionTest, FailingTest1) { template bool is_equal(const C1& c1, const C2& c2) { return (bool)(c1 == c2); } - )")); + )" DFLT_FALSE)); - Cpp::TCppType_t o1 = Cpp::GetTypeFromScope(Cpp::GetNamed("o1")); - Cpp::TCppType_t o2 = Cpp::GetTypeFromScope(Cpp::GetNamed("o2")); + Cpp::TCppType_t o1 = Cpp::GetTypeFromScope(Cpp::GetNamed("o1" DFLT_NULLPTR)); + Cpp::TCppType_t o2 = Cpp::GetTypeFromScope(Cpp::GetNamed("o2" DFLT_NULLPTR)); std::vector fns; Cpp::GetClassTemplatedMethods("is_equal", Cpp::GetGlobalScope(), fns); EXPECT_EQ(fns.size(), 1); Cpp::TemplateArgInfo args[2] = {{o1}, {o2}}; - Cpp::TCppScope_t fn = Cpp::InstantiateTemplate(fns[0], args, 2); + Cpp::TCppScope_t fn = Cpp::InstantiateTemplate(fns[0], args, 2 DFLT_FALSE); EXPECT_TRUE(fn); Cpp::JitCall jit_call = Cpp::MakeFunctionCallable(fn); EXPECT_EQ(jit_call.getKind(), Cpp::JitCall::kUnknown); // expected to fail - EXPECT_FALSE(Cpp::Declare("int x = 1;")); - EXPECT_FALSE(Cpp::Declare("int y = x;")); + EXPECT_FALSE(Cpp::Declare("int x = 1;" DFLT_FALSE)); + EXPECT_FALSE(Cpp::Declare("int y = x;" DFLT_FALSE)); +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsExplicit) { + std::vector Decls; + std::vector SubDecls; + std::string code = R"( + class C { + public: + C() {} + explicit C(int) {} + C(const C&) {} + explicit C(C&&) {} + + operator int() { return 0; } + explicit operator bool() { return true; } + + void regular_method() {} + private: + explicit C(double) {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + // constructors + EXPECT_FALSE(Cpp::IsExplicit(SubDecls[2])); // C() + EXPECT_TRUE(Cpp::IsExplicit(SubDecls[3])); // explicit C(int) + EXPECT_FALSE(Cpp::IsExplicit(SubDecls[4])); // C(const C&) copy ctor + EXPECT_TRUE(Cpp::IsExplicit(SubDecls[5])); // explicit C(C&&) move ctor + + // conversion operators + EXPECT_FALSE(Cpp::IsExplicit(SubDecls[6])); // operator int() + EXPECT_TRUE(Cpp::IsExplicit(SubDecls[7])); // explicit operator bool() + EXPECT_FALSE(Cpp::IsExplicit(SubDecls[8])); // regular_method() + EXPECT_TRUE(Cpp::IsExplicit(SubDecls[10])); // private explicit C(double) + EXPECT_FALSE(Cpp::IsExplicit(Decls[0])); + EXPECT_FALSE(Cpp::IsExplicit(nullptr)); +} +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsExplicitTemplated) { + std::vector Decls; + std::vector SubDecls; + std::string code = R"( + class T { + public: + T() = delete; + + template + T(U) {} + + template + explicit T(U, int) {} + + template + operator U() { return U{}; } + + template + explicit operator U*() { return nullptr; } + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + int implicitCtorCount = 0; + int explicitCtorCount = 0; + int implicitConvCount = 0; + int explicitConvCount = 0; + + for (auto* decl : SubDecls) { + // skip deleted constructors + if (auto* CD = llvm::dyn_cast_or_null( + static_cast(decl))) { + if (CD->isDeleted()) + continue; + } + + if (Cpp::IsConstructor(decl)) { + if (Cpp::IsExplicit(decl)) + explicitCtorCount++; + else + implicitCtorCount++; + } else { + // conversion operator + auto* D = static_cast(decl); + if (auto* FTD = llvm::dyn_cast_or_null(D)) + D = FTD->getTemplatedDecl(); + + if (llvm::isa(D)) { + if (Cpp::IsExplicit(decl)) + explicitConvCount++; + else + implicitConvCount++; + } + } + } + + EXPECT_EQ(implicitCtorCount, 1); // T(U) + EXPECT_EQ(explicitCtorCount, 1); // explicit T(U, int) + EXPECT_EQ(implicitConvCount, 1); // operator U() + EXPECT_EQ(explicitConvCount, 1); // explicit operator U*() +} + +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsExplicitDeductionGuide) { + // Deduction guides are a C++17 feature + std::vector interpreter_args = {"-include", "new", "-std=c++17"}; + Cpp::CreateInterpreter(interpreter_args, {}); + + Interp->declare(R"( + template + struct Wrapper { + T value; + Wrapper(T v) : value(v) {} + }; + template + explicit Wrapper(T*) -> Wrapper; + )"); + + auto* TUD = Interp->getCI()->getASTContext().getTranslationUnitDecl(); + bool foundExplicitGuide = false; + Decl* guide = nullptr; + for (auto* D : TUD->decls()) { + if (Cpp::IsExplicit(D)) { + foundExplicitGuide = true; + guide = D; + break; + } + } + + EXPECT_TRUE(foundExplicitGuide); + EXPECT_TRUE(guide != nullptr); + EXPECT_EQ(Cpp::GetFunctionSignature(guide), + "explicit Wrapper(T *) -> Wrapper"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(guide)), + "Wrapper"); } diff --git a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp index e9b82ea1def54..3c19cc9638d62 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp @@ -27,23 +27,23 @@ using ::testing::StartsWith; -TEST(InterpreterTest, Version) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Version) { EXPECT_THAT(Cpp::GetVersion(), StartsWith("CppInterOp version")); } #ifdef NDEBUG -TEST(InterpreterTest, DISABLED_DebugFlag) { +TYPED_TEST(CPPINTEROP_TEST_MODE, DISABLED_Interpreter_DebugFlag) { #else -TEST(InterpreterTest, DebugFlag) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DebugFlag) { #endif // NDEBUG - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); EXPECT_FALSE(Cpp::IsDebugOutputEnabled()); std::string cerrs; testing::internal::CaptureStderr(); Cpp::Process("int a = 12;"); cerrs = testing::internal::GetCapturedStderr(); EXPECT_STREQ(cerrs.c_str(), ""); - Cpp::EnableDebugOutput(); + Cpp::EnableDebugOutput(true); EXPECT_TRUE(Cpp::IsDebugOutputEnabled()); testing::internal::CaptureStderr(); Cpp::Process("int b = 12;"); @@ -58,7 +58,7 @@ TEST(InterpreterTest, DebugFlag) { EXPECT_STREQ(cerrs.c_str(), ""); } -TEST(InterpreterTest, Evaluate) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Evaluate) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -67,23 +67,42 @@ 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 ; - EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201402); + TestFixture::CreateInterpreter(); + EXPECT_TRUE(Cpp::Evaluate("__cplusplus", nullptr) == 201402); bool HadError; EXPECT_TRUE(Cpp::Evaluate("#error", &HadError) == (intptr_t)~0UL); EXPECT_TRUE(HadError); + // for llvm < 19 this tests different overloads of + // __clang_Interpreter_SetValueNoAlloc EXPECT_EQ(Cpp::Evaluate("int i = 11; ++i", &HadError), 12); - EXPECT_FALSE(HadError) ; + EXPECT_FALSE(HadError); + EXPECT_EQ(Cpp::Evaluate("double a = 12.; a", &HadError), 12.); + EXPECT_FALSE(HadError); + EXPECT_EQ(Cpp::Evaluate("float b = 13.; b", &HadError), 13.); + EXPECT_FALSE(HadError); + EXPECT_EQ(Cpp::Evaluate("long double c = 14.; c", &HadError), 14.); + EXPECT_FALSE(HadError); + EXPECT_EQ(Cpp::Evaluate("long double d = 15.; d", &HadError), 15.); + EXPECT_FALSE(HadError); + EXPECT_EQ(Cpp::Evaluate("unsigned long long e = 16; e", &HadError), 16); + EXPECT_FALSE(HadError); + EXPECT_NE(Cpp::Evaluate("struct S{} s; s", &HadError), (intptr_t)~0UL); + EXPECT_FALSE(HadError); } -TEST(InterpreterTest, DeleteInterpreter) { - auto* I1 = Cpp::CreateInterpreter(); - auto* I2 = Cpp::CreateInterpreter(); - auto* I3 = Cpp::CreateInterpreter(); +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DeleteInterpreter) { + 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,43 +117,47 @@ TEST(InterpreterTest, DeleteInterpreter) { EXPECT_EQ(I2, Cpp::GetInterpreter()) << "I2 is not active"; } -TEST(InterpreterTest, ActivateInterpreter) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_ActivateInterpreter) { #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) + EXPECT_TRUE(Cpp::Evaluate("__cplusplus" DFLT_NULLPTR) == 202002L) << "Failed to activate C++20"; auto* UntrackedI = reinterpret_cast(static_cast(~0U)); EXPECT_FALSE(Cpp::ActivateInterpreter(UntrackedI)); EXPECT_TRUE(Cpp::ActivateInterpreter(Cpp14)); - EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201402L); + EXPECT_TRUE(Cpp::Evaluate("__cplusplus" DFLT_NULLPTR) == 201402L); Cpp::DeleteInterpreter(Cpp14); EXPECT_EQ(Cpp::GetInterpreter(), Cpp20); EXPECT_TRUE(Cpp::ActivateInterpreter(Cpp17)); - EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201703L); + EXPECT_TRUE(Cpp::Evaluate("__cplusplus" DFLT_NULLPTR) == 201703L); } -TEST(InterpreterTest, Process) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Process) { #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); + std::vector interpreter_args = { "-include", "new", "-Xclang", "-iwithsysroot/include/compat" }; + 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,21 +175,18 @@ TEST(InterpreterTest, Process) { clang_Interpreter_dispose(CXI); } -TEST(InterpreterTest, EmscriptenExceptionHandling) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_EmscriptenExceptionHandling) { #ifndef EMSCRIPTEN GTEST_SKIP() << "This test is intended to check exception handling for Emscripten builds."; #endif - - std::vector Args = { + std::vector Args = { "-std=c++20", "-v", - "-fexceptions", - "-fcxx-exceptions", - "-mllvm", "-enable-emscripten-cxx-exceptions", - "-mllvm", "-enable-emscripten-sjlj" + "-fwasm-exceptions", + "-mllvm","-wasm-enable-sjlj" }; - Cpp::CreateInterpreter(Args); + Cpp::CreateInterpreter(Args, {}); const char* tryCatchCode = R"( try { @@ -179,28 +199,27 @@ TEST(InterpreterTest, EmscriptenExceptionHandling) { EXPECT_TRUE(Cpp::Process(tryCatchCode) == 0); } -TEST(InterpreterTest, CreateInterpreter) { - auto* I = Cpp::CreateInterpreter(); +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_CreateInterpreter) { + auto* I = TestFixture::CreateInterpreter(); EXPECT_TRUE(I); // Check if the default standard is c++14 Cpp::Declare("#if __cplusplus==201402L\n" - "int cpp14() { return 2014; }\n" - "#else\n" - "void cppUnknown() {}\n" - "#endif"); - EXPECT_TRUE(Cpp::GetNamed("cpp14")); - EXPECT_FALSE(Cpp::GetNamed("cppUnknown")); - - I = Cpp::CreateInterpreter({"-std=c++17"}); + "int cpp14() { return 2014; }\n" + "#else\n" + "void cppUnknown() {}\n" + "#endif" DFLT_FALSE); + EXPECT_TRUE(Cpp::GetNamed("cpp14" DFLT_NULLPTR)); + EXPECT_FALSE(Cpp::GetNamed("cppUnknown" DFLT_NULLPTR)); + + I = TestFixture::CreateInterpreter({"-std=c++17"}); Cpp::Declare("#if __cplusplus==201703L\n" - "int cpp17() { return 2017; }\n" - "#else\n" - "void cppUnknown() {}\n" - "#endif"); - EXPECT_TRUE(Cpp::GetNamed("cpp17")); - EXPECT_FALSE(Cpp::GetNamed("cppUnknown")); - + "int cpp17() { return 2017; }\n" + "#else\n" + "void cppUnknown() {}\n" + "#endif" DFLT_FALSE); + EXPECT_TRUE(Cpp::GetNamed("cpp17" DFLT_NULLPTR)); + EXPECT_FALSE(Cpp::GetNamed("cppUnknown" DFLT_NULLPTR)); #ifndef CPPINTEROP_USE_CLING // C API @@ -215,7 +234,7 @@ TEST(InterpreterTest, CreateInterpreter) { } #ifndef CPPINTEROP_USE_CLING -TEST(InterpreterTest, CreateInterpreterCAPI) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_CreateInterpreterCAPI) { const char* argv[] = {"-std=c++17"}; auto *CXI = clang_createInterpreter(argv, 1); auto CLI = clang_Interpreter_getClangInterpreter(CXI); @@ -223,7 +242,7 @@ TEST(InterpreterTest, CreateInterpreterCAPI) { clang_Interpreter_dispose(CXI); } -TEST(InterpreterTest, CreateInterpreterCAPIFailure) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_CreateInterpreterCAPIFailure) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif @@ -234,17 +253,17 @@ TEST(InterpreterTest, CreateInterpreterCAPIFailure) { #endif #ifdef LLVM_BINARY_DIR -TEST(InterpreterTest, DetectResourceDir) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DetectResourceDir) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif #else -TEST(InterpreterTest, DISABLED_DetectResourceDir) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DISABLED_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 +275,7 @@ TEST(InterpreterTest, DISABLED_DetectResourceDir) { EXPECT_STREQ(DetectedPath.c_str(), Cpp::GetResourceDir()); } -TEST(InterpreterTest, DetectSystemCompilerIncludePaths) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DetectSystemCompilerIncludePaths) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif @@ -268,7 +287,9 @@ TEST(InterpreterTest, DetectSystemCompilerIncludePaths) { EXPECT_FALSE(includes.empty()); } -TEST(InterpreterTest, IncludePaths) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_IncludePaths) { + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; std::vector includes; Cpp::GetIncludePaths(includes); EXPECT_FALSE(includes.empty()); @@ -292,11 +313,11 @@ TEST(InterpreterTest, IncludePaths) { std::end(includes)); } -TEST(InterpreterTest, CodeCompletion) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_CodeCompletion) { #if CLANG_VERSION_MAJOR >= 18 || defined(CPPINTEROP_USE_CLING) - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); std::vector cc; - Cpp::Declare("int foo = 12;"); + Cpp::Declare("int foo = 12;" DFLT_FALSE); Cpp::CodeComplete(cc, "f", 1, 2); // We check only for 'float' and 'foo', because they // must be present in the result. Other hints may appear @@ -312,10 +333,10 @@ TEST(InterpreterTest, CodeCompletion) { #endif } -TEST(InterpreterTest, ExternalInterpreterTest) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_ExternalInterpreter) { -if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; #ifndef CPPINTEROP_USE_CLING llvm::ExitOnError ExitOnErr; diff --git a/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp index a1b6909b7d56c..34052a48866e7 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp @@ -11,7 +11,7 @@ static int printf_jit(const char* format, ...) { return 0; } -TEST(JitTest, InsertOrReplaceJitSymbol) { +TYPED_TEST(CPPINTEROP_TEST_MODE, Jit_InsertOrReplaceJitSymbol) { #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(CPPINTEROP_TEST_MODE, Jit_StreamRedirect) { + 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,38 @@ TEST(Streams, StreamRedirect) { EXPECT_STREQ(cerrs.c_str(), "Err\nStdErr\n"); // NOLINTEND(cppcoreguidelines-pro-type-vararg) } + +TYPED_TEST(CPPINTEROP_TEST_MODE, Jit_StreamRedirectJIT) { +#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/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp index 53b2ee8948ced..5d828fc59e6ad 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -25,7 +25,53 @@ using namespace TestUtils; using namespace llvm; using namespace clang; -TEST(ScopeReflectionTest, Demangle) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsEnumScope) { + 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(CPPINTEROP_TEST_MODE, ScopeReflection_IsEnumConstant) { + 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(CPPINTEROP_TEST_MODE, ScopeReflection_Demangle) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_IsAggregate) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_IsNamespace) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_IsClass) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_IsClassPolymorphic) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_IsComplete) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_SizeOf) { std::vector Decls; std::string code = R"(namespace N {} class C{}; int I; struct S; enum E : int; union U{}; class Size4{int i;}; @@ -162,8 +208,7 @@ TEST(ScopeReflectionTest, SizeOf) { EXPECT_EQ(Cpp::SizeOf(Decls[7]), sizeof(B)); } - -TEST(ScopeReflectionTest, IsBuiltin) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsBuiltin) { #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 +221,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 +244,7 @@ TEST(ScopeReflectionTest, IsBuiltin) { EXPECT_TRUE(Cpp::IsBuiltin(C.getTypeDeclType(CTSD).getAsOpaquePtr())); } -TEST(ScopeReflectionTest, IsTemplate) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsTemplate) { std::vector Decls; std::string code = R"(template class A{}; @@ -225,7 +270,7 @@ TEST(ScopeReflectionTest, IsTemplate) { EXPECT_FALSE(Cpp::IsTemplate(Decls[3])); } -TEST(ScopeReflectionTest, IsTemplateSpecialization) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsTemplateSpecialization) { std::vector Decls; std::string code = R"( template @@ -241,7 +286,7 @@ TEST(ScopeReflectionTest, IsTemplateSpecialization) { Cpp::GetScopeFromType(Cpp::GetVariableType(Decls[1])))); } -TEST(ScopeReflectionTest, IsTypedefed) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsTypedefed) { std::vector Decls; std::string code = R"( typedef int I; @@ -255,7 +300,7 @@ TEST(ScopeReflectionTest, IsTypedefed) { EXPECT_FALSE(Cpp::IsTypedefed(Decls[2])); } -TEST(ScopeReflectionTest, IsAbstract) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsAbstract) { std::vector Decls; std::string code = R"( class A {}; @@ -275,7 +320,7 @@ TEST(ScopeReflectionTest, IsAbstract) { EXPECT_FALSE(Cpp::IsAbstract(Decls[2])); } -TEST(ScopeReflectionTest, IsVariable) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsVariable) { std::vector Decls; std::string code = R"( int i; @@ -299,7 +344,7 @@ TEST(ScopeReflectionTest, IsVariable) { EXPECT_TRUE(Cpp::IsVariable(SubDecls[3])); } -TEST(ScopeReflectionTest, GetName) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetName) { 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 +362,7 @@ TEST(ScopeReflectionTest, GetName) { EXPECT_EQ(Cpp::GetName(nullptr), ""); } -TEST(ScopeReflectionTest, GetCompleteName) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetCompleteName) { std::vector Decls; std::string code = R"(namespace N {} class C{}; @@ -358,12 +403,13 @@ TEST(ScopeReflectionTest, GetCompleteName) { ASTContext& C = Interp->getCI()->getASTContext(); Cpp::TemplateArgInfo template_args[2] = {C.IntTy.getAsOpaquePtr(), C.DoubleTy.getAsOpaquePtr()}; - Cpp::TCppScope_t fn = Cpp::InstantiateTemplate(Decls[11], template_args, 2); + Cpp::TCppScope_t fn = + Cpp::InstantiateTemplate(Decls[11], template_args, 2 DFLT_FALSE); EXPECT_TRUE(fn); EXPECT_EQ(Cpp::GetCompleteName(fn), "fn"); } -TEST(ScopeReflectionTest, GetQualifiedName) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetQualifiedName) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_GetQualifiedCompleteName) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_GetUsingNamespaces) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_GetGlobalScope) { EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetGlobalScope()), ""); EXPECT_EQ(Cpp::GetName(Cpp::GetGlobalScope()), ""); } -TEST(ScopeReflectionTest, GetUnderlyingScope) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetUnderlyingScope) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_GetScope) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_GetScopefromCompleteName) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_GetNamed) { #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(CPPINTEROP_TEST_MODE, ScopeReflection_GetParentScope) { std::string code = R"(namespace N1 { namespace N2 { class C { @@ -567,10 +613,10 @@ TEST(ScopeReflectionTest, GetParentScope) { } )"; - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); Interp->declare(code); - Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1"); + Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1" DFLT_NULLPTR); Cpp::TCppScope_t ns_N2 = Cpp::GetNamed("N2", ns_N1); Cpp::TCppScope_t cl_C = Cpp::GetNamed("C", ns_N2); Cpp::TCppScope_t int_i = Cpp::GetNamed("i", cl_C); @@ -587,7 +633,7 @@ TEST(ScopeReflectionTest, GetParentScope) { EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetParentScope(en_B)), "N1::N2::C::E"); } -TEST(ScopeReflectionTest, GetScopeFromType) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetScopeFromType) { std::vector Decls; std::string code = R"( namespace N { @@ -631,7 +677,7 @@ TEST(ScopeReflectionTest, GetScopeFromType) { "N::C"); } -TEST(ScopeReflectionTest, GetNumBases) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetNumBases) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_GetBaseClass) { std::vector Decls; std::string code = R"( class A {}; @@ -698,20 +744,20 @@ TEST(ScopeReflectionTest, GetBaseClass) { EXPECT_EQ(get_base_class_name(Decls[4], 0), "D"); EXPECT_EQ(get_base_class_name(Decls[10], 0), ""); - auto *VD = Cpp::GetNamed("var"); + auto* VD = Cpp::GetNamed("var" DFLT_NULLPTR); auto *VT = Cpp::GetVariableType(VD); auto *TC2_A_Decl = Cpp::GetScopeFromType(VT); auto *TC1_A_Decl = Cpp::GetBaseClass(TC2_A_Decl, 0); EXPECT_EQ(Cpp::GetCompleteName(TC1_A_Decl), "TC1"); - auto* VD1 = Cpp::GetNamed("var1"); + auto* VD1 = Cpp::GetNamed("var1" DFLT_NULLPTR); auto* VT1 = Cpp::GetVariableType(VD1); auto* TC3_A_Decl = Cpp::GetScopeFromType(VT1); auto* A_class = Cpp::GetBaseClass(TC3_A_Decl, 0); EXPECT_EQ(Cpp::GetCompleteName(A_class), "A"); } -TEST(ScopeReflectionTest, IsSubclass) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IsSubclass) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_GetBaseClassOffset) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_GetAllCppNames) { 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(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateNNTPClassTemplate) { std::vector Decls; std::string code = R"( template @@ -851,7 +897,7 @@ TEST(ScopeReflectionTest, InstantiateNNTPClassTemplate) { Cpp::TCppType_t IntTy = C.IntTy.getAsOpaquePtr(); std::vector args1 = {{IntTy, "5"}}; EXPECT_TRUE(Cpp::InstantiateTemplate(Decls[0], args1.data(), - /*type_size*/ args1.size())); + /*type_size*/ args1.size() DFLT_FALSE)); // C API auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); @@ -865,7 +911,7 @@ TEST(ScopeReflectionTest, InstantiateNNTPClassTemplate) { clang_Interpreter_dispose(I); } -TEST(ScopeReflectionTest, InstantiateVarTemplate) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateVarTemplate) { std::vector Decls; std::string code = R"( template constexpr T pi = T(3.1415926535897932385L); @@ -875,8 +921,9 @@ template constexpr T pi = T(3.1415926535897932385L); ASTContext& C = Interp->getCI()->getASTContext(); std::vector args1 = {C.IntTy.getAsOpaquePtr()}; - auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(), - /*type_size*/ args1.size()); + auto* Instance1 = + Cpp::InstantiateTemplate(Decls[0], args1.data(), + /*type_size*/ args1.size() DFLT_FALSE); EXPECT_TRUE(isa((Decl*)Instance1)); auto* VD = cast((Decl*)Instance1); VarTemplateDecl* VDTD1 = VD->getSpecializedTemplate(); @@ -889,7 +936,7 @@ template constexpr T pi = T(3.1415926535897932385L); EXPECT_TRUE(TA1.getAsType()->isIntegerType()); } -TEST(ScopeReflectionTest, InstantiateFunctionTemplate) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateFunctionTemplate) { std::vector Decls; std::string code = R"( template T TrivialFnTemplate() { return T(); } @@ -899,8 +946,9 @@ template T TrivialFnTemplate() { return T(); } ASTContext& C = Interp->getCI()->getASTContext(); std::vector args1 = {C.IntTy.getAsOpaquePtr()}; - auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(), - /*type_size*/ args1.size()); + auto* Instance1 = + Cpp::InstantiateTemplate(Decls[0], args1.data(), + /*type_size*/ args1.size() DFLT_FALSE); EXPECT_TRUE(isa((Decl*)Instance1)); FunctionDecl* FD = cast((Decl*)Instance1); FunctionDecl* FnTD1 = FD->getTemplateInstantiationPattern(); @@ -909,7 +957,8 @@ template T TrivialFnTemplate() { return T(); } EXPECT_TRUE(TA1.getAsType()->isIntegerType()); } -TEST(ScopeReflectionTest, InstantiateTemplateFunctionFromString) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + ScopeReflection_InstantiateTemplateFunctionFromString) { #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 +966,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 +974,7 @@ TEST(ScopeReflectionTest, InstantiateTemplateFunctionFromString) { EXPECT_TRUE(Instance1); } -TEST(ScopeReflectionTest, InstantiateTemplate) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateTemplate) { std::vector Decls; std::string code = R"( template @@ -969,8 +1018,9 @@ TEST(ScopeReflectionTest, InstantiateTemplate) { ASTContext &C = Interp->getCI()->getASTContext(); std::vector args1 = {C.IntTy.getAsOpaquePtr()}; - auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(), - /*type_size*/ args1.size()); + auto* Instance1 = + Cpp::InstantiateTemplate(Decls[0], args1.data(), + /*type_size*/ args1.size() DFLT_FALSE); EXPECT_TRUE(isa((Decl*)Instance1)); auto *CTSD1 = static_cast(Instance1); EXPECT_TRUE(CTSD1->hasDefinition()); @@ -979,7 +1029,7 @@ TEST(ScopeReflectionTest, InstantiateTemplate) { EXPECT_TRUE(CTSD1->hasDefinition()); auto Instance2 = Cpp::InstantiateTemplate(Decls[1], nullptr, - /*type_size*/ 0); + /*type_size*/ 0 DFLT_FALSE); EXPECT_TRUE(isa((Decl*)Instance2)); auto *CTSD2 = static_cast(Instance2); EXPECT_TRUE(CTSD2->hasDefinition()); @@ -987,8 +1037,9 @@ TEST(ScopeReflectionTest, InstantiateTemplate) { EXPECT_TRUE(TA2.getAsType()->isIntegerType()); std::vector args3 = {C.IntTy.getAsOpaquePtr()}; - auto Instance3 = Cpp::InstantiateTemplate(Decls[2], args3.data(), - /*type_size*/ args3.size()); + auto* Instance3 = + Cpp::InstantiateTemplate(Decls[2], args3.data(), + /*type_size*/ args3.size() DFLT_FALSE); EXPECT_TRUE(isa((Decl*)Instance3)); auto *CTSD3 = static_cast(Instance3); EXPECT_TRUE(CTSD3->hasDefinition()); @@ -1004,8 +1055,9 @@ TEST(ScopeReflectionTest, InstantiateTemplate) { std::vector args4 = {C.IntTy.getAsOpaquePtr(), {C.IntTy.getAsOpaquePtr(), "3"}}; - auto Instance4 = Cpp::InstantiateTemplate(Decls[3], args4.data(), - /*type_size*/ args4.size()); + auto* Instance4 = + Cpp::InstantiateTemplate(Decls[3], args4.data(), + /*type_size*/ args4.size() DFLT_FALSE); EXPECT_TRUE(isa((Decl*)Instance4)); auto *CTSD4 = static_cast(Instance4); @@ -1016,7 +1068,8 @@ TEST(ScopeReflectionTest, InstantiateTemplate) { EXPECT_TRUE(TA4_1.getAsIntegral() == 3); } -TEST(ScopeReflectionTest, GetClassTemplateInstantiationArgs) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + ScopeReflection_GetClassTemplateInstantiationArgs) { std::vector Decls; std::string code = R"( template struct __Cppyy_AppendTypesSlow {}; @@ -1027,9 +1080,9 @@ TEST(ScopeReflectionTest, GetClassTemplateInstantiationArgs) { GetAllTopLevelDecls(code, Decls); - auto *v1 = Cpp::GetNamed("v1"); - auto *v2 = Cpp::GetNamed("v2"); - auto *v3 = Cpp::GetNamed("v3"); + auto* v1 = Cpp::GetNamed("v1" DFLT_NULLPTR); + auto* v2 = Cpp::GetNamed("v2" DFLT_NULLPTR); + auto* v3 = Cpp::GetNamed("v3" DFLT_NULLPTR); EXPECT_TRUE(v1 && v2 && v3); auto *v1_class = Cpp::GetScopeFromType(Cpp::GetVariableType(v1)); @@ -1053,8 +1106,7 @@ TEST(ScopeReflectionTest, GetClassTemplateInstantiationArgs) { EXPECT_TRUE(instance_types.size() == 0); } - -TEST(ScopeReflectionTest, IncludeVector) { +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IncludeVector) { #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 +1118,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(CPPINTEROP_TEST_MODE, ScopeReflection_GetOperator) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); std::string code = R"( class MyClass { @@ -1108,39 +1160,44 @@ TEST(ScopeReflectionTest, GetOperator) { } )"; - Cpp::Declare(code.c_str()); + Cpp::Declare(code.c_str() DFLT_FALSE); std::vector ops; - Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Plus, ops); + Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Plus, + ops DFLT_OP_ARITY); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Minus, ops); + Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Minus, + ops DFLT_OP_ARITY); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Star, ops); + Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Star, + ops DFLT_OP_ARITY); EXPECT_EQ(ops.size(), 0); ops.clear(); // operators defined within a namespace - Cpp::GetOperator(Cpp::GetScope("extra_ops"), Cpp::Operator::OP_Plus, ops); + Cpp::GetOperator(Cpp::GetScope("extra_ops" DFLT_0), Cpp::Operator::OP_Plus, + ops DFLT_OP_ARITY); EXPECT_EQ(ops.size(), 2); ops.clear(); // unary operator - Cpp::GetOperator(Cpp::GetScope("extra_ops"), Cpp::Operator::OP_Tilde, ops); + Cpp::GetOperator(Cpp::GetScope("extra_ops" DFLT_0), Cpp::Operator::OP_Tilde, + ops DFLT_OP_ARITY); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetScope("extra_ops"), Cpp::Operator::OP_Tilde, ops, - Cpp::OperatorArity::kUnary); + Cpp::GetOperator(Cpp::GetScope("extra_ops" DFLT_0), Cpp::Operator::OP_Tilde, + ops, Cpp::OperatorArity::kUnary); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetScope("extra_ops"), Cpp::Operator::OP_Tilde, ops, - Cpp::OperatorArity::kBinary); + Cpp::GetOperator(Cpp::GetScope("extra_ops" DFLT_0), Cpp::Operator::OP_Tilde, + ops, Cpp::OperatorArity::kBinary); EXPECT_EQ(ops.size(), 0); std::string inheritance_code = R"( @@ -1159,13 +1216,15 @@ TEST(ScopeReflectionTest, GetOperator) { } }; )"; - Cpp::Declare(inheritance_code.c_str()); + Cpp::Declare(inheritance_code.c_str() DFLT_FALSE); ops.clear(); - Cpp::GetOperator(Cpp::GetScope("Child"), Cpp::Operator::OP_Plus, ops); + Cpp::GetOperator(Cpp::GetScope("Child" DFLT_0), Cpp::Operator::OP_Plus, + ops DFLT_OP_ARITY); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetScope("Child"), Cpp::Operator::OP_Minus, ops); + Cpp::GetOperator(Cpp::GetScope("Child" DFLT_0), Cpp::Operator::OP_Minus, + ops DFLT_OP_ARITY); EXPECT_EQ(ops.size(), 1); } diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/CMakeLists.txt b/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/CMakeLists.txt index 0c27afc95084d..477b2fc225029 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/CMakeLists.txt @@ -5,8 +5,8 @@ add_llvm_library(TestSharedLib TestSharedLib.cpp) # Put TestSharedLib next to the unit test executable. set_output_directory(TestSharedLib - BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/unittests/bin/$/ - LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/unittests/bin/$/ + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/../bin/$/ + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/../bin/$/ ) @@ -14,9 +14,14 @@ if (EMSCRIPTEN) set_target_properties(TestSharedLib PROPERTIES NO_SONAME 1 ) + target_compile_options(TestSharedLib + PUBLIC "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" + ) + target_link_options(TestSharedLib PRIVATE "SHELL: -s WASM_BIGINT" PRIVATE "SHELL: -s SIDE_MODULE=1" + PUBLIC "SHELL: ${CPPINTEROP_EXTRA_WASM_FLAGS}" ) endif() diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp index 215d30f104394..9a85b8fa88d88 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp @@ -17,7 +17,7 @@ using namespace TestUtils; using namespace llvm; using namespace clang; -TEST(TypeReflectionTest, GetTypeAsString) { +TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_GetTypeAsString) { 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(CPPINTEROP_TEST_MODE, TypeReflection_GetSizeOfType) { std::vector Decls; std::string code = R"( struct S { @@ -85,7 +85,7 @@ TEST(TypeReflectionTest, GetSizeOfType) { sizeof(intptr_t)); } -TEST(TypeReflectionTest, GetCanonicalType) { +TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_GetCanonicalType) { 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(CPPINTEROP_TEST_MODE, TypeReflection_GetType) { + 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(CPPINTEROP_TEST_MODE, TypeReflection_IsRecordType) { 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(CPPINTEROP_TEST_MODE, TypeReflection_GetUnderlyingType) { 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(CPPINTEROP_TEST_MODE, TypeReflection_IsUnderlyingTypeRecordType) { 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(CPPINTEROP_TEST_MODE, TypeReflection_GetComplexType) { + 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(CPPINTEROP_TEST_MODE, TypeReflection_GetTypeFromScope) { 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(CPPINTEROP_TEST_MODE, TypeReflection_IsTypeDerivedFrom) { 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(CPPINTEROP_TEST_MODE, TypeReflection_GetDimensions) { std::vector Decls, SubDecls; std::string code = R"( @@ -528,7 +528,7 @@ TEST(TypeReflectionTest, GetDimensions) { } } -TEST(TypeReflectionTest, IsPODType) { +TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_IsPODType) { std::vector Decls; std::string code = R"( @@ -550,7 +550,7 @@ TEST(TypeReflectionTest, IsPODType) { EXPECT_FALSE(Cpp::IsPODType(0)); } -TEST(TypeReflectionTest, IsSmartPtrType) { +TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_IsSmartPtrType) { #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 @@ -582,8 +582,8 @@ TEST(TypeReflectionTest, IsSmartPtrType) { C object(); )"); - auto get_type_from_varname = [&](const std::string &varname) { - return Cpp::GetVariableType(Cpp::GetNamed(varname)); + auto get_type_from_varname = [&](const std::string& varname) { + return Cpp::GetVariableType(Cpp::GetNamed(varname DFLT_NULLPTR)); }; //EXPECT_TRUE(Cpp::IsSmartPtrType(get_type_from_varname("smart_ptr1"))); @@ -596,9 +596,9 @@ TEST(TypeReflectionTest, IsSmartPtrType) { EXPECT_FALSE(Cpp::IsSmartPtrType(get_type_from_varname("object"))); } -TEST(TypeReflectionTest, IsFunctionPointerType) { +TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_IsFunctionPointerType) { std::vector interpreter_args = {"-include", "new"}; - Cpp::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); Interp->declare(R"( typedef int (*int_func)(int, int); @@ -607,13 +607,13 @@ TEST(TypeReflectionTest, IsFunctionPointerType) { int i = 2; )"); - EXPECT_TRUE( - Cpp::IsFunctionPointerType(Cpp::GetVariableType(Cpp::GetNamed("f")))); - EXPECT_FALSE( - Cpp::IsFunctionPointerType(Cpp::GetVariableType(Cpp::GetNamed("i")))); + EXPECT_TRUE(Cpp::IsFunctionPointerType( + Cpp::GetVariableType(Cpp::GetNamed("f" DFLT_NULLPTR)))); + EXPECT_FALSE(Cpp::IsFunctionPointerType( + Cpp::GetVariableType(Cpp::GetNamed("i" DFLT_NULLPTR)))); } -TEST(TypeReflectionTest, OperatorSpelling) { +TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_OperatorSpelling) { 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(CPPINTEROP_TEST_MODE, TypeReflection_TypeQualifiers) { + TestFixture::CreateInterpreter(); Cpp::Declare(R"( int *a; int *__restrict__ b; @@ -632,16 +632,16 @@ TEST(TypeReflectionTest, TypeQualifiers) { int *__restrict__ const f = nullptr; int *__restrict__ volatile g; int *__restrict__ const volatile h = nullptr; - )"); - - Cpp::TCppType_t a = Cpp::GetVariableType(Cpp::GetNamed("a")); - Cpp::TCppType_t b = Cpp::GetVariableType(Cpp::GetNamed("b")); - Cpp::TCppType_t c = Cpp::GetVariableType(Cpp::GetNamed("c")); - Cpp::TCppType_t d = Cpp::GetVariableType(Cpp::GetNamed("d")); - Cpp::TCppType_t e = Cpp::GetVariableType(Cpp::GetNamed("e")); - Cpp::TCppType_t f = Cpp::GetVariableType(Cpp::GetNamed("f")); - Cpp::TCppType_t g = Cpp::GetVariableType(Cpp::GetNamed("g")); - Cpp::TCppType_t h = Cpp::GetVariableType(Cpp::GetNamed("h")); + )" DFLT_FALSE); + + Cpp::TCppType_t a = Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)); + Cpp::TCppType_t b = Cpp::GetVariableType(Cpp::GetNamed("b" DFLT_NULLPTR)); + Cpp::TCppType_t c = Cpp::GetVariableType(Cpp::GetNamed("c" DFLT_NULLPTR)); + Cpp::TCppType_t d = Cpp::GetVariableType(Cpp::GetNamed("d" DFLT_NULLPTR)); + Cpp::TCppType_t e = Cpp::GetVariableType(Cpp::GetNamed("e" DFLT_NULLPTR)); + Cpp::TCppType_t f = Cpp::GetVariableType(Cpp::GetNamed("f" DFLT_NULLPTR)); + Cpp::TCppType_t g = Cpp::GetVariableType(Cpp::GetNamed("g" DFLT_NULLPTR)); + Cpp::TCppType_t h = Cpp::GetVariableType(Cpp::GetNamed("h" DFLT_NULLPTR)); EXPECT_FALSE(Cpp::HasTypeQualifier(nullptr, Cpp::QualKind::Const)); EXPECT_FALSE(Cpp::RemoveTypeQualifier(nullptr, Cpp::QualKind::Const)); diff --git a/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp b/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp index e048b0df5a02c..d603737da36f2 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp @@ -1,26 +1,62 @@ #include "Utils.h" -#include "CppInterOp/CppInterOp.h" - #include "clang/AST/Decl.h" #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" +#include "llvm/TargetParser/Triple.h" + #include +#include #include #include using namespace clang; using namespace llvm; +#if defined(ENABLE_DISPATCH_TESTS) +#define DISPATCH_API(name, type) CppAPIType::name Cpp::name = nullptr; +CPPINTEROP_API_TABLE +#undef DISPATCH_API +namespace { +struct DispatchInitializer { + DispatchInitializer() { + if (!Cpp::LoadDispatchAPI(CPPINTEROP_LIB_PATH)) { + std::abort(); + } + } + ~DispatchInitializer() { Cpp::UnloadDispatchAPI(); } + DispatchInitializer(const DispatchInitializer&) = delete; + DispatchInitializer& operator=(const DispatchInitializer&) = delete; + DispatchInitializer(DispatchInitializer&&) noexcept = default; + DispatchInitializer& operator=(DispatchInitializer&&) noexcept = default; +}; +// FIXME: Make this threadsafe by moving it as a function static. +DispatchInitializer g_dispatch_init; +} // namespace +#endif + +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 */, const std::vector& interpreter_args /* = {} */) { - Cpp::CreateInterpreter(interpreter_args); + Cpp::CreateInterpreter(interpreter_args, {}); #ifdef CPPINTEROP_USE_CLING cling::Transaction *T = nullptr; Interp->declare(code, &T); @@ -57,6 +93,15 @@ void TestUtils::GetAllSubDecls(Decl *D, std::vector& SubDecls, } } +bool IsTargetX86() { +#ifndef CPPINTEROP_USE_CLING + llvm::Triple triple(Interp->getCompilerInstance()->getTargetOpts().Triple); +#else + llvm::Triple triple(Interp->getCI()->getTargetOpts().Triple); +#endif + return triple.isX86(); +} + const char* get_c_string(CXString string) { return static_cast(string.data); } diff --git a/interpreter/CppInterOp/unittests/CppInterOp/Utils.h b/interpreter/CppInterOp/unittests/CppInterOp/Utils.h index 2b7b12590cc05..be7c62cddcdc1 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/Utils.h +++ b/interpreter/CppInterOp/unittests/CppInterOp/Utils.h @@ -6,20 +6,62 @@ #include "clang-c/CXCppInterOp.h" #include "clang-c/CXString.h" +#if defined(ENABLE_DISPATCH_TESTS) +#include "CppInterOp/Dispatch.h" +#define CPPINTEROP_TEST_MODE CppInterOpDispatchTest +// Helper macros that conditionally pass default arguments in dispatch mode +// tests +#define DFLT_OP_ARITY , Cpp::OperatorArity::kBoth +#define DFLT_NULLPTR , nullptr +#define DFLT_FALSE , false +#define DFLT_TRUE , true +#define DFLT_0 , 0 +#define DFLT_1 , 1 +#else +#include "CppInterOp/CppInterOp.h" +#define CPPINTEROP_TEST_MODE CppInterOpTest +#define DFLT_OP_ARITY +#define DFLT_NULLPTR +#define DFLT_FALSE +#define DFLT_TRUE +#define DFLT_0 +#define DFLT_1 +#endif + #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, @@ -34,4 +76,55 @@ void dispose_string(CXString string); 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 CPPINTEROP_TEST_MODE : 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(CPPINTEROP_TEST_MODE, CppInterOpTestTypes, + JITConfigNameGenerator); + #endif // CPPINTEROP_UNITTESTS_LIBCPPINTEROP_UTILS_H diff --git a/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp index e6bde0e8d5b72..9dc346da47f26 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp @@ -16,7 +16,7 @@ using namespace TestUtils; using namespace llvm; using namespace clang; -TEST(VariableReflectionTest, GetDatamembers) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_GetDatamembers) { std::vector Decls; std::string code = R"( class C { @@ -113,7 +113,7 @@ TEST(VariableReflectionTest, GetDatamembers) { CODE -TEST(VariableReflectionTest, DatamembersWithAnonymousStructOrUnion) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_DatamembersWithAnonymousStructOrUnion) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -136,33 +136,35 @@ TEST(VariableReflectionTest, DatamembersWithAnonymousStructOrUnion) { EXPECT_EQ(datamembers_klass1.size(), 3); EXPECT_EQ(datamembers_klass2.size(), 3); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[0]), 0); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[1]), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[0] DFLT_0), 0); + // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[1] DFLT_0), ((intptr_t) & (k1.a)) - ((intptr_t) & (k1.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[2]), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[2] DFLT_0), ((intptr_t) & (k1.b)) - ((intptr_t) & (k1.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[0]), 0); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[1]), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[0] DFLT_0), 0); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[1] DFLT_0), ((intptr_t) & (k2.a)) - ((intptr_t) & (k2.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[2]), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[2] DFLT_0), ((intptr_t) & (k2.b)) - ((intptr_t) & (k2.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[0]), 0); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[1]), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[0] DFLT_0), 0); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[1] DFLT_0), ((intptr_t) & (k3.a)) - ((intptr_t) & (k3.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[2]), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[2] DFLT_0), ((intptr_t) & (k3.b)) - ((intptr_t) & (k3.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[3]), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[3] DFLT_0), ((intptr_t) & (k3.c)) - ((intptr_t) & (k3.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[4]), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[4] DFLT_0), ((intptr_t) & (k3.num2)) - ((intptr_t) & (k3.num))); + // NOLINTEND(cppcoreguidelines-pro-type-union-access) #ifdef _WIN32 #pragma warning(default : 4201) #endif } -TEST(VariableReflectionTest, GetTypeAsString) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_GetTypeAsString) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -180,8 +182,8 @@ TEST(VariableReflectionTest, GetTypeAsString) { } )"; - Cpp::CreateInterpreter(); - EXPECT_EQ(Cpp::Declare(code.c_str()), 0); + TestFixture::CreateInterpreter(); + EXPECT_EQ(Cpp::Declare(code.c_str() DFLT_FALSE), 0); Cpp::TCppScope_t wrapper = Cpp::GetScopeFromCompleteName("my_namespace::Wrapper"); @@ -195,7 +197,7 @@ TEST(VariableReflectionTest, GetTypeAsString) { "my_namespace::Container"); } -TEST(VariableReflectionTest, LookupDatamember) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_LookupDatamember) { std::vector Decls; std::string code = R"( class C { @@ -219,7 +221,7 @@ TEST(VariableReflectionTest, LookupDatamember) { EXPECT_EQ(Cpp::GetQualifiedName(Cpp::LookupDatamember("k", Decls[0])), ""); } -TEST(VariableReflectionTest, GetVariableType) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_GetVariableType) { std::vector Decls; std::string code = R"( class C {}; @@ -268,7 +270,7 @@ TEST(VariableReflectionTest, GetVariableType) { CODE -TEST(VariableReflectionTest, GetVariableOffset) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_GetVariableOffset) { #ifdef EMSCRIPTEN #if CLANG_VERSION_MAJOR < 20 GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -287,63 +289,62 @@ TEST(VariableReflectionTest, GetVariableOffset) { std::vector datamembers; Cpp::GetDatamembers(Decls[4], datamembers); - EXPECT_TRUE((bool) Cpp::GetVariableOffset(Decls[0])); // a - EXPECT_TRUE((bool) Cpp::GetVariableOffset(Decls[1])); // N - EXPECT_TRUE((bool)Cpp::GetVariableOffset(Decls[2])); // S - EXPECT_TRUE((bool)Cpp::GetVariableOffset(Decls[3])); // SN + EXPECT_TRUE((bool)Cpp::GetVariableOffset(Decls[0] DFLT_0)); // a + EXPECT_TRUE((bool)Cpp::GetVariableOffset(Decls[1] DFLT_0)); // N + EXPECT_TRUE((bool)Cpp::GetVariableOffset(Decls[2] DFLT_0)); // S + EXPECT_TRUE((bool)Cpp::GetVariableOffset(Decls[3] DFLT_0)); // SN - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[0]), 0); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[0] DFLT_0), 0); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[1]), - ((intptr_t) &(c.b)) - ((intptr_t) &(c.a))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[2]), - ((intptr_t) &(c.c)) - ((intptr_t) &(c.a))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[3]), - ((intptr_t) &(c.d)) - ((intptr_t) &(c.a))); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[1] DFLT_0), + ((intptr_t) & (c.b)) - ((intptr_t) & (c.a))); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[2] DFLT_0), + ((intptr_t) & (c.c)) - ((intptr_t) & (c.a))); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[3] DFLT_0), + ((intptr_t) & (c.d)) - ((intptr_t) & (c.a))); auto* VD_C_s_a = Cpp::GetNamed("s_a", Decls[4]); // C::s_a - EXPECT_TRUE((bool) Cpp::GetVariableOffset(VD_C_s_a)); + EXPECT_TRUE((bool)Cpp::GetVariableOffset(VD_C_s_a DFLT_0)); struct K { int x; int y; int z; }; - Cpp::Declare("struct K;"); - Cpp::TCppScope_t k = Cpp::GetNamed("K"); + Cpp::Declare("struct K;" DFLT_FALSE); + Cpp::TCppScope_t k = Cpp::GetNamed("K" DFLT_NULLPTR); EXPECT_TRUE(k); - Cpp::Declare("struct K { int x; int y; int z; };"); - + Cpp::Declare("struct K { int x; int y; int z; };" DFLT_FALSE); datamembers.clear(); Cpp::GetDatamembers(k, datamembers); EXPECT_EQ(datamembers.size(), 3); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[0]), offsetof(K, x)); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[1]), offsetof(K, y)); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[2]), offsetof(K, z)); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[0] DFLT_0), offsetof(K, x)); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[1] DFLT_0), offsetof(K, y)); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[2] DFLT_0), offsetof(K, z)); Cpp::Declare(R"( template struct ClassWithStatic { static T const ref_value; }; template T constexpr ClassWithStatic::ref_value = 42; - )"); + )" DFLT_FALSE); - Cpp::TCppScope_t klass = Cpp::GetNamed("ClassWithStatic"); + Cpp::TCppScope_t klass = Cpp::GetNamed("ClassWithStatic" DFLT_NULLPTR); EXPECT_TRUE(klass); ASTContext& C = Interp->getCI()->getASTContext(); std::vector template_args = { {C.IntTy.getAsOpaquePtr()}}; Cpp::TCppScope_t klass_instantiated = Cpp::InstantiateTemplate( - klass, template_args.data(), template_args.size()); + klass, template_args.data(), template_args.size() DFLT_FALSE); EXPECT_TRUE(klass_instantiated); Cpp::TCppScope_t var = Cpp::GetNamed("ref_value", klass_instantiated); EXPECT_TRUE(var); - EXPECT_TRUE(Cpp::GetVariableOffset(var)); + EXPECT_TRUE(Cpp::GetVariableOffset(var DFLT_0)); } #define CODE \ @@ -379,27 +380,30 @@ TEST(VariableReflectionTest, GetVariableOffset) { CODE -TEST(VariableReflectionTest, VariableOffsetsWithInheritance) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_VariableOffsetsWithInheritance) { #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"; +#endif +#ifdef __EMSCRIPTEN__ + GTEST_SKIP() << "This test crashes for Emscripten builds of CppInterOp"; #endif 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); - Cpp::Declare("#include"); + Cpp::Declare("#include" DFLT_FALSE); #define Stringify(s) Stringifyx(s) #define Stringifyx(...) #__VA_ARGS__ - Cpp::Declare(Stringify(CODE)); + Cpp::Declare(Stringify(CODE) DFLT_FALSE); #undef Stringifyx #undef Stringify #undef CODE - Cpp::TCppScope_t myklass = Cpp::GetNamed("MyKlass"); + Cpp::TCppScope_t myklass = Cpp::GetNamed("MyKlass" DFLT_NULLPTR); EXPECT_TRUE(myklass); size_t num_bases = Cpp::GetNumBases(myklass); @@ -435,7 +439,7 @@ TEST(VariableReflectionTest, VariableOffsetsWithInheritance) { ((intptr_t)&(my_k.s)) - ((intptr_t)&(my_k))); } -TEST(VariableReflectionTest, IsPublicVariable) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_IsPublicVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -458,7 +462,7 @@ TEST(VariableReflectionTest, IsPublicVariable) { EXPECT_FALSE(Cpp::IsPublicVariable(SubDecls[7])); } -TEST(VariableReflectionTest, IsProtectedVariable) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_IsProtectedVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -479,7 +483,7 @@ TEST(VariableReflectionTest, IsProtectedVariable) { EXPECT_TRUE(Cpp::IsProtectedVariable(SubDecls[6])); } -TEST(VariableReflectionTest, IsPrivateVariable) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_IsPrivateVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -500,7 +504,7 @@ TEST(VariableReflectionTest, IsPrivateVariable) { EXPECT_FALSE(Cpp::IsPrivateVariable(SubDecls[6])); } -TEST(VariableReflectionTest, IsStaticVariable) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_IsStaticVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -516,7 +520,7 @@ TEST(VariableReflectionTest, IsStaticVariable) { EXPECT_TRUE(Cpp::IsStaticVariable(SubDecls[2])); } -TEST(VariableReflectionTest, IsConstVariable) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_IsConstVariable) { std::vector Decls, SubDecls; std::string code = R"( class C { @@ -533,7 +537,8 @@ TEST(VariableReflectionTest, IsConstVariable) { EXPECT_TRUE(Cpp::IsConstVariable(SubDecls[2])); } -TEST(VariableReflectionTest, DISABLED_GetArrayDimensions) { +TYPED_TEST(CPPINTEROP_TEST_MODE, + VariableReflection_DISABLED_GetArrayDimensions) { std::vector Decls; std::string code = R"( int a; @@ -557,7 +562,7 @@ TEST(VariableReflectionTest, DISABLED_GetArrayDimensions) { // EXPECT_TRUE(is_vec_eq(Cpp::GetArrayDimensions(Decls[2]), {1,2})); } -TEST(VariableReflectionTest, StaticConstExprDatamember) { +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_StaticConstExprDatamember) { if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; @@ -565,7 +570,7 @@ TEST(VariableReflectionTest, StaticConstExprDatamember) { GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif - Cpp::CreateInterpreter(); + TestFixture::CreateInterpreter(); Cpp::Declare(R"( class MyClass { @@ -588,40 +593,40 @@ TEST(VariableReflectionTest, StaticConstExprDatamember) { template struct Elements : public integral_constant {}; - )"); + )" DFLT_FALSE); - Cpp::TCppScope_t MyClass = Cpp::GetNamed("MyClass"); + Cpp::TCppScope_t MyClass = Cpp::GetNamed("MyClass" DFLT_NULLPTR); EXPECT_TRUE(MyClass); std::vector datamembers; Cpp::GetStaticDatamembers(MyClass, datamembers); EXPECT_EQ(datamembers.size(), 1); - intptr_t offset = Cpp::GetVariableOffset(datamembers[0]); + intptr_t offset = Cpp::GetVariableOffset(datamembers[0] DFLT_0); EXPECT_EQ(3, *(size_t*)offset); ASTContext& C = Interp->getCI()->getASTContext(); std::vector template_args = { {C.IntTy.getAsOpaquePtr(), "5"}}; - Cpp::TCppFunction_t MyTemplatedClass = - Cpp::InstantiateTemplate(Cpp::GetNamed("MyTemplatedClass"), - template_args.data(), template_args.size()); + Cpp::TCppFunction_t MyTemplatedClass = Cpp::InstantiateTemplate( + Cpp::GetNamed("MyTemplatedClass" DFLT_NULLPTR), template_args.data(), + template_args.size() DFLT_FALSE); EXPECT_TRUE(MyTemplatedClass); datamembers.clear(); Cpp::GetStaticDatamembers(MyTemplatedClass, datamembers); EXPECT_EQ(datamembers.size(), 1); - offset = Cpp::GetVariableOffset(datamembers[0]); + offset = Cpp::GetVariableOffset(datamembers[0] DFLT_0); EXPECT_EQ(5, *(size_t*)offset); std::vector ele_template_args = { {C.IntTy.getAsOpaquePtr()}, {C.FloatTy.getAsOpaquePtr()}}; Cpp::TCppFunction_t Elements = Cpp::InstantiateTemplate( - Cpp::GetNamed("Elements"), ele_template_args.data(), - ele_template_args.size()); + Cpp::GetNamed("Elements" DFLT_NULLPTR), ele_template_args.data(), + ele_template_args.size() DFLT_FALSE); EXPECT_TRUE(Elements); EXPECT_EQ(1, Cpp::GetNumBases(Elements)); @@ -632,12 +637,13 @@ TEST(VariableReflectionTest, StaticConstExprDatamember) { Cpp::GetStaticDatamembers(IC, datamembers); EXPECT_EQ(datamembers.size(), 1); - offset = Cpp::GetVariableOffset(datamembers[0]); + offset = Cpp::GetVariableOffset(datamembers[0] DFLT_0); EXPECT_EQ(2, *(size_t*)offset); } -TEST(VariableReflectionTest, GetEnumConstantDatamembers) { - Cpp::CreateInterpreter(); +TYPED_TEST(CPPINTEROP_TEST_MODE, + VariableReflection_GetEnumConstantDatamembers) { + TestFixture::CreateInterpreter(); Cpp::Declare(R"( class MyEnumClass { @@ -645,13 +651,13 @@ TEST(VariableReflectionTest, GetEnumConstantDatamembers) { enum A { ONE, TWO, THREE }; enum class B { SEVEN, EIGHT, NINE }; }; - )"); + )" DFLT_FALSE); - Cpp::TCppScope_t MyEnumClass = Cpp::GetNamed("MyEnumClass"); + Cpp::TCppScope_t MyEnumClass = Cpp::GetNamed("MyEnumClass" DFLT_NULLPTR); EXPECT_TRUE(MyEnumClass); std::vector datamembers; - Cpp::GetEnumConstantDatamembers(MyEnumClass, datamembers); + Cpp::GetEnumConstantDatamembers(MyEnumClass, datamembers DFLT_TRUE); EXPECT_EQ(datamembers.size(), 9); EXPECT_TRUE(Cpp::IsEnumType(Cpp::GetVariableType(datamembers[0]))); @@ -660,8 +666,8 @@ TEST(VariableReflectionTest, GetEnumConstantDatamembers) { EXPECT_EQ(datamembers2.size(), 6); } -TEST(VariableReflectionTest, Is_Get_Pointer) { - Cpp::CreateInterpreter(); +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_Is_Get_Pointer) { + TestFixture::CreateInterpreter(); std::vector Decls; std::string code = R"( class A {}; @@ -692,8 +698,8 @@ TEST(VariableReflectionTest, Is_Get_Pointer) { EXPECT_FALSE(Cpp::GetPointeeType(Cpp::GetVariableType(Decls[5]))); } -TEST(VariableReflectionTest, Is_Get_Reference) { - Cpp::CreateInterpreter(); +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_Is_Get_Reference) { + TestFixture::CreateInterpreter(); std::vector Decls; std::string code = R"( class A {}; @@ -723,15 +729,19 @@ TEST(VariableReflectionTest, Is_Get_Reference) { EXPECT_FALSE(Cpp::GetNonReferenceType(Cpp::GetVariableType(Decls[5]))); - EXPECT_TRUE(Cpp::IsLValueReferenceType(Cpp::GetVariableType(Decls[2]))); - EXPECT_EQ(Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1])), + EXPECT_EQ(Cpp::GetValueKind(Cpp::GetVariableType(Decls[2])), + Cpp::ValueKind::LValue); + EXPECT_EQ(Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1]) DFLT_FALSE), Cpp::GetVariableType(Decls[2])); - EXPECT_TRUE(Cpp::IsRValueReferenceType( - Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1]), true))); + EXPECT_EQ(Cpp::GetValueKind( + Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1]), true)), + Cpp::ValueKind::RValue); + EXPECT_EQ(Cpp::GetValueKind(Cpp::GetVariableType(Decls[1])), + Cpp::ValueKind::None); } -TEST(VariableReflectionTest, GetPointerType) { - Cpp::CreateInterpreter(); +TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_GetPointerType) { + TestFixture::CreateInterpreter(); std::vector Decls; std::string code = R"( class A {}; From f78ea8446cf99f4a8aea466c03de900640caeff6 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 3 Feb 2026 12:01:42 +0100 Subject: [PATCH 2/2] fix alma8: link against stdc++fs if GCC8 --- interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt index 0383055cd2481..abdb6d3f9a026 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt @@ -54,6 +54,10 @@ if(NOT WIN32 AND NOT EMSCRIPTEN) list(APPEND link_libs dl) endif() +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + list(APPEND link_libs stdc++fs) +endif() + # Get rid of libLLVM-X.so which is appended to the list of static libraries. if (LLVM_LINK_LLVM_DYLIB) set(new_libs ${link_libs})