From 3b088c81d13be9019709c410e05ccd5f9866c6f1 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 16 May 2026 12:43:30 +0200 Subject: [PATCH 1/4] [cppinterop] Upgrade to latest --- interpreter/CppInterOp/.readthedocs.yaml | 1 + interpreter/CppInterOp/CMakeLists.txt | 113 +- .../Emscripten-build-instructions.md | 68 +- interpreter/CppInterOp/README.md | 8 +- .../CppInterOp/cmake/FindLLVMOrcRuntime.cmake | 51 + .../CppInterOp/cmake/modules/GoogleTest.cmake | 28 +- interpreter/CppInterOp/cppinterop-version.tag | 2 +- .../docs/DevelopersDocumentation.rst | 6 +- .../docs/Emscripten-build-instructions.rst | 68 +- .../CppInterOp/docs/InstallationAndUsage.rst | 6 +- interpreter/CppInterOp/docs/conf.py | 2 + interpreter/CppInterOp/docs/doxygen.cfg.in | 3 +- .../CppInterOp/etc/clang20-valgrind.supp | 18 - .../CppInterOp/etc/clang21-valgrind.supp | 65 + .../CppInterOp/etc/clang22-valgrind.supp | 219 +++ .../include/CppInterOp/CppInterOp.h | 1033 +---------- .../include/CppInterOp/CppInterOpTypes.h | 425 +++++ .../CppInterOp/include/CppInterOp/Dispatch.h | 223 +-- .../CppInterOp/include/clang-c/CXCppInterOp.h | 472 ----- .../lib/CppInterOp/BuildInfo.inc.in | 13 + .../CppInterOp/lib/CppInterOp/CMakeLists.txt | 111 +- .../lib/CppInterOp/CXCppInterOp.cpp | 630 +------ .../lib/CppInterOp/CXCppInterOpGenerated.cpp | 15 + .../CppInterOp/lib/CppInterOp/Compatibility.h | 156 +- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 459 ++++- .../CppInterOp/lib/CppInterOp/CppInterOp.td | 1562 +++++++++++++++++ .../lib/CppInterOp/CppInterOpAPI.td | 205 +++ .../lib/CppInterOp/CppInterOpDispatch.cpp | 46 + .../lib/CppInterOp/CppInterOpInterpreter.h | 31 +- .../CppInterOp/lib/CppInterOp/Dispatch.cpp | 26 - .../CppInterOp/lib/CppInterOp/Tracing.cpp | 171 +- .../CppInterOp/lib/CppInterOp/Tracing.h | 470 ++++- .../CppInterOp/lib/CppInterOp/exports.ld | 2 + interpreter/CppInterOp/patches/llvm/README.md | 1 - .../llvm/clang20-1-out-of-process.patch | 966 ---------- .../patches/llvm/cling1.2-LookupHelper.patch | 42 - ...0-2-shift-temporary-files-to-tmp-dir.patch | 15 - ...-clang20-3-enable_exception_handling.patch | 67 - ...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 | 32 +- .../unittests/CppInterOp/CAPITest.cpp | 299 ++++ .../unittests/CppInterOp/CAPITestC.c | 53 + .../unittests/CppInterOp/CMakeLists.txt | 90 +- .../unittests/CppInterOp/CUDATest.cpp | 21 +- .../CppInterOp/CommentReflectionTest.cpp | 17 - .../unittests/CppInterOp/DispatchInit.cpp | 25 + .../CppInterOp/DispatchSmokeTest.cpp | 241 +++ .../unittests/CppInterOp/DispatchTest.cpp | 92 +- .../CppInterOp/FunctionReflectionTest.cpp | 441 ++--- .../unittests/CppInterOp/InterpreterTest.cpp | 261 ++- .../unittests/CppInterOp/JitTest.cpp | 4 - .../CppInterOp/ScopeReflectionTest.cpp | 245 ++- .../TestDownstreamLib/CMakeLists.txt | 17 + .../TestDownstreamLib/TestDownstreamLib.cpp | 33 + .../TestDownstreamLib/TestDownstreamLib.h | 22 + .../CppInterOp/TestDownstreamLoader.cpp | 56 + .../unittests/CppInterOp/TracingTests.cpp | 806 ++++++++- .../CppInterOp/TypeReflectionTest.cpp | 110 +- .../CppInterOp/unittests/CppInterOp/Utils.cpp | 39 - .../CppInterOp/unittests/CppInterOp/Utils.h | 67 +- .../CppInterOp/VariableReflectionTest.cpp | 104 +- .../CppInterOp/utils/TableGen/CMakeLists.txt | 6 + .../utils/TableGen/CppInterOpEmitter.cpp | 535 ++++++ .../CppInterOp/utils/TableGen/TableGen.cpp | 70 + 66 files changed, 7036 insertions(+), 4578 deletions(-) create mode 100644 interpreter/CppInterOp/cmake/FindLLVMOrcRuntime.cmake delete mode 100644 interpreter/CppInterOp/etc/clang20-valgrind.supp create mode 100644 interpreter/CppInterOp/etc/clang21-valgrind.supp create mode 100644 interpreter/CppInterOp/etc/clang22-valgrind.supp create mode 100644 interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h delete mode 100644 interpreter/CppInterOp/include/clang-c/CXCppInterOp.h create mode 100644 interpreter/CppInterOp/lib/CppInterOp/BuildInfo.inc.in create mode 100644 interpreter/CppInterOp/lib/CppInterOp/CXCppInterOpGenerated.cpp create mode 100644 interpreter/CppInterOp/lib/CppInterOp/CppInterOp.td create mode 100644 interpreter/CppInterOp/lib/CppInterOp/CppInterOpAPI.td create mode 100644 interpreter/CppInterOp/lib/CppInterOp/CppInterOpDispatch.cpp delete mode 100644 interpreter/CppInterOp/lib/CppInterOp/Dispatch.cpp delete mode 100644 interpreter/CppInterOp/patches/llvm/README.md delete mode 100644 interpreter/CppInterOp/patches/llvm/clang20-1-out-of-process.patch delete mode 100644 interpreter/CppInterOp/patches/llvm/cling1.2-LookupHelper.patch delete mode 100644 interpreter/CppInterOp/patches/llvm/emscripten-clang20-2-shift-temporary-files-to-tmp-dir.patch delete mode 100644 interpreter/CppInterOp/patches/llvm/emscripten-clang20-3-enable_exception_handling.patch delete mode 100644 interpreter/CppInterOp/patches/llvm/emscripten-clang21-1-shift-temporary-files-to-tmp-dir.patch delete mode 100644 interpreter/CppInterOp/patches/llvm/emscripten-clang21-2-enable_exception_handling.patch delete mode 100644 interpreter/CppInterOp/patches/llvm/emscripten-clang21-3-webassembly_target_machine_reordering.patch create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/CAPITest.cpp create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/CAPITestC.c create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/DispatchInit.cpp create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/DispatchSmokeTest.cpp create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/CMakeLists.txt create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/TestDownstreamLib.cpp create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/TestDownstreamLib.h create mode 100644 interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLoader.cpp create mode 100644 interpreter/CppInterOp/utils/TableGen/CMakeLists.txt create mode 100644 interpreter/CppInterOp/utils/TableGen/CppInterOpEmitter.cpp create mode 100644 interpreter/CppInterOp/utils/TableGen/TableGen.cpp diff --git a/interpreter/CppInterOp/.readthedocs.yaml b/interpreter/CppInterOp/.readthedocs.yaml index a5b6503c20215..8d6637fb7dade 100644 --- a/interpreter/CppInterOp/.readthedocs.yaml +++ b/interpreter/CppInterOp/.readthedocs.yaml @@ -11,6 +11,7 @@ build: apt_packages: - clang-20 - cmake + - doxygen - libclang-20-dev - llvm-20-dev - llvm-20-tools diff --git a/interpreter/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/CMakeLists.txt index 38f7f1c93b482..0a14b6a9d7107 100644 --- a/interpreter/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/CMakeLists.txt @@ -12,6 +12,11 @@ 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) +option(CPPINTEROP_BUILD_TABLEGEN_ONLY "Only build cppinterop-tblgen (for cross-compilation)" OFF) + +#=Set OFF to suppress install() rules and route clangCppInterOp through +# add_llvm_library(... BUILDTREE_ONLY) +option(CPPINTEROP_INSTALL "Generate install rules for CppInterOp" ON) if(EMSCRIPTEN) set(CPPINTEROP_EXTRA_WASM_FLAGS "-fwasm-exceptions" CACHE STRING "Extra flags for wasm") endif() @@ -24,6 +29,19 @@ endif() if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) project(CppInterOp) + # When only building cppinterop-tblgen for cross-compilation, skip + # all find_package calls and library/test subdirectories. Only needs + # LLVM_DIR pointing to a native LLVM build with libLLVMTableGen. + if(CPPINTEROP_BUILD_TABLEGEN_ONLY) + find_package(LLVM REQUIRED CONFIG) + include_directories(${LLVM_INCLUDE_DIRS}) + include(AddLLVM) + include(HandleLLVMOptions) + include(TableGen) + add_subdirectory(utils/TableGen) + return() + endif() + if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(WARNING "No build type selected. Defaulted CMAKE_BUILD_TYPE=Release.\n" "Valid options are: None, Debug, Release, RelWithDebInfo, MinSizeRel.") @@ -300,10 +318,18 @@ endif() include(AddLLVM) include(HandleLLVMOptions) + include(TableGen) endif() set(CMAKE_INCLUDE_CURRENT_DIR ON) +# Generated .inc files from cppinterop-tblgen live under our own build +# subtree. Use CMAKE_CURRENT_BINARY_DIR so consumers embedding CppInterOP +# via add_subdirectory() (e.g. ROOT) don't write CppInterOp artifacts into +# top-level build/include +set(CPPINTEROP_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/include/CppInterOp) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + # In rare cases we might want to have clang installed in a different place # than llvm and the header files should be found first (even though the # LLVM_INCLUDE_DIRS) contain clang headers, too. @@ -321,16 +347,53 @@ 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}") +# Bundle the ORC runtime + executor inside CppInterOp's distribution +# so the OOP-JIT path is self-contained: no extra package to install, +# no resource-dir scan in the host's clang tree at runtime. Paths are +# baked at build time (CPPINTEROP_RUNTIME_{BUILD,INSTALL}_DIR in +# Compatibility.h) and probed via fs::exists. +set(CPPINTEROP_RUNTIME_SUBDIR "cppinterop-rt") +set(CPPINTEROP_RUNTIME_BUILDTREE_DIR + "${CMAKE_BINARY_DIR}/lib/${CPPINTEROP_RUNTIME_SUBDIR}") + +if(LLVM_VERSION_MAJOR GREATER 21) + find_package(LLVMOrcRuntime QUIET MODULE) +endif() -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() +if(LLVMOrcRuntime_FOUND) + message(STATUS "CppInterOp OOP-JIT runtime:") + message(STATUS " orc_rt: ${LLVMOrcRuntime_LIBRARY}") + message(STATUS " executor: ${LLVMOrcRuntime_EXECUTOR}") + + file(MAKE_DIRECTORY "${CPPINTEROP_RUNTIME_BUILDTREE_DIR}") + configure_file("${LLVMOrcRuntime_LIBRARY}" + "${CPPINTEROP_RUNTIME_BUILDTREE_DIR}/liborc_rt.a" + COPYONLY) + configure_file("${LLVMOrcRuntime_EXECUTOR}" + "${CPPINTEROP_RUNTIME_BUILDTREE_DIR}/llvm-jitlink-executor" + COPYONLY) + if(CPPINTEROP_INSTALL) + install(FILES "${LLVMOrcRuntime_LIBRARY}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/${CPPINTEROP_RUNTIME_SUBDIR}" + RENAME liborc_rt.a) + install(PROGRAMS "${LLVMOrcRuntime_EXECUTOR}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/${CPPINTEROP_RUNTIME_SUBDIR}" + RENAME llvm-jitlink-executor) + endif() + + # Bake build-tree and install-tree runtime locations into the library + # so it can locate the bundled parts at runtime without dladdr or any + # platform-specific self-DSO discovery. + add_definitions( + "-DCPPINTEROP_RUNTIME_BUILD_DIR=\"${CPPINTEROP_RUNTIME_BUILDTREE_DIR}\"" + "-DCPPINTEROP_RUNTIME_INSTALL_DIR=\"${CMAKE_INSTALL_FULL_LIBDIR}/${CPPINTEROP_RUNTIME_SUBDIR}\"") +elseif(LLVM_VERSION_MAJOR GREATER 21) + message(STATUS + "CppInterOp: OOP-JIT runtime (liborc_rt + llvm-jitlink-executor) " + "not found in ${LLVM_LIBRARY_DIR}. Out-of-process JIT support will " + "be unavailable. To enable it, build LLVM with compiler-rt and " + "ninja the llvm-jitlink-executor + orc_rt* targets, or install a " + "compiler-rt package that ships them.") endif() # If the llvm sources are present add them with higher priority. @@ -432,6 +495,35 @@ configure_file( ${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/CppInterOp/CppInterOpConfigVersion.cmake @ONLY) +if(CPPINTEROP_INSTALL) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/CppInterOp/ + DESTINATION lib/cmake/CppInterOp + FILES_MATCHING + PATTERN "*.cmake" + ) + + install(DIRECTORY include/ + DESTINATION include + FILES_MATCHING + PATTERN "*.def" + PATTERN "*.h" + PATTERN "*.inc" + PATTERN ".svn" EXCLUDE + ) + + install(DIRECTORY tools/ + DESTINATION include/CppInterOp/tools + FILES_MATCHING + PATTERN "*.h" + ) + + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/ + DESTINATION include + FILES_MATCHING + PATTERN "CMakeFiles" EXCLUDE + PATTERN "*.inc" + ) +endif() add_definitions( -D_GNU_SOURCE ) # Add deps if we build together with clang. @@ -521,6 +613,9 @@ if (CPPINTEROP_INCLUDE_DOCS) add_subdirectory(docs) endif() +if(NOT EMSCRIPTEN AND NOT CPPINTEROP_TABLEGEN_EXE) + add_subdirectory(utils/TableGen) +endif() add_subdirectory(lib) if (CPPINTEROP_ENABLE_TESTING) add_subdirectory(unittests) diff --git a/interpreter/CppInterOp/Emscripten-build-instructions.md b/interpreter/CppInterOp/Emscripten-build-instructions.md index cce73735f8c40..c35ad0a78a26f 100644 --- a/interpreter/CppInterOp/Emscripten-build-instructions.md +++ b/interpreter/CppInterOp/Emscripten-build-instructions.md @@ -5,7 +5,7 @@ It should be noted that the wasm build of CppInterOp is still experimental and s ## CppInterOp Wasm Build Instructions This document first starts with the instructions on how to build a wasm build of CppInterOp. Before we start it should be noted that -unlike the non wasm version of CppInterOp we currently only support the Clang-REPL backend using llvm>19. +unlike the non wasm version of CppInterOp we currently only support the Clang-REPL backend using llvm>20. We will first make folder to build our wasm build of CppInterOp. This can be done by executing the following command ```bash @@ -42,11 +42,11 @@ $env:PWD_DIR= $PWD.Path $env:SYSROOT_PATH="$env:EMSDK/upstream/emscripten/cache/sysroot" ``` -Now clone the 21.x release of the LLVM project repository and CppInterOp (the building of the emscripten version of llvm can be +Now clone the 22.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/21.x https://github.com/llvm/llvm-project.git +git clone --depth=1 --branch release/22.x https://github.com/llvm/llvm-project.git git clone --depth=1 https://github.com/compiler-research/CppInterOp.git ``` @@ -55,17 +55,16 @@ executing ```bash cd ./llvm-project/ -git apply -v ../CppInterOp/patches/llvm/emscripten-clang21-*.patch +git apply -v ../CppInterOp/patches/llvm/emscripten-clang22-*.patch ``` On Windows execute the following ```powershell cd .\llvm-project\ -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 +cp -r ..\patches\llvm\emscripten-clang22* +git apply -v emscripten-clang22-1-enable_exception_handling.patch +git apply -v emscripten-clang22-2-webassembly_target_machine_reordering.patch ``` We are now in a position to build an emscripten build of llvm by executing the following on Linux @@ -75,7 +74,8 @@ mkdir native_build cd native_build cmake -DLLVM_ENABLE_PROJECTS=clang -DLLVM_TARGETS_TO_BUILD=host -DCMAKE_BUILD_TYPE=Release ../llvm/ cmake --build . --target llvm-tblgen clang-tblgen --parallel $(nproc --all) -export NATIVE_DIR=$PWD/bin/ +export NATIVE_LLVM_BUILD_DIR=$PWD +export NATIVE_LLVM_BIN_DIR=$PWD/bin/ cd .. mkdir build cd build @@ -97,7 +97,7 @@ emcmake cmake -DCMAKE_BUILD_TYPE=Release \ -DLLVM_BUILD_TOOLS=OFF \ -DLLVM_ENABLE_LIBPFM=OFF \ -DCLANG_BUILD_TOOLS=OFF \ - -DLLVM_NATIVE_TOOL_DIR=$NATIVE_DIR \ + -DLLVM_NATIVE_TOOL_DIR=$NATIVE_LLVM_BIN_DIR \ -DCMAKE_C_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" \ -DCMAKE_CXX_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" \ -DLLVM_ENABLE_LTO=Full \ @@ -113,7 +113,8 @@ cd native_build cmake -DLLVM_ENABLE_PROJECTS=clang -DLLVM_TARGETS_TO_BUILD=host -DCMAKE_BUILD_TYPE=Release -G Ninja ../llvm/ cmake --build . --target llvm-tblgen clang-tblgen --parallel $(nproc --all) $env:PWD_DIR= $PWD.Path -$env:NATIVE_DIR="$env:PWD_DIR/bin/" +$env:NATIVE_LLVM_BUILD_DIR="$env:PWD_DIR" +$env:NATIVE_LLVM_BIN_DIR="$env:PWD_DIR/bin/" cd .. mkdir build cd build @@ -135,7 +136,7 @@ emcmake cmake -DCMAKE_BUILD_TYPE=Release ` -DLLVM_BUILD_TOOLS=OFF ` -DLLVM_ENABLE_LIBPFM=OFF ` -DCLANG_BUILD_TOOLS=OFF ` - -DLLVM_NATIVE_TOOL_DIR="$env:NATIVE_DIR" ` + -DLLVM_NATIVE_TOOL_DIR="$env:NATIVE_LLVM_BIN_DIR" ` -G Ninja ` -DCMAKE_C_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" ` -DCMAKE_CXX_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" ` @@ -184,7 +185,42 @@ $env:CMAKE_PREFIX_PATH=$env:PREFIX $env:CMAKE_SYSTEM_PREFIX_PATH=$env:PREFIX ``` -on Windows. Now to build and test your Emscripten build of CppInterOp using node on Linux and osx execute the following +on Windows. efore building the Emscripten version of CppInterOp, we need +to build ``cppinterop-tblgen`` natively. This tool generates ``.inc`` files +from ``.td`` definitions and must run on the host (not under Emscripten). +We use the native LLVM build from the earlier step since it has the required +``libLLVMTableGen`` library. + +On Linux and osx: + +```bash +mkdir -p native_cppinterop_build && cd native_cppinterop_build +cmake -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_DIR=$NATIVE_LLVM_BUILD_DIR/lib/cmake/llvm \ + -DCMAKE_CXX_STANDARD=17 \ + -DCPPINTEROP_BUILD_TABLEGEN_ONLY=ON \ + ../ +cmake --build . --target cppinterop-tblgen -j $(nproc --all) +export CPPINTEROP_TBLGEN_EXE=$(find $PWD -name cppinterop-tblgen -type f | head -1) +cd .. +``` + +On Windows: + +```powershell +mkdir native_cppinterop_build +cd native_cppinterop_build +cmake -DCMAKE_BUILD_TYPE=Release ` + -DLLVM_DIR="$env:NATIVE_LLVM_BUILD_DIR\lib\cmake\llvm" ` + -DCMAKE_CXX_STANDARD=17 ` + -DCPPINTEROP_BUILD_TABLEGEN_ONLY=ON ` +..\ +cmake --build . --target cppinterop-tblgen -j $(nproc --all) +$env:CPPINTEROP_TBLGEN_EXE = (Get-ChildItem -Recurse -Filter "cppinterop-tblgen.exe" | Select-Object -First 1).FullName +cd .. +``` + +Now to build and test your Emscripten build of CppInterOp using node on Linux and osx execute the following (BUILD_SHARED_LIBS=ON is only needed if building xeus-cpp, as CppInterOp can be built as an Emscripten static library) ```bash @@ -198,6 +234,7 @@ emcmake cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DSYSROOT_PATH=$SYSROOT_PATH \ + -DCPPINTEROP_TABLEGEN_EXE=$CPPINTEROP_TBLGEN_EXE \ ../ emmake make -j $(nproc --all) check-cppinterop ``` @@ -216,6 +253,7 @@ emcmake cmake -DCMAKE_BUILD_TYPE=Release ` -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON ` -DLLVM_ENABLE_WERROR=On ` -DSYSROOT_PATH="$env:SYSROOT_PATH" ` + -DCPPINTEROP_TABLEGEN_EXE="$env:CPPINTEROP_TBLGEN_EXE" ` ..\ emmake make -j $(nproc --all) check-cppinterop ``` @@ -342,7 +380,7 @@ of llvm you are building against) ```bash cd ../.. git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git -export LLVM_VERSION=21 +export LLVM_VERSION=22 cd ./xeus-cpp mkdir build cd build @@ -363,7 +401,7 @@ and on Windows by executing ```powershell cd ..\.. git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git -$env:LLVM_VERSION=21 +$env:LLVM_VERSION=22 cd .\xeus-cpp mkdir build cd build diff --git a/interpreter/CppInterOp/README.md b/interpreter/CppInterOp/README.md index 5198e99d240fa..f9b8aee994ace 100644 --- a/interpreter/CppInterOp/README.md +++ b/interpreter/CppInterOp/README.md @@ -213,9 +213,11 @@ cmake -DBUILD_SHARED_LIBS=ON -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_D 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. +> Out-of-process JIT auto-enables when CppInterOp is built against +> LLVM 22+ with compiler-rt's ORC runtime available +> (`liborc_rt*.a` and `llvm-jitlink-executor`). When detected, the +> two artifacts are bundled under `/cppinterop-rt/` so the +> distribution is self-contained. #### Testing CppInterOp diff --git a/interpreter/CppInterOp/cmake/FindLLVMOrcRuntime.cmake b/interpreter/CppInterOp/cmake/FindLLVMOrcRuntime.cmake new file mode 100644 index 0000000000000..707d5d2b216dc --- /dev/null +++ b/interpreter/CppInterOp/cmake/FindLLVMOrcRuntime.cmake @@ -0,0 +1,51 @@ +# Locates the ORC runtime archive (compiler-rt's `orc_rt`) and the +# `llvm-jitlink-executor` binary inside the LLVM tree we are linking +# against. Used to bundle them into CppInterOp's distribution so the +# OOP-JIT path works without an external `compiler-rt` install. +# +# Expects find_package(LLVM) to have run already (provides the input +# variables below). +# +# Inputs: +# LLVM_LIBRARY_DIR +# LLVM_TOOLS_BINARY_DIR +# LLVM_VERSION_MAJOR +# +# Output cache variables: +# LLVMOrcRuntime_FOUND +# LLVMOrcRuntime_LIBRARY Path to libclang_rt.orc_rt*.a / liborc_rt*.a +# LLVMOrcRuntime_EXECUTOR Path to llvm-jitlink-executor + +include(FindPackageHandleStandardArgs) + +# compiler-rt installs into `clang//lib//`; the triple +# subdir is host-derived (`darwin`, `x86_64-unknown-linux-gnu`, etc.). +# Enumerate them at configure time rather than hard-coding the triple. +file(GLOB _orc_hint_dirs LIST_DIRECTORIES TRUE + "${LLVM_LIBRARY_DIR}/clang/${LLVM_VERSION_MAJOR}/lib/*") + +# NAMES covers the spread of compiler-rt archive names across versions +# and platforms: `libclang_rt.orc_rt.a` (modern, per-triple subdir), +# `liborc_rt.a` (legacy unsuffixed), and the arch/OS-suffixed variants +# `_osx` (macOS universal), `-x86_64`, `-aarch64`. NO_DEFAULT_PATH so +# we never pick up a system-installed orc_rt with a different ABI than +# the LLVM we're linking against. +find_library(LLVMOrcRuntime_LIBRARY + NAMES + clang_rt.orc_rt + orc_rt + orc_rt_osx + orc_rt-x86_64 + orc_rt-aarch64 + HINTS ${_orc_hint_dirs} + NO_DEFAULT_PATH) + +find_program(LLVMOrcRuntime_EXECUTOR + NAMES llvm-jitlink-executor + HINTS "${LLVM_TOOLS_BINARY_DIR}" + NO_DEFAULT_PATH) + +find_package_handle_standard_args(LLVMOrcRuntime + REQUIRED_VARS LLVMOrcRuntime_LIBRARY LLVMOrcRuntime_EXECUTOR) + +mark_as_advanced(LLVMOrcRuntime_LIBRARY LLVMOrcRuntime_EXECUTOR) diff --git a/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake b/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake index c2b7c2ef75915..eb7f73f66dd49 100644 --- a/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake +++ b/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake @@ -1,5 +1,14 @@ +# BUILD_BYPRODUCTS, the explicit -B in CONFIGURE_COMMAND, and +# IMPORTED_LOCATION (resolved via ExternalProject_Get_Property +# binary_dir) all need to agree on where googletest builds. +# ExternalProject's binary_dir defaults to +# ${CMAKE_CURRENT_BINARY_DIR}/-prefix/src/-build, so +# tracking CMAKE_CURRENT_BINARY_DIR keeps the three in sync whether +# this module is consumed standalone or via add_subdirectory (e.g. +# under root-project/root, where CMAKE_BINARY_DIR is root-build but +# this directory is root-build/interpreter/CppInterOp/unittests). set(_gtest_byproduct_binary_dir - ${CMAKE_BINARY_DIR}/downloads/googletest-prefix/src/googletest-build) + ${CMAKE_CURRENT_BINARY_DIR}/googletest-prefix/src/googletest-build) set(_gtest_byproducts ${_gtest_byproduct_binary_dir}/lib/libgtest.a ${_gtest_byproduct_binary_dir}/lib/libgtest_main.a @@ -19,10 +28,14 @@ elseif(APPLE) endif() include(ExternalProject) +# Forward parent CMAKE_CXX_FLAGS to the gtest sub-build so sanitizer +# and -stdlib=libc++ additions don't get dropped (else gtest builds +# against system defaults and ABI-clashes with the parent at link). +set(GOOGLETEST_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 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}") + string(REPLACE "-sSUPPORT_LONGJMP=wasm" "" GOOGLETEST_CMAKE_CXX_FLAGS "${GOOGLETEST_CMAKE_CXX_FLAGS}") set(config_cmd emcmake${EMCC_SUFFIX} cmake) if(CMAKE_GENERATOR STREQUAL "Ninja") set(build_cmd emmake${EMCC_SUFFIX} ninja) @@ -31,7 +44,7 @@ if (EMSCRIPTEN) endif() else() set(config_cmd ${CMAKE_COMMAND}) - set(build_cmd ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/unittests/googletest-prefix/src/googletest-build/ --config $) + set(build_cmd ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR}/googletest-prefix/src/googletest-build/ --config $) endif() ExternalProject_Add( @@ -46,13 +59,18 @@ ExternalProject_Add( # -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs # -Dgtest_force_shared_crt=ON CONFIGURE_COMMAND ${config_cmd} -G ${CMAKE_GENERATOR} - -S ${CMAKE_BINARY_DIR}/unittests/googletest-prefix/src/googletest/ - -B ${CMAKE_BINARY_DIR}/unittests/googletest-prefix/src/googletest-build/ + -S ${CMAKE_CURRENT_BINARY_DIR}/googletest-prefix/src/googletest/ + -B ${CMAKE_CURRENT_BINARY_DIR}/googletest-prefix/src/googletest-build/ -DCMAKE_BUILD_TYPE=$ -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_FLAGS=${GOOGLETEST_CMAKE_CXX_FLAGS} + # HandleLLVMOptions puts -stdlib=libc++ / -fsanitize=* + # in CMAKE_*_LINKER_FLAGS for LLVM_USE_SANITIZER. + -DCMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS} + -DCMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS} + -DCMAKE_SHARED_LINKER_FLAGS=${CMAKE_SHARED_LINKER_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 e65eba7854971..93e00085c4566 100644 --- a/interpreter/CppInterOp/cppinterop-version.tag +++ b/interpreter/CppInterOp/cppinterop-version.tag @@ -1 +1 @@ -2e0183b8035c2fd737bb3f6ba2fe4f412e148447 \ No newline at end of file +ed3696d403c4064c494375087eb92e612cfd7245 diff --git a/interpreter/CppInterOp/docs/DevelopersDocumentation.rst b/interpreter/CppInterOp/docs/DevelopersDocumentation.rst index 15f7249047a3d..c833f4685635f 100644 --- a/interpreter/CppInterOp/docs/DevelopersDocumentation.rst +++ b/interpreter/CppInterOp/docs/DevelopersDocumentation.rst @@ -292,7 +292,11 @@ commands on Linux and MacOS .. note:: - Do make sure to pass ``DLLVM_BUILT_WITH_OOP_JIT=ON``, if you want to have out-of-process JIT execution feature enabled. + Out-of-process JIT auto-enables when CppInterOp is built against + LLVM 22+ with compiler-rt's ORC runtime available + (``liborc_rt*.a`` and ``llvm-jitlink-executor``). When detected, + the two artifacts are bundled under ``/cppinterop-rt/`` so + the distribution is self-contained. and diff --git a/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst b/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst index 5ba93186078e5..e9b001abaf0d4 100644 --- a/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst +++ b/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst @@ -16,7 +16,7 @@ experimental and subject to change. Try a Jupyter Lite demo of xeus-cpp by click This document first starts with the instructions on how to build a wasm build of CppInterOp. Before we start it should be noted that unlike the non wasm version of CppInterOp we currently only support the Clang-REPL -backend using llvm>19. We will first make folder to +backend using llvm>20. We will first make folder to build our wasm build of CppInterOp. This can be done by executing the following command @@ -57,7 +57,7 @@ and on Windows execute in Powershell $env:PWD_DIR= $PWD.Path $env:SYSROOT_PATH="$env:EMSDK/upstream/emscripten/cache/sysroot" -Now clone the 21.x release of the LLVM project repository and CppInterOp +Now clone the 22.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/21.x https://github.com/llvm/llvm-project.git + git clone --depth=1 --branch release/22.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,17 +74,16 @@ executing .. code:: bash cd ./llvm-project/ - git apply -v ../CppInterOp/patches/llvm/emscripten-clang21-*.patch + git apply -v ../CppInterOp/patches/llvm/emscripten-clang22-*.patch On Windows execute the following .. code:: powershell cd .\llvm-project\ - 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 + cp -r ..\patches\llvm\emscripten-clang22* + git apply -v emscripten-clang22-1-enable_exception_handling.patch + git apply -v emscripten-clang22-2-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 @@ -95,7 +94,8 @@ and osx cd native_build cmake -DLLVM_ENABLE_PROJECTS=clang -DLLVM_TARGETS_TO_BUILD=host -DCMAKE_BUILD_TYPE=Release ../llvm/ cmake --build . --target llvm-tblgen clang-tblgen --parallel $(nproc --all) - export NATIVE_DIR=$PWD/bin/ + export NATIVE_LLVM_BUILD_DIR==$PWD + export NATIVE_LLVM_BIN_DIR=$PWD/bin/ cd .. mkdir build cd build @@ -117,7 +117,7 @@ and osx -DLLVM_BUILD_TOOLS=OFF \ -DLLVM_ENABLE_LIBPFM=OFF \ -DCLANG_BUILD_TOOLS=OFF \ - -DLLVM_NATIVE_TOOL_DIR=$NATIVE_DIR \ + -DLLVM_NATIVE_TOOL_DIR=$NATIVE_LLVM_BIN_DIR \ -DCMAKE_C_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" \ -DCMAKE_CXX_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" \ -DLLVM_ENABLE_LTO=Full \ @@ -133,7 +133,8 @@ or executing cmake -DLLVM_ENABLE_PROJECTS=clang -DLLVM_TARGETS_TO_BUILD=host -DCMAKE_BUILD_TYPE=Release -G Ninja ../llvm/ cmake --build . --target llvm-tblgen clang-tblgen --parallel $(nproc --all) $env:PWD_DIR= $PWD.Path - $env:NATIVE_DIR="$env:PWD_DIR/bin/" + $env:NATIVE_LLVM_BUILD_DIR=="$env:PWD_DIR" + $env:NATIVE_LLVM_BIN_DIR=="$env:PWD_DIR/bin/" cd .. mkdir build cd build @@ -155,7 +156,7 @@ or executing -DLLVM_BUILD_TOOLS=OFF ` -DLLVM_ENABLE_LIBPFM=OFF ` -DCLANG_BUILD_TOOLS=OFF ` - -DLLVM_NATIVE_TOOL_DIR="$env:NATIVE_DIR" ` + -DLLVM_NATIVE_TOOL_DIR="$env:NATIVE_BIN_DIR" ` -G Ninja ` -DCMAKE_C_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" ` -DCMAKE_CXX_FLAGS_RELEASE="-Oz -g0 -DNDEBUG" ` @@ -208,7 +209,42 @@ and $env:CMAKE_PREFIX_PATH=$env:PREFIX $env:CMAKE_SYSTEM_PREFIX_PATH=$env:PREFIX -on Windows. Now to build and test your Emscripten build of CppInterOp on Linux and osx execute the following +on Windows. Before building the Emscripten version of CppInterOp, we need +to build ``cppinterop-tblgen`` natively. This tool generates ``.inc`` files +from ``.td`` definitions and must run on the host (not under Emscripten). +We use the native LLVM build from the earlier step since it has the required +``libLLVMTableGen`` library. + +On Linux and osx: + +.. code:: bash + + mkdir -p native_cppinterop_build && cd native_cppinterop_build + cmake -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_DIR=$NATIVE_LLVM_BUILD_DIR/lib/cmake/llvm \ + -DCMAKE_CXX_STANDARD=17 \ + -DCPPINTEROP_BUILD_TABLEGEN_ONLY=ON \ + ../ + cmake --build . --target cppinterop-tblgen -j $(nproc --all) + export CPPINTEROP_TBLGEN_EXE=$(find $PWD -name cppinterop-tblgen -type f | head -1) + cd .. + +On Windows: + +.. code:: powershell + + mkdir native_cppinterop_build + cd native_cppinterop_build + cmake -DCMAKE_BUILD_TYPE=Release ` + -DLLVM_DIR="$env:NATIVE_LLVM_BUILD_DIR\lib\cmake\llvm" ` + -DCMAKE_CXX_STANDARD=17 ` + -DCPPINTEROP_BUILD_TABLEGEN_ONLY=ON ` + ..\ + cmake --build . --target cppinterop-tblgen -j $(nproc --all) + $env:CPPINTEROP_TBLGEN_EXE = (Get-ChildItem -Recurse -Filter "cppinterop-tblgen.exe" | Select-Object -First 1).FullName + cd .. + +Now to build and test your Emscripten build of CppInterOp on Linux and osx execute the following (BUILD_SHARED_LIBS=ON is only needed if building xeus-cpp, as CppInterOp can be built as an Emscripten static library) .. code:: bash @@ -223,6 +259,7 @@ on Windows. Now to build and test your Emscripten build of CppInterOp on Linux a -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DSYSROOT_PATH=$SYSROOT_PATH \ + -DCPPINTEROP_TABLEGEN_EXE=$CPPINTEROP_TBLGEN_EXE \ ../ emmake make -j $(nproc --all) check-cppinterop @@ -241,6 +278,7 @@ To build and test your Emscripten build of CppInterOp on Windows execute the fol -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON ` -DLLVM_ENABLE_WERROR=On ` -DSYSROOT_PATH="$env:SYSROOT_PATH" ` + -DCPPINTEROP_TABLEGEN_EXE="$env:CPPINTEROP_TBLGEN_EXE" ` ..\ emmake make -j $(nproc --all) check-cppinterop @@ -366,7 +404,7 @@ by executing (replace LLVM_VERSION with the version of llvm you are building aga cd ../.. git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git - export LLVM_VERSION=21 + export LLVM_VERSION=22 cd ./xeus-cpp mkdir build cd build @@ -387,7 +425,7 @@ and on Windows by executing cd ..\.. git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git - $env:LLVM_VERSION=21 + $env:LLVM_VERSION=22 cd .\xeus-cpp mkdir build cd build diff --git a/interpreter/CppInterOp/docs/InstallationAndUsage.rst b/interpreter/CppInterOp/docs/InstallationAndUsage.rst index 27f9794450647..c0b47eb5c9463 100644 --- a/interpreter/CppInterOp/docs/InstallationAndUsage.rst +++ b/interpreter/CppInterOp/docs/InstallationAndUsage.rst @@ -294,7 +294,11 @@ commands on Linux and MacOS .. note:: - Do make sure to pass ``DLLVM_BUILT_WITH_OOP_JIT=ON``, if you want to have out-of-process JIT execution feature enabled. + Out-of-process JIT auto-enables when CppInterOp is built against + LLVM 22+ with compiler-rt's ORC runtime available + (``liborc_rt*.a`` and ``llvm-jitlink-executor``). When detected, + the two artifacts are bundled under ``/cppinterop-rt/`` so + the distribution is self-contained. and diff --git a/interpreter/CppInterOp/docs/conf.py b/interpreter/CppInterOp/docs/conf.py index d8f9dad816acf..dcf03a7b8d497 100644 --- a/interpreter/CppInterOp/docs/conf.py +++ b/interpreter/CppInterOp/docs/conf.py @@ -54,4 +54,6 @@ -DLLVM_DIR=/usr/lib/llvm-20/build/lib/cmake/llvm -DCPPINTEROP_ENABLE_DOXYGEN=ON\ -DCPPINTEROP_INCLUDE_DOCS=ON'.format(CPPINTEROP_ROOT) subprocess.call(command, shell=True) +# Generate .inc files so doxygen can find the API declarations in CppInterOp.h. +subprocess.call('cmake --build {0}/build --target CppInterOpTableGen'.format(CPPINTEROP_ROOT), shell=True) subprocess.call('doxygen {0}/build/docs/doxygen.cfg'.format(CPPINTEROP_ROOT), shell=True) diff --git a/interpreter/CppInterOp/docs/doxygen.cfg.in b/interpreter/CppInterOp/docs/doxygen.cfg.in index 6a7e0c47c7b19..61db8e76fc41b 100644 --- a/interpreter/CppInterOp/docs/doxygen.cfg.in +++ b/interpreter/CppInterOp/docs/doxygen.cfg.in @@ -1617,7 +1617,8 @@ SEARCH_INCLUDES = YES # contain include files that are not input files but should be processed by # the preprocessor. -INCLUDE_PATH = @CMAKE_SOURCE_DIR@/include +INCLUDE_PATH = @CMAKE_SOURCE_DIR@/include \ + @CMAKE_BINARY_DIR@/include # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the diff --git a/interpreter/CppInterOp/etc/clang20-valgrind.supp b/interpreter/CppInterOp/etc/clang20-valgrind.supp deleted file mode 100644 index 9261befb2b6a9..0000000000000 --- a/interpreter/CppInterOp/etc/clang20-valgrind.supp +++ /dev/null @@ -1,18 +0,0 @@ -{ - 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/etc/clang21-valgrind.supp b/interpreter/CppInterOp/etc/clang21-valgrind.supp new file mode 100644 index 0000000000000..aff8fe1502568 --- /dev/null +++ b/interpreter/CppInterOp/etc/clang21-valgrind.supp @@ -0,0 +1,65 @@ +# Valgrind suppressions for clang-21 builds. +# +# Tracks llvm/llvm-project#194147 ("clang-21+ reads uninitialised +# bytes in many AST/Sema paths"). The same regression class as +# clang-22; the per-major file lets each binary's specific report +# set drift without entries from one major silencing the other's +# coverage. +# +# `flavor: system` consumers (CppInterOp's llvm-21 vg row) get this +# file applied automatically by Build_and_Test_CppInterOp's loader, +# which loads `etc/clang${CLANG_VERSION}-valgrind.supp` whenever it +# exists. apt-llvm.org's clang-21 binary is what trips the reports +# below. +# +# What's *actually* in here is what fired on a real local valgrind +# run (validated via `bin/repro --save-temps` + targeted +# `--gtest_filter` re-runs against the saved container, watching the +# `used_suppression` summary). Earlier iterations carried ~17 more +# blocks targeting symbols I'd extracted from "Uninitialised value +# was created by" allocation-origin frames; valgrind's matcher only +# considers the read-site top frame, so every one of those blocks +# was dead. Removed. +# +# Drop this whole file once #194147 lands upstream and apt-llvm.org's +# clang-21 packages refresh. + +# clang::FunctionProtoType::FunctionProtoType(...) is the read-site +# top frame for the bulk of #194147 reports on this binary +# (54k+ errors over 51 contexts in our wider test sample). +# +# Three patterns considered, only one fires: +# *FunctionProtoType::FunctionProtoType* -- demangled, didn't match +# _ZN5clang17FunctionProtoTypeC* -- mangled, didn't match +# *FunctionProtoType* -- single substring, MATCHES +# +# The single-substring form over-matches by design: every method on +# the FunctionProtoType class (getCallConv, getReturnType, etc.) +# would also be silenced if it ever surfaces a real bug. Tolerated +# because (a) the constructor is the only top frame seen today, (b) +# the more specific patterns demonstrably don't match valgrind's +# matcher despite reading correctly to a human, (c) dropping the +# whole file when #194147 lands restores precision. Documented so +# the next maintainer doesn't waste time on the same investigation. +{ + llvm/llvm-project#194147: FunctionProtoType (any method, see comment) + Memcheck:Cond + fun:*FunctionProtoType* + ... +} + +# Two more #194147 patterns that fire on lower-frequency code paths +# (~14 errors each on the sampled tests). Wildcards on two distinct +# identifiers, which matches valgrind's matcher reliably. +{ + llvm/llvm-project#194147: CodeGenFunction::EmitCall + Memcheck:Cond + fun:*CodeGenFunction*EmitCall* + ... +} +{ + llvm/llvm-project#194147: EHScopeStack::requiresLandingPad + Memcheck:Cond + fun:*EHScopeStack*requiresLandingPad* + ... +} diff --git a/interpreter/CppInterOp/etc/clang22-valgrind.supp b/interpreter/CppInterOp/etc/clang22-valgrind.supp new file mode 100644 index 0000000000000..cc88b36d5ba5d --- /dev/null +++ b/interpreter/CppInterOp/etc/clang22-valgrind.supp @@ -0,0 +1,219 @@ +# Upstream LLVM write(buf) uninit-bytes via the EPC transport, still +# present in release/22.x. No llvm/llvm-project issue filed yet; if you +# touch this file, please file one and add a reference here. +{ + Clang 22 Out of Process: uninitialised bytes in FDSimpleRemoteEPCTransport::sendMessage + Memcheck:Param + write(buf) + fun:__libc_write + fun:write + fun:_ZN4llvm3orc26FDSimpleRemoteEPCTransport11sendMessageENS0_21SimpleRemoteEPCOpcodeEmNS0_12ExecutorAddrENS_8ArrayRefIcEE + ... +} + +# llvm/llvm-project#194147: clang-22 reads uninitialised bytes in many +# AST/Sema/CodeGen paths (regression vs clang-20). Was x86_64-only at +# first; aarch64 caught up after a release/22.x advance and now trips +# its own set (TreeTransform, SemaCUDA, AsmPrinter, ValueEnumerator). +# Drop the whole #194147 family of blocks once the upstream issue lands. + +{ + llvm/llvm-project#194147: InitListExpr::isTransparent + Memcheck:Cond + fun:_ZNK5clang12InitListExpr13isTransparentEv + ... +} +{ + llvm/llvm-project#194147: InitListExpr::getInit + Memcheck:Cond + fun:_ZN5clang12InitListExpr7getInitEj + ... +} +{ + llvm/llvm-project#194147: InitListExpr::getInit const + Memcheck:Cond + fun:_ZNK5clang12InitListExpr7getInitEj + ... +} +{ + llvm/llvm-project#194147: InitListExpr::setInit + Memcheck:Cond + fun:_ZN5clang12InitListExpr7setInitEjPNS_4ExprE + ... +} +{ + llvm/llvm-project#194147: InitListExpr::resizeInits + Memcheck:Cond + fun:_ZN5clang12InitListExpr11resizeInitsERKNS_10ASTContextEj + ... +} +{ + llvm/llvm-project#194147: InitListExpr::resizeInits via memset + Memcheck:Cond + fun:memset + fun:_ZN5clang12InitListExpr11resizeInitsERKNS_10ASTContextEj + ... +} + +{ + llvm/llvm-project#194147: Sema::CheckForIntOverflow Cond + Memcheck:Cond + fun:_ZN5clang4Sema19CheckForIntOverflowEPKNS_4ExprE + ... +} +{ + llvm/llvm-project#194147: Sema::CheckForIntOverflow Value8 + Memcheck:Value8 + fun:_ZN5clang4Sema19CheckForIntOverflowEPKNS_4ExprE + ... +} +{ + llvm/llvm-project#194147: SequenceChecker::SequenceExpressionsInOrder + Memcheck:Cond + fun:*SequenceChecker*SequenceExpressionsInOrder* + ... +} + +{ + llvm/llvm-project#194147: Stmt::children via AnalyzeImplicitConversions + Memcheck:Cond + fun:_ZN5clang4Stmt8childrenEv + fun:*AnalyzeImplicitConversions* + ... +} +{ + llvm/llvm-project#194147: AnalyzeImplicitConversions leaf + Memcheck:Cond + fun:*AnalyzeImplicitConversions* + ... +} +{ + llvm/llvm-project#194147: CFGBuilder::Visit Cond + Memcheck:Cond + fun:*CFGBuilder*Visit* + ... +} +{ + llvm/llvm-project#194147: CFGBuilder::Visit Value8 + Memcheck:Value8 + fun:*CFGBuilder*Visit* + ... +} + +{ + llvm/llvm-project#194147: RecordExprEvaluator::VisitCXXParenListOrInitListExpr + Memcheck:Cond + fun:*RecordExprEvaluator*VisitCXXParenListOrInitListExpr* + ... +} +{ + llvm/llvm-project#194147: AggExprEmitter init-list visits + Memcheck:Cond + fun:*AggExprEmitter*Visit*InitListExpr* + ... +} + +{ + llvm/llvm-project#194147: InitListChecker::FillInEmptyInitForField + Memcheck:Cond + fun:*InitListChecker*FillInEmptyInitForField* + ... +} +{ + llvm/llvm-project#194147: InitListChecker::FillInEmptyInitializations + Memcheck:Cond + fun:*InitListChecker*FillInEmptyInitializations* + ... +} + +# llvm/llvm-project#194147 (continued): symbols newly tripped after +# release/22.x advanced and the llvm-release recipe republished. The +# FunctionProtoType ctor is the dominant top frame on x86_64; the +# CodeGen + EHScope entries cover the lower-frequency paths. +# +# Earlier iterations of this file carried ~17 more blocks targeting +# symbols extracted from "Uninitialised value was created by" +# allocation-origin frames; valgrind's matcher only considers the +# read-site top frame, so every one of those blocks was dead. +# Removed. (See the matching comment in clang21-valgrind.supp for +# why the over-broad `*FunctionProtoType*` is the only pattern that +# matches the constructor in valgrind's wildcard matcher.) +{ + llvm/llvm-project#194147: FunctionProtoType (any method, see comment) + Memcheck:Cond + fun:*FunctionProtoType* + ... +} +{ + llvm/llvm-project#194147: CodeGenFunction::EmitCall + Memcheck:Cond + fun:*CodeGenFunction*EmitCall* + ... +} +{ + llvm/llvm-project#194147: EHScopeStack::requiresLandingPad + Memcheck:Cond + fun:*EHScopeStack*requiresLandingPad* + ... +} +{ + # Top frame for every unsuppressed error in the llvm22-vg + # x86_64 row after FunctionProtoType started being silenced -- + # 21->173 errors all surfaced here, across 3->5 contexts + # depending on which CppInterOpTests' OutOfProcessJIT subtests + # ran. Origin frame is llvm::Module::setDataLayout (stack + # allocation) but valgrind matches read-site, so suppress + # by the read-site symbol. + llvm/llvm-project#194147: CodeGenModule::SetLLVMFunctionAttributesForDefinition + Memcheck:Cond + fun:*CodeGenModule*SetLLVMFunctionAttributesForDefinition* + ... +} + +# llvm/llvm-project#194147 (continued): symbols newly tripped on +# the arm64 cell after recipe republish. The aarch64 cell was +# previously documented as clean for #194147 (per the existing +# top-of-file note); that's now stale. Different code paths than +# x86_64 trips, mainly TreeTransform / SemaCUDA / AsmPrinter / +# bitcode writer. +{ + llvm/llvm-project#194147: TreeTransform::TransformIfStmt + Memcheck:Cond + fun:*TreeTransform*TransformIfStmt* + ... +} +{ + llvm/llvm-project#194147: SemaCUDA::inferTargetForImplicitSpecialMember + Memcheck:Cond + fun:*SemaCUDA*inferTargetForImplicitSpecialMember* + ... +} +{ + llvm/llvm-project#194147: AsmPrinter::emitPatchableFunctionEntries + Memcheck:Cond + fun:*AsmPrinter*emitPatchableFunctionEntries* + ... +} +{ + llvm/llvm-project#194147: ValueEnumerator::getAttributeGroupID Cond + Memcheck:Cond + fun:*ValueEnumerator*getAttributeGroupID* + ... +} +{ + # Same symbol, different error class: BitcodeWriter reads an + # 8-byte uninit field (AttributeSet pointer pair) when the + # ModuleBitcodeWriter::write() path serializes the module. Cond + # alone doesn't catch Memcheck:Value8 — value-load errors are + # tracked separately by valgrind. + llvm/llvm-project#194147: ValueEnumerator::getAttributeGroupID Value8 + Memcheck:Value8 + fun:*ValueEnumerator*getAttributeGroupID* + ... +} +{ + llvm/llvm-project#194147: TargetLowering::LowerCallTo + Memcheck:Cond + fun:*TargetLowering*LowerCallTo* + ... +} diff --git a/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h index 92c459b3b2b9c..0928cb29f25df 100644 --- a/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h +++ b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h @@ -14,1039 +14,20 @@ #ifndef CPPINTEROP_CPPINTEROP_H #define CPPINTEROP_CPPINTEROP_H -#include -#include -#include -#include -#include -#include -#include - -// The cross-platform CPPINTEROP_API macro definition -#if defined _WIN32 || defined __CYGWIN__ -#define CPPINTEROP_API __declspec(dllexport) -#else -#ifdef __GNUC__ -#define CPPINTEROP_API __attribute__((__visibility__("default"))) -#else -#define CPPINTEROP_API -#endif +#ifdef CPPINTEROP_DISPATCH_H +#error "CppInterOp.h and Dispatch.h are mutually exclusive — include only one." #endif -namespace CppImpl { -using TCppIndex_t = size_t; -using TCppScope_t = void*; -using TCppConstScope_t = const void*; -using TCppType_t = void*; -using TCppConstType_t = const void*; -using TCppFunction_t = void*; -using TCppConstFunction_t = const void*; -using TCppFuncAddr_t = void*; -using TInterp_t = void*; -using TCppObject_t = void*; - -enum Operator : unsigned char { - OP_None, - OP_New, - OP_Delete, - OP_Array_New, - OP_Array_Delete, - OP_Plus, - OP_Minus, - OP_Star, - OP_Slash, - OP_Percent, - OP_Caret, - OP_Amp, - OP_Pipe, - OP_Tilde, - OP_Exclaim, - OP_Equal, - OP_Less, - OP_Greater, - OP_PlusEqual, - OP_MinusEqual, - OP_StarEqual, - OP_SlashEqual, - OP_PercentEqual, - OP_CaretEqual, - OP_AmpEqual, - OP_PipeEqual, - OP_LessLess, - OP_GreaterGreater, - OP_LessLessEqual, - OP_GreaterGreaterEqual, - OP_EqualEqual, - OP_ExclaimEqual, - OP_LessEqual, - OP_GreaterEqual, - OP_Spaceship, - OP_AmpAmp, - OP_PipePipe, - OP_PlusPlus, - OP_MinusMinus, - OP_Comma, - OP_ArrowStar, - OP_Arrow, - OP_Call, - OP_Subscript, - OP_Conditional, - OP_Coawait, -}; - -enum OperatorArity : unsigned char { kUnary = 1, kBinary, kBoth }; -enum Signedness : unsigned char { kSigned = 1, kUnsigned }; - -/// Enum modelling CVR qualifiers. -enum QualKind : unsigned char { - Const = 1 << 0, - Volatile = 1 << 1, - Restrict = 1 << 2, - All = Const | Volatile | Restrict -}; - -/// Enum modelling programming languages. -enum class InterpreterLanguage : unsigned char { - Unknown, - Asm, - CIR, - LLVM_IR, - C, - CPlusPlus, - ObjC, - ObjCPlusPlus, - OpenCL, - OpenCLCXX, - CUDA, - HIP, - HLSL -}; - -/// Enum modelling language standards. -enum class InterpreterLanguageStandard : unsigned char { - c89, - c94, - gnu89, - c99, - gnu99, - c11, - gnu11, - c17, - gnu17, - c23, - gnu23, - c2y, - gnu2y, - cxx98, - gnucxx98, - cxx11, - gnucxx11, - cxx14, - gnucxx14, - cxx17, - gnucxx17, - cxx20, - gnucxx20, - cxx23, - gnucxx23, - cxx26, - gnucxx26, - opencl10, - opencl11, - opencl12, - opencl20, - opencl30, - openclcpp10, - openclcpp2021, - hlsl, - hlsl2015, - hlsl2016, - hlsl2017, - hlsl2018, - hlsl2021, - hlsl202x, - hlsl202y, - lang_unspecified -}; -inline QualKind operator|(QualKind a, QualKind b) { - return static_cast(static_cast(a) | - 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. -class JitCall { -public: - friend CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, - TCppConstFunction_t func); - enum Kind : char { - kUnknown = 0, - kGenericCall, - kConstructorCall, - kDestructorCall, - }; - struct ArgList { - void** m_Args = nullptr; - size_t m_ArgSize = 0; - // Clang struggles with =default... - ArgList() {} - ArgList(void** Args, size_t ArgSize) : m_Args(Args), m_ArgSize(ArgSize) {} - }; - // FIXME: Figure out how to unify the wrapper signatures. - // FIXME: Hide these implementation details by moving wrapper generation in - // this class. - // (self, nargs, args, result, nary) - using GenericCall = void (*)(void*, size_t, void**, void*); - // (result, nary, nargs, args, is_arena) - using ConstructorCall = void (*)(void*, size_t, size_t, void**, void*); - // (self, nary, withFree) - using DestructorCall = void (*)(void*, size_t, int); - -private: - union { - GenericCall m_GenericCall; - ConstructorCall m_ConstructorCall; - DestructorCall m_DestructorCall; - }; - Kind m_Kind; - TCppConstFunction_t m_FD; - JitCall() : m_GenericCall(nullptr), m_Kind(kUnknown), m_FD(nullptr) {} - JitCall(Kind K, GenericCall C, TCppConstFunction_t FD) - : m_GenericCall(C), m_Kind(K), m_FD(FD) {} - JitCall(Kind K, ConstructorCall C, TCppConstFunction_t Ctor) - : m_ConstructorCall(C), m_Kind(K), m_FD(Ctor) {} - JitCall(Kind K, DestructorCall C, TCppConstFunction_t Dtor) - : m_DestructorCall(C), m_Kind(K), m_FD(Dtor) {} - - /// Checks if the passed arguments are valid for the given function. - CPPINTEROP_API bool AreArgumentsValid(void* result, ArgList args, void* self, - size_t nary) const; - - /// This function is used for debugging, it reports when the function was - /// called. - CPPINTEROP_API void ReportInvokeStart(void* result, ArgList args, - void* self) const; - CPPINTEROP_API void ReportInvokeStart(void* object, unsigned long nary, - int withFree) const; - void ReportInvokeEnd() const; - -public: - Kind getKind() const { return m_Kind; } - bool isValid() const { return getKind() != kUnknown; } - bool isInvalid() const { return !isValid(); } - explicit operator bool() const { return isValid(); } - - // Specialized for calling void functions. - void Invoke(ArgList args = {}, void* self = nullptr) const { - Invoke(/*result=*/nullptr, args, self); - } - - /// Makes a call to a generic function or method. - ///\param[in] result - the location where the return result will be placed. - ///\param[in] args - a pointer to a argument list and argument size. - ///\param[in] self - the 'this pointer' of the object. - // FIXME: Adjust the arguments and their types: args_size can be unsigned; - // self can go in the end and be nullptr by default; result can be a nullptr - // by default. These changes should be synchronized with the wrapper if we - // decide to directly. - void Invoke(void* result, ArgList args = {}, void* self = nullptr) const { - // NOLINTBEGIN(*-type-union-access) - // Its possible the JitCall object deals with structor decls but went - // through Invoke - - switch (m_Kind) { - case kUnknown: - assert(0 && "Attempted to call an invalid function declaration"); - break; - - case kGenericCall: -#ifndef NDEBUG - // We pass 1UL to nary which is only relevant for structors - assert(AreArgumentsValid(result, args, self, 1UL) && "Invalid args!"); - ReportInvokeStart(result, args, self); -#endif // NDEBUG - m_GenericCall(self, args.m_ArgSize, args.m_Args, result); - break; - - case kConstructorCall: - // Forward if we intended to call a constructor (nary cannot be inferred, - // so we stick to constructing a single object) - InvokeConstructor(result, /*nary=*/1UL, args, self); - break; - case kDestructorCall: - // Forward if we intended to call a dtor with only 1 parameter. - assert(!args.m_Args && "Destructor called with arguments"); - InvokeDestructor(result, /*nary=*/0UL, /*withFree=*/true); - break; - } - // NOLINTEND(*-type-union-access) - } - /// Makes a call to a destructor. - ///\param[in] object - the pointer of the object whose destructor we call. - ///\param[in] nary - the count of the objects we destruct if we deal with an - /// array of objects. - ///\param[in] withFree - true if we should call operator delete or false if - /// we should call only the destructor. - // FIXME: Change the type of withFree from int to bool in the wrapper code. - void InvokeDestructor(void* object, unsigned long nary = 0, - int withFree = true) const { - assert(m_Kind == kDestructorCall && "Wrong overload!"); -#ifndef NDEBUG - ReportInvokeStart(object, nary, withFree); -#endif // NDEBUG - m_DestructorCall(object, nary, withFree); - } - - /// Makes a call to a constructor. - ///\param[in] result - the memory address at which we construct the object - /// (placement new). - ///\param[in] nary - Use array new if we have to construct an array of - /// objects (nary > 1). - ///\param[in] args - a pointer to a argument list and argument size. - ///\param[in] is_arena - a pointer that indicates if placement new is to be - /// used - // FIXME: Change the type of withFree from int to bool in the wrapper code. - void InvokeConstructor(void* result, unsigned long nary = 1, - ArgList args = {}, void* is_arena = nullptr) const { - assert(m_Kind == kConstructorCall && "Wrong overload!"); -#ifndef NDEBUG - assert(AreArgumentsValid(result, args, /*self=*/nullptr, nary) && - "Invalid args!"); - ReportInvokeStart(result, args, nullptr); -#endif // NDEBUG - m_ConstructorCall(result, nary, args.m_ArgSize, args.m_Args, is_arena); - } -}; - -///\returns the version string information of the library. -CPPINTEROP_API std::string GetVersion(); - -///\returns the demangled representation of the given mangled_name -CPPINTEROP_API std::string Demangle(const std::string& mangled_name); - -/// Enables or disables the debugging printouts on stderr. -/// Debugging output can be enabled also by the environment variable -/// CPPINTEROP_EXTRA_INTERPRETER_ARGS. For example, -/// CPPINTEROP_EXTRA_INTERPRETER_ARGS="-mllvm -debug-only=jitcall" to produce -/// only debug output for jitcall events. -CPPINTEROP_API void EnableDebugOutput(bool value = true); - -///\returns true if the debugging printouts on stderr are enabled. -CPPINTEROP_API bool IsDebugOutputEnabled(); - -/// Checks if the given class represents an aggregate type). -///\returns true if \c scope is an array or a C++ tag (as per C++ -///[dcl.init.aggr]) \returns true if the scope supports aggregate -/// initialization. -CPPINTEROP_API bool IsAggregate(TCppScope_t scope); - -/// Checks if the scope is a namespace or not. -CPPINTEROP_API bool IsNamespace(TCppScope_t scope); - -/// Checks if the scope is a class or not. -CPPINTEROP_API bool IsClass(TCppScope_t scope); - -/// Checks if the scope is a function. -CPPINTEROP_API bool IsFunction(TCppScope_t scope); - -/// Checks if the type is a function pointer. -CPPINTEROP_API bool IsFunctionPointerType(TCppType_t type); - -/// Checks if the klass polymorphic. -/// which means that the class contains or inherits a virtual function -CPPINTEROP_API bool IsClassPolymorphic(TCppScope_t klass); - -// See TClingClassInfo::IsLoaded -/// Checks if the class definition is present, or not. Performs a -/// template instantiation if necessary. -CPPINTEROP_API bool IsComplete(TCppScope_t scope); - -CPPINTEROP_API size_t SizeOf(TCppScope_t scope); - -/// Checks if it is a "built-in" or a "complex" type. -CPPINTEROP_API bool IsBuiltin(TCppConstType_t type); - -/// Checks if it is a templated class. -CPPINTEROP_API bool IsTemplate(TCppScope_t handle); - -/// Checks if it is a class template specialization class. -CPPINTEROP_API bool IsTemplateSpecialization(TCppScope_t handle); - -/// Checks if \c handle introduces a typedef name via \c typedef or \c using. -CPPINTEROP_API bool IsTypedefed(TCppScope_t handle); - -CPPINTEROP_API bool IsAbstract(TCppType_t klass); - -/// Checks if it is an enum name (EnumDecl represents an enum name). -CPPINTEROP_API bool IsEnumScope(TCppScope_t handle); - -/// Checks if it is an enum's value (EnumConstantDecl represents -/// each enum constant that is defined). -CPPINTEROP_API bool IsEnumConstant(TCppScope_t handle); - -/// Checks if the passed value is an enum type or not. -CPPINTEROP_API bool IsEnumType(TCppType_t type); - -/// Checks if the passed type has qual Qualifiers -/// qual can be ORed value of enum QualKind -CPPINTEROP_API bool HasTypeQualifier(TCppType_t type, QualKind qual); - -/// Returns type with the qual Qualifiers removed -/// qual can be ORed value of enum QualKind -CPPINTEROP_API TCppType_t RemoveTypeQualifier(TCppType_t type, QualKind qual); - -/// Returns type with the qual Qualifiers added -/// qual can be ORed value of enum QualKind -CPPINTEROP_API TCppType_t AddTypeQualifier(TCppType_t type, QualKind qual); - -/// Extracts enum declarations from a specified scope and stores them in -/// vector -CPPINTEROP_API void GetEnums(TCppScope_t scope, - std::vector& Result); - -/// We assume that smart pointer types define both operator* and -/// operator->. -CPPINTEROP_API bool IsSmartPtrType(TCppType_t type); - -/// For the given "class", get the integer type that the enum -/// represents, so that you can store it properly in your specific -/// language. -CPPINTEROP_API TCppType_t GetIntegerTypeFromEnumScope(TCppScope_t handle); - -/// For the given "type", this function gets the integer type that the enum -/// represents, so that you can store it properly in your specific -/// language. -CPPINTEROP_API TCppType_t GetIntegerTypeFromEnumType(TCppType_t handle); - -/// Gets a list of all the enum constants for an enum. -CPPINTEROP_API std::vector GetEnumConstants(TCppScope_t scope); - -/// Gets the enum name when an enum constant is passed. -CPPINTEROP_API TCppType_t GetEnumConstantType(TCppScope_t scope); - -/// Gets the index value (0,1,2, etcetera) of the enum constant -/// that was passed into this function. -CPPINTEROP_API TCppIndex_t GetEnumConstantValue(TCppScope_t scope); - -/// Gets the size of the "type" that is passed in to this function. -CPPINTEROP_API size_t GetSizeOfType(TCppType_t type); - -/// Checks if the passed value is a variable. -CPPINTEROP_API bool IsVariable(TCppScope_t scope); - -/// Gets the name of any named decl (a class, -/// namespace, variable, or a function). -CPPINTEROP_API std::string GetName(TCppScope_t klass); - -/// This is similar to GetName() function, but besides -/// the name, it also gets the template arguments. -CPPINTEROP_API std::string GetCompleteName(TCppScope_t klass); - -/// Gets the "qualified" name (including the namespace) of any -/// named decl (a class, namespace, variable, or a function). -CPPINTEROP_API std::string GetQualifiedName(TCppScope_t klass); - -/// This is similar to GetQualifiedName() function, but besides -/// the "qualified" name (including the namespace), it also -/// 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); - -/// Gets the global scope of the whole C++ instance. -CPPINTEROP_API TCppScope_t GetGlobalScope(); - -/// Strips the typedef and returns the underlying class, and if the -/// underlying decl is not a class it returns the input unchanged. -CPPINTEROP_API TCppScope_t GetUnderlyingScope(TCppScope_t scope); - -/// Gets the namespace or class (by stripping typedefs) for the name -/// passed as a parameter, and if the parent is not passed, -/// then global scope will be assumed. -CPPINTEROP_API TCppScope_t GetScope(const std::string& name, - TCppScope_t parent = nullptr); - -/// When the namespace is known, then the parent doesn't need -/// to be specified. This will probably be phased-out in -/// future versions of the interop library. -CPPINTEROP_API TCppScope_t GetScopeFromCompleteName(const std::string& name); - -/// This function performs a lookup within the specified parent, -/// a specific named entity (functions, enums, etcetera). -CPPINTEROP_API TCppScope_t GetNamed(const std::string& name, - TCppScope_t parent = nullptr); - -/// Gets the parent of the scope that is passed as a parameter. -CPPINTEROP_API TCppScope_t GetParentScope(TCppScope_t scope); - -/// Gets the scope of the type that is passed as a parameter. -CPPINTEROP_API TCppScope_t GetScopeFromType(TCppType_t type); - -/// Gets the number of Base Classes for the Derived Class that -/// is passed as a parameter. -CPPINTEROP_API TCppIndex_t GetNumBases(TCppScope_t klass); - -/// Gets a specific Base Class using its index. Typically GetNumBases() -/// is used to get the number of Base Classes, and then that number -/// can be used to iterate through the index value to get each specific -/// base class. -CPPINTEROP_API TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase); - -/// Checks if the supplied Derived Class is a sub-class of the -/// provided Base Class. -CPPINTEROP_API bool IsSubclass(TCppScope_t derived, TCppScope_t base); - -/// Each base has its own offset in a Derived Class. This offset can be -/// used to get to the Base Class fields. -CPPINTEROP_API int64_t GetBaseClassOffset(TCppScope_t derived, - TCppScope_t base); - -/// Sets a list of all the Methods that are in the Class that is -/// supplied as a parameter. -///\param[in] klass - Pointer to the scope/class under which the methods have -/// to be retrieved -///\param[out] methods - Vector of methods in the class -CPPINTEROP_API void GetClassMethods(TCppScope_t klass, - std::vector& methods); - -/// Template function pointer list to add proxies for un-instantiated/ -/// non-overloaded templated methods -///\param[in] klass - Pointer to the scope/class under which the methods have -/// to be retrieved -///\param[out] methods - Vector of methods in the class -CPPINTEROP_API void -GetFunctionTemplatedDecls(TCppScope_t klass, - std::vector& methods); - -///\returns if a class has a default constructor. -CPPINTEROP_API bool HasDefaultConstructor(TCppScope_t scope); - -///\returns the default constructor of a class, if any. -CPPINTEROP_API TCppFunction_t GetDefaultConstructor(TCppScope_t scope); - -///\returns the class destructor, if any. -CPPINTEROP_API TCppFunction_t GetDestructor(TCppScope_t scope); - -/// Looks up all the functions that have the name that is -/// passed as a parameter in this function. -CPPINTEROP_API std::vector -GetFunctionsUsingName(TCppScope_t scope, const std::string& name); - -/// Gets the return type of the provided function. -CPPINTEROP_API TCppType_t GetFunctionReturnType(TCppFunction_t func); - -/// Gets the number of Arguments for the provided function. -CPPINTEROP_API TCppIndex_t GetFunctionNumArgs(TCppFunction_t func); - -/// Gets the number of Required Arguments for the provided function. -CPPINTEROP_API TCppIndex_t GetFunctionRequiredArgs(TCppConstFunction_t func); - -/// For each Argument of a function, you can get the Argument Type -/// by providing the Argument Index, based on the number of arguments -/// from the GetFunctionNumArgs() function. -CPPINTEROP_API TCppType_t GetFunctionArgType(TCppFunction_t func, - TCppIndex_t iarg); - -///\returns a stringified version of a given function signature in the form: -/// void N::f(int i, double d, long l = 0, char ch = 'a'). -CPPINTEROP_API std::string GetFunctionSignature(TCppFunction_t func); - -///\returns if a function was marked as \c =delete. -CPPINTEROP_API bool IsFunctionDeleted(TCppConstFunction_t function); - -CPPINTEROP_API bool IsTemplatedFunction(TCppFunction_t func); - -/// This function performs a lookup to check if there is a -/// templated function of that type. -CPPINTEROP_API bool ExistsFunctionTemplate(const std::string& name, - TCppScope_t parent = nullptr); - -/// Sets a list of all the constructor for a scope/class that is -/// supplied as a parameter. -///\param[in] name - This string is used as a constraint, that clients can use -/// to ensure the constructors match the name that they provide -///\param[in] parent - Pointer to the scope/class for which the constructors -/// are being looked up -/// to be retrieved -///\param[out] funcs - vector of handles to all constructors found under the -/// given scope -CPPINTEROP_API void LookupConstructors(const std::string& name, - TCppScope_t parent, - std::vector& funcs); - -/// Sets a list of all the Templated Methods that are in the Class that is -/// supplied as a parameter. -///\returns true if the lookup succeeded, and false if there are no candidates -///\param[in] name - method name -///\param[in] parent - Pointer to the scope/class under which the methods have -/// to be retrieved -///\param[out] funcs - vector of function pointers matching the name -CPPINTEROP_API bool -GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, - std::vector& funcs); - -/// Checks if the provided parameter is a method. -CPPINTEROP_API bool IsMethod(TCppConstFunction_t method); - -/// Checks if the provided parameter is a 'Public' method. -CPPINTEROP_API bool IsPublicMethod(TCppFunction_t method); - -/// Checks if the provided parameter is a 'Protected' method. -CPPINTEROP_API bool IsProtectedMethod(TCppFunction_t method); - -/// Checks if the provided parameter is a 'Private' method. -CPPINTEROP_API bool IsPrivateMethod(TCppFunction_t method); - -/// Checks if the provided parameter is a Constructor. -CPPINTEROP_API bool IsConstructor(TCppConstFunction_t method); +#include "CppInterOp/CppInterOpTypes.h" -/// Checks if the provided parameter is a Destructor. -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); - -///\returns the address of the function given its function declaration. -CPPINTEROP_API TCppFuncAddr_t GetFunctionAddress(TCppFunction_t method); - -/// Checks if the provided parameter is a 'Virtual' method. -CPPINTEROP_API bool IsVirtualMethod(TCppFunction_t method); - -/// Gets all the Fields/Data Members of a Class -CPPINTEROP_API void GetDatamembers(TCppScope_t scope, - std::vector& datamembers); - -/// Gets all the Static Fields/Data Members of a Class -///\param[in] scope - class -///\param[out] funcs - vector of static data members -CPPINTEROP_API void GetStaticDatamembers(TCppScope_t scope, - std::vector& datamembers); - -/// Gets all the Enum Constants declared in a Class -///\param[in] scope - class -///\param[out] funcs - vector of static data members -///\param[in] include_enum_class - include enum constants from enum class -CPPINTEROP_API -void GetEnumConstantDatamembers(TCppScope_t scope, - std::vector& datamembers, - bool include_enum_class = true); - -/// This is a Lookup function to be used specifically for data members. -CPPINTEROP_API TCppScope_t LookupDatamember(const std::string& name, - TCppScope_t parent); - -/// Check if the given type is a lamda class -CPPINTEROP_API bool IsLambdaClass(TCppType_t type); - -/// Gets the type of the variable that is passed as a parameter. -CPPINTEROP_API TCppType_t GetVariableType(TCppScope_t var); - -/// Gets the address of the variable, you can use it to get the -/// value stored in the variable. -CPPINTEROP_API intptr_t GetVariableOffset(TCppScope_t var, - TCppScope_t parent = nullptr); - -/// Checks if the provided variable is a 'Public' variable. -CPPINTEROP_API bool IsPublicVariable(TCppScope_t var); - -/// Checks if the provided variable is a 'Protected' variable. -CPPINTEROP_API bool IsProtectedVariable(TCppScope_t var); - -/// Checks if the provided variable is a 'Private' variable. -CPPINTEROP_API bool IsPrivateVariable(TCppScope_t var); - -/// Checks if the provided variable is a 'Static' variable. -CPPINTEROP_API bool IsStaticVariable(TCppScope_t var); - -/// Checks if the provided variable is a 'Constant' variable. -CPPINTEROP_API bool IsConstVariable(TCppScope_t var); - -/// Checks if the provided parameter is a Record (struct). -CPPINTEROP_API bool IsRecordType(TCppType_t type); - -/// Checks if the provided parameter is a Plain Old Data Type (POD). -CPPINTEROP_API bool IsPODType(TCppType_t type); - -/// Checks if type has an integer representation. -/// If \p s is non-null, it is set to the signedness of the type. -CPPINTEROP_API bool IsIntegerType(TCppType_t type, Signedness* s = nullptr); - -/// Checks if type has a floating representation -CPPINTEROP_API bool IsFloatingType(TCppType_t type); - -/// Checks if two types are the equivalent -/// i.e. have the same canonical type -CPPINTEROP_API bool IsSameType(TCppType_t type_a, TCppType_t type_b); - -/// Checks if type is a void pointer -CPPINTEROP_API bool IsVoidPointerType(TCppType_t type); - -/// Checks if type is a pointer -CPPINTEROP_API bool IsPointerType(TCppType_t type); - -/// Get the underlying pointee type -CPPINTEROP_API TCppType_t GetPointeeType(TCppType_t type); - -/// Checks if type is a reference -CPPINTEROP_API bool IsReferenceType(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); - -/// Get lvalue referenced type, or rvalue if rvalue is true -CPPINTEROP_API TCppType_t GetReferencedType(TCppType_t type, - bool rvalue = false); - -/// Get the pointer to type -CPPINTEROP_API TCppType_t GetPointerType(TCppType_t type); - -/// Gets the pure, Underlying Type (as opposed to the Using Type). -CPPINTEROP_API TCppType_t GetUnderlyingType(TCppType_t type); - -/// Gets the Type (passed as a parameter) as a String value. -CPPINTEROP_API std::string GetTypeAsString(TCppType_t type); - -/// Gets the Canonical Type string from the std string. A canonical type -/// is the type with any typedef names, syntactic sugars or modifiers stripped -/// out of it. -CPPINTEROP_API TCppType_t GetCanonicalType(TCppType_t type); - -/// Used to either get the built-in type of the provided string, or -/// use the name to lookup the actual type. -CPPINTEROP_API TCppType_t GetType(const std::string& type); - -///\returns the complex of the provided type. -CPPINTEROP_API TCppType_t GetComplexType(TCppType_t element_type); - -/// This will convert a class into its type, so for example, you can -/// use it to declare variables in it. -CPPINTEROP_API TCppType_t GetTypeFromScope(TCppScope_t klass); - -/// Checks if a C++ type derives from another. -CPPINTEROP_API bool IsTypeDerivedFrom(TCppType_t derived, TCppType_t base); - -/// Creates a trampoline function by using the interpreter and returns a -/// uniform interface to call it from compiled code. -CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func); - -CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, - TCppConstFunction_t func); - -/// Checks if a function declared is of const type or not. -CPPINTEROP_API bool IsConstMethod(TCppFunction_t method); - -///\returns the default argument value as string. -CPPINTEROP_API std::string GetFunctionArgDefault(TCppFunction_t func, - TCppIndex_t param_index); - -///\returns the argument name of function as string. -CPPINTEROP_API std::string GetFunctionArgName(TCppFunction_t func, - TCppIndex_t param_index); - -///\returns string representation of the operator -CPPINTEROP_API std::string GetSpellingFromOperator(Operator Operator); - -///\returns operator of representing the string -CPPINTEROP_API Operator GetOperatorFromSpelling(const std::string& op); - -///\returns arity of the operator or kNone -CPPINTEROP_API OperatorArity GetOperatorArity(TCppFunction_t op); - -///\returns list of operator overloads -CPPINTEROP_API void GetOperator(TCppScope_t scope, Operator op, - std::vector& operators, - OperatorArity kind = kBoth); - -/// Creates an owned instance of the interpreter we need for the various interop -/// services and pushes it onto a stack. -///\param[in] Args - the list of arguments for interpreter constructor. -///\param[in] CPPINTEROP_EXTRA_INTERPRETER_ARGS - an env variable, if defined, -/// adds additional arguments to the interpreter. -///\returns nullptr on failure. -CPPINTEROP_API TInterp_t -CreateInterpreter(const std::vector& Args = {}, - const std::vector& GpuArgs = {}); - -/// Deletes an instance of an interpreter. -///\param[in] I - the interpreter to be deleted, if nullptr, deletes the last. -///\returns false on failure or if \c I is not tracked in the stack. -CPPINTEROP_API bool DeleteInterpreter(TInterp_t I = nullptr); - -/// Activates an instance of an interpreter to handle subsequent API requests -///\param[in] I - the interpreter to be activated. -///\returns false on failure. -CPPINTEROP_API bool ActivateInterpreter(TInterp_t I); - -/// Checks which Interpreter backend was CppInterOp library built with (Cling, -/// Clang-REPL, etcetera). In practice, the selected interpreter should not -/// matter, since the library will function in the same way. -///\returns the current interpreter instance, if any. -CPPINTEROP_API TInterp_t GetInterpreter(); - -/// Returns the programming language of the interpreter. -CPPINTEROP_API InterpreterLanguage GetLanguage(TInterp_t I = nullptr); - -/// Returns the language standard of the interpreter. -CPPINTEROP_API InterpreterLanguageStandard -GetLanguageStandard(TInterp_t I = nullptr); - -/// Sets the Interpreter instance with an external interpreter, meant to -/// be called by an external library that manages it's own interpreter. -/// Sets a flag signifying CppInterOp does not have ownership of the -/// sInterpreter. -///\param[in] Args - the pointer to an external interpreter -CPPINTEROP_API void UseExternalInterpreter(TInterp_t I); - -/// Adds a Search Path for the Interpreter to get the libraries. -CPPINTEROP_API void AddSearchPath(const char* dir, bool isUser = true, - bool prepend = false); - -/// Returns the resource-dir path (for headers). -CPPINTEROP_API const char* GetResourceDir(); - -/// Uses the underlying clang compiler to detect the resource directory. -/// In essence calling clang -print-resource-dir and checks if it ends with -/// a compatible to CppInterOp version. -///\param[in] ClangBinaryName - the name (or the full path) of the compiler -/// to ask. -CPPINTEROP_API std::string -DetectResourceDir(const char* ClangBinaryName = "clang"); - -/// Asks the system compiler for its default include paths. -///\param[out] Paths - the list of include paths returned by eg. -/// `c++ -xc++ -E -v /dev/null 2>&1` -///\param[in] CompilerName - the name (or the full path) of the compiler -/// binary file. -CPPINTEROP_API void -DetectSystemCompilerIncludePaths(std::vector& Paths, - const char* CompilerName = "c++"); - -/// Secondary search path for headers, if not found using the -/// GetResourceDir() function. -CPPINTEROP_API void AddIncludePath(const char* dir); - -// Gets the currently used include paths -///\param[out] IncludePaths - the list of include paths -/// -CPPINTEROP_API void GetIncludePaths(std::vector& IncludePaths, - bool withSystem = false, - bool withFlags = false); - -/// Only Declares a code snippet in \c code and does not execute it. -///\returns 0 on success -CPPINTEROP_API int Declare(const char* code, bool silent = false); - -/// Declares and executes a code snippet in \c code. -///\returns 0 on success -CPPINTEROP_API int Process(const char* code); - -/// Declares, executes and returns the execution result as a intptr_t. -///\returns the expression results as a intptr_t. -CPPINTEROP_API intptr_t Evaluate(const char* code, bool* HadError = nullptr); - -/// Looks up the library if access is enabled. -///\returns the path to the library. -CPPINTEROP_API std::string LookupLibrary(const char* lib_name); - -/// Finds \c lib_stem considering the list of search paths and loads it by -/// calling dlopen. -/// \returns true on success. -CPPINTEROP_API bool LoadLibrary(const char* lib_stem, bool lookup = true); - -/// Finds \c lib_stem considering the list of search paths and unloads it by -/// calling dlclose. -/// function. -CPPINTEROP_API void UnloadLibrary(const char* lib_stem); - -/// Scans all libraries on the library search path for a given potentially -/// mangled symbol name. -///\returns the path to the first library that contains the symbol definition. -CPPINTEROP_API std::string -SearchLibrariesForSymbol(const char* mangled_name, bool search_system /*true*/); - -/// Inserts or replaces a symbol in the JIT with the one provided. This is -/// useful for providing our own implementations of facilities such as printf. -/// -///\param[in] linker_mangled_name - the name of the symbol to be inserted or -/// replaced. -///\param[in] address - the new address of the symbol. -/// -///\returns true on failure. -CPPINTEROP_API bool InsertOrReplaceJitSymbol(const char* linker_mangled_name, - uint64_t address); - -/// Tries to load provided objects in a string format (prettyprint). -CPPINTEROP_API std::string ObjToString(const char* type, void* obj); - -struct TemplateArgInfo { - TCppType_t m_Type; - const char* m_IntegralValue; - TemplateArgInfo(TCppScope_t type, const char* integral_value = nullptr) - : m_Type(type), m_IntegralValue(integral_value) {} -}; -/// Builds a template instantiation for a given templated declaration. -/// Offers a single interface for instantiation of class, function and -/// variable templates -/// -///\param[in] tmpl - Uninstantiated template class/function -///\param[in] template_args - Pointer to vector of template arguments stored -/// in the \c TemplateArgInfo struct -///\param[in] template_args_size - Size of the vector of template arguments -/// passed as \c template_args -///\param[in] instantiate_body - also instantiate/define the body -/// -///\returns Instantiated templated class/function/variable pointer -CPPINTEROP_API TCppScope_t -InstantiateTemplate(TCppScope_t tmpl, const TemplateArgInfo* template_args, - size_t template_args_size, bool instantiate_body = false); - -/// Sets the class template instantiation arguments of \c templ_instance. -/// -///\param[in] templ_instance - Pointer to the template instance -///\param[out] args - Vector of instantiation arguments -CPPINTEROP_API void -GetClassTemplateInstantiationArgs(TCppScope_t templ_instance, - std::vector& args); - -/// Instantiates a function template from a given string representation. This -/// function also does overload resolution. -///\returns the instantiated function template declaration. -CPPINTEROP_API TCppFunction_t -InstantiateTemplateFunctionFromString(const char* function_template); - -/// Finds best overload match based on explicit template parameters (if any) -/// and argument types. -/// -///\param[in] candidates - vector of overloads that come under the -/// parent scope and have the same name -///\param[in] explicit_types - set of explicitly instantiated template types -///\param[in] arg_types - set of argument types -///\returns Instantiated function pointer -CPPINTEROP_API TCppFunction_t -BestOverloadFunctionMatch(const std::vector& candidates, - const std::vector& explicit_types, - const std::vector& arg_types); - -CPPINTEROP_API void GetAllCppNames(TCppScope_t scope, - std::set& names); - -CPPINTEROP_API void DumpScope(TCppScope_t scope); - -// FIXME: Rework the GetDimensions and make this enum redundant. -namespace DimensionValue { -enum : long int { - UNKNOWN_SIZE = -1, -}; -} - -/// Gets the size/dimensions of a multi-dimension array. -CPPINTEROP_API std::vector GetDimensions(TCppType_t type); - -/// Allocates memory required by an object of a given class -/// \param[in] scope Given class for which to allocate memory for -/// \param[in] count is used to indicate the number of objects to allocate for. -CPPINTEROP_API TCppObject_t Allocate(TCppScope_t scope, - TCppIndex_t count = 1UL); - -/// Deallocates memory for a given class. -/// \param[in] scope Class to indicate size of memory to deallocate -/// \param[in] count is used to indicate the number of objects to dallocate for -CPPINTEROP_API void Deallocate(TCppScope_t scope, TCppObject_t address, - TCppIndex_t count = 1UL); - -/// Creates one or more objects of class \c scope by calling its default -/// constructor. -/// \param[in] scope Class to construct, or handle to Constructor -/// \param[in] arena If set, this API uses placement new to construct at this -/// address. -/// \param[in] is used to indicate the number of objects to construct. -/// \returns a pointer to the constructed object, which is arena if placement -/// new is used. -CPPINTEROP_API TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr, - TCppIndex_t count = 1UL); - -/// 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 scope, - bool withFree = true, TCppIndex_t count = 0UL); - -/// @name Stream Redirection -/// -///@{ - -enum CaptureStreamKind : char { - kStdOut = 1, ///< stdout - kStdErr, ///< stderr - // kStdBoth, ///< stdout and stderr - // kSTDSTRM // "&1" or "&2" is not a filename -}; - -/// Begins recording the given standard stream. -///\param[fd_kind] - The stream to be captured -CPPINTEROP_API void BeginStdStreamCapture(CaptureStreamKind fd_kind); - -/// Ends recording the standard stream and returns the result as a string. -CPPINTEROP_API std::string EndStdStreamCapture(); - -///@} - -/// Append all Code completion suggestions to Results. -///\param[out] Results - CC suggestions for code fragment. Suggestions are -/// appended. -///\param[in] code - code fragment to complete -///\param[in] complete_line - position (line) in code for suggestion -///\param[in] complete_column - position (column) in code for suggestion -CPPINTEROP_API void CodeComplete(std::vector& Results, - const char* code, unsigned complete_line = 1U, - unsigned complete_column = 1U); +namespace CppImpl { -/// Reverts the last N operations performed by the interpreter. -///\param[in] N The number of operations to undo. Defaults to 1. -///\returns 0 on success, non-zero on failure. -CPPINTEROP_API int Undo(unsigned N = 1); - -#ifndef _WIN32 -/// Returns the process ID of the executor process. -/// \returns the PID of the executor process. -CPPINTEROP_API pid_t GetExecutorPID(); -#endif +// Public API function declarations. Generated by cppinterop-tblgen +// from CppInterOp.td. Do not edit this section by hand. +#include "CppInterOp/CppInterOpDecl.inc" } // 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/CppInterOpTypes.h b/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h new file mode 100644 index 0000000000000..2e7151c05c843 --- /dev/null +++ b/interpreter/CppInterOp/include/CppInterOp/CppInterOpTypes.h @@ -0,0 +1,425 @@ +//===--- CppInterOpTypes.h - Types for CppInterOp API -----------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Opaque types, enums, and helper classes used by the CppInterOp API. +// This header is self-contained and does not declare any API functions, +// making it safe to include from both CppInterOp.h and Dispatch.h. +// +//===----------------------------------------------------------------------===// + +#ifndef CPPINTEROP_CPPINTEROPTYPES_H +#define CPPINTEROP_CPPINTEROPTYPES_H + +// The cross-platform CPPINTEROP_API macro definition +#if defined _WIN32 || defined __CYGWIN__ +#define CPPINTEROP_API __declspec(dllexport) +#else +#ifdef __GNUC__ +#define CPPINTEROP_API __attribute__((__visibility__("default"))) +#else +#define CPPINTEROP_API +#endif +#endif + +// Cross-platform deprecation attribute. Older Clang versions (Cling) +// mis-parse C++11 `[[deprecated]]` near the return type, so we use +// the vendor-specific spelling on each compiler. +#if defined(_MSC_VER) +#define CPPINTEROP_DEPRECATED(msg) __declspec(deprecated(msg)) +#elif defined(__GNUC__) || defined(__clang__) +#define CPPINTEROP_DEPRECATED(msg) __attribute__((deprecated(msg))) +#else +#define CPPINTEROP_DEPRECATED(msg) +#endif + +// C-compatible headers — usable from both C and C++. +#include +#include +#include + +#ifdef __cplusplus +#include +#include +#include +#include +#include +#include + +namespace CppImpl { +class JitCall; +} +namespace CppInternal { +namespace DispatchRaw { +// Trace-hook slot forward decls; the X-macro expansion of +// CppInterOpAPI.inc re-declares these with identical types. They're +// here so JitCall::Invoke's inline body below can reference them. +extern CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeImpl)( + const CppImpl::JitCall* JC, void* result, void** args, std::size_t nargs, + void* self); +extern CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeDestructorImpl)( + const CppImpl::JitCall* JC, void* object, unsigned long nary, int withFree); +extern CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeReturnImpl)( + const CppImpl::JitCall* JC, void* result); +} // namespace DispatchRaw +} // namespace CppInternal + +namespace CppImpl { +#endif // __cplusplus + +/// C-compatible array of opaque pointers, returned by generated C API +/// wrappers for functions that produce collections. The caller must free +/// the array by calling cppinterop_DisposeArray(). +typedef struct CppInterOpArray { + void** data; + size_t size; +} CppInterOpArray; + +/// C-compatible array of strings, returned by generated C API wrappers +/// for functions that produce string collections. Each string is +/// individually allocated with strdup(). The caller must free the array +/// by calling cppinterop_DisposeStringArray(). +typedef struct CppInterOpStringArray { + char** data; + size_t size; +} CppInterOpStringArray; + +/// Holds information for instantiating a template. +/// Standard-layout, C-compatible. +typedef struct TemplateArgInfo { + void* m_Type; + const char* m_IntegralValue; +#ifdef __cplusplus + TemplateArgInfo(void* type, const char* integral_value = nullptr) + : m_Type(type), m_IntegralValue(integral_value) {} +#endif +} TemplateArgInfo; + +#ifdef __cplusplus + +using TCppIndex_t = size_t; +using TCppScope_t = void*; +using TCppConstScope_t = const void*; +using TCppType_t = void*; +using TCppConstType_t = const void*; +using TCppFunction_t = void*; +using TCppConstFunction_t = const void*; +using TCppFuncAddr_t = void*; +using TInterp_t = void*; +using TCppObject_t = void*; + +enum Operator : unsigned char { + OP_None, + OP_New, + OP_Delete, + OP_Array_New, + OP_Array_Delete, + OP_Plus, + OP_Minus, + OP_Star, + OP_Slash, + OP_Percent, + OP_Caret, + OP_Amp, + OP_Pipe, + OP_Tilde, + OP_Exclaim, + OP_Equal, + OP_Less, + OP_Greater, + OP_PlusEqual, + OP_MinusEqual, + OP_StarEqual, + OP_SlashEqual, + OP_PercentEqual, + OP_CaretEqual, + OP_AmpEqual, + OP_PipeEqual, + OP_LessLess, + OP_GreaterGreater, + OP_LessLessEqual, + OP_GreaterGreaterEqual, + OP_EqualEqual, + OP_ExclaimEqual, + OP_LessEqual, + OP_GreaterEqual, + OP_Spaceship, + OP_AmpAmp, + OP_PipePipe, + OP_PlusPlus, + OP_MinusMinus, + OP_Comma, + OP_ArrowStar, + OP_Arrow, + OP_Call, + OP_Subscript, + OP_Conditional, + OP_Coawait, +}; + +enum OperatorArity : unsigned char { kUnary = 1, kBinary, kBoth }; +enum Signedness : unsigned char { kSigned = 1, kUnsigned }; + +/// Enum modelling CVR qualifiers. +enum QualKind : unsigned char { + Const = 1 << 0, + Volatile = 1 << 1, + Restrict = 1 << 2, + All = Const | Volatile | Restrict +}; + +/// Enum modelling programming languages. +enum class InterpreterLanguage : unsigned char { + Unknown, + Asm, + CIR, + LLVM_IR, + C, + CPlusPlus, + ObjC, + ObjCPlusPlus, + OpenCL, + OpenCLCXX, + CUDA, + HIP, + HLSL +}; + +/// Enum modelling language standards. +enum class InterpreterLanguageStandard : unsigned char { + c89, + c94, + gnu89, + c99, + gnu99, + c11, + gnu11, + c17, + gnu17, + c23, + gnu23, + c2y, + gnu2y, + cxx98, + gnucxx98, + cxx11, + gnucxx11, + cxx14, + gnucxx14, + cxx17, + gnucxx17, + cxx20, + gnucxx20, + cxx23, + gnucxx23, + cxx26, + gnucxx26, + opencl10, + opencl11, + opencl12, + opencl20, + opencl30, + openclcpp10, + openclcpp2021, + hlsl, + hlsl2015, + hlsl2016, + hlsl2017, + hlsl2018, + hlsl2021, + hlsl202x, + hlsl202y, + lang_unspecified +}; +inline QualKind operator|(QualKind a, QualKind b) { + return static_cast(static_cast(a) | + 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. +class JitCall { +public: + friend CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, + TCppConstFunction_t func); + enum Kind : char { + kUnknown = 0, + kGenericCall, + kConstructorCall, + kDestructorCall, + }; + struct ArgList { + void** m_Args = nullptr; + size_t m_ArgSize = 0; + // Clang struggles with =default... + ArgList() {} + ArgList(void** Args, size_t ArgSize) : m_Args(Args), m_ArgSize(ArgSize) {} + }; + // FIXME: Figure out how to unify the wrapper signatures. + // FIXME: Hide these implementation details by moving wrapper generation in + // this class. + // (self, nargs, args, result, nary) + using GenericCall = void (*)(void*, size_t, void**, void*); + // (result, nary, nargs, args, is_arena) + using ConstructorCall = void (*)(void*, size_t, size_t, void**, void*); + // (self, nary, withFree) + using DestructorCall = void (*)(void*, size_t, int); + +private: + union { + GenericCall m_GenericCall; + ConstructorCall m_ConstructorCall; + DestructorCall m_DestructorCall; + }; + Kind m_Kind; + TCppConstFunction_t m_FD; + JitCall() : m_GenericCall(nullptr), m_Kind(kUnknown), m_FD(nullptr) {} + JitCall(Kind K, GenericCall C, TCppConstFunction_t FD) + : m_GenericCall(C), m_Kind(K), m_FD(FD) {} + JitCall(Kind K, ConstructorCall C, TCppConstFunction_t Ctor) + : m_ConstructorCall(C), m_Kind(K), m_FD(Ctor) {} + JitCall(Kind K, DestructorCall C, TCppConstFunction_t Dtor) + : m_DestructorCall(C), m_Kind(K), m_FD(Dtor) {} + + // Trace-hook impls need private m_FD for the function-name lookup. + // CPPINTEROP_API matches the X-macro-generated decl in CppInterOpDecl.inc; + // MSVC treats a friend decl without the dllimport/dllexport attribute as + // a different-linkage redeclaration. + friend CPPINTEROP_API void + CppInterOpTraceJitCallInvokeImpl(const JitCall*, void* result, void** args, + std::size_t nargs, void* self); + friend CPPINTEROP_API void + CppInterOpTraceJitCallInvokeDestructorImpl(const JitCall*, void* object, + unsigned long nary, int withFree); + friend CPPINTEROP_API void + CppInterOpTraceJitCallInvokeReturnImpl(const JitCall*, void* result); + + /// Checks if the passed arguments are valid for the given function. + CPPINTEROP_API bool AreArgumentsValid(void* result, ArgList args, void* self, + size_t nary) const; + +public: + [[nodiscard]] Kind getKind() const { return m_Kind; } + bool isValid() const { return getKind() != kUnknown; } + bool isInvalid() const { return !isValid(); } + explicit operator bool() const { return isValid(); } + + // Specialized for calling void functions. + void Invoke(ArgList args = {}, void* self = nullptr) const { + Invoke(/*result=*/nullptr, args, self); + } + + /// Makes a call to a generic function or method. + ///\param[in] result - the location where the return result will be placed. + ///\param[in] args - a pointer to a argument list and argument size. + ///\param[in] self - the 'this pointer' of the object. + // FIXME: Adjust the arguments and their types: args_size can be unsigned; + // self can go in the end and be nullptr by default; result can be a nullptr + // by default. These changes should be synchronized with the wrapper if we + // decide to directly. + void Invoke(void* result, ArgList args = {}, void* self = nullptr) const { + // NOLINTBEGIN(*-type-union-access) + // Its possible the JitCall object deals with structor decls but went + // through Invoke + + switch (m_Kind) { + case kUnknown: + assert(0 && "Attempted to call an invalid function declaration"); + break; + + case kGenericCall: + // We pass 1UL to nary which is only relevant for structors + assert(AreArgumentsValid(result, args, self, 1UL) && "Invalid args!"); + if (auto fn = + ::CppInternal::DispatchRaw::CppInterOpTraceJitCallInvokeImpl) + fn(this, result, args.m_Args, args.m_ArgSize, self); + m_GenericCall(self, args.m_ArgSize, args.m_Args, result); + if (auto fn = ::CppInternal::DispatchRaw:: + CppInterOpTraceJitCallInvokeReturnImpl) + fn(this, result); + break; + + case kConstructorCall: + // Forward if we intended to call a constructor (nary cannot be inferred, + // so we stick to constructing a single object) + InvokeConstructor(result, /*nary=*/1UL, args, self); + break; + case kDestructorCall: + // Forward if we intended to call a dtor with only 1 parameter. + assert(!args.m_Args && "Destructor called with arguments"); + InvokeDestructor(result, /*nary=*/0UL, /*withFree=*/true); + break; + } + // NOLINTEND(*-type-union-access) + } + /// Makes a call to a destructor. + ///\param[in] object - the pointer of the object whose destructor we call. + ///\param[in] nary - the count of the objects we destruct if we deal with an + /// array of objects. + ///\param[in] withFree - true if we should call operator delete or false if + /// we should call only the destructor. + // FIXME: Change the type of withFree from int to bool in the wrapper code. + void InvokeDestructor(void* object, unsigned long nary = 0, + int withFree = true) const { + assert(m_Kind == kDestructorCall && "Wrong overload!"); + if (auto fn = ::CppInternal::DispatchRaw:: + CppInterOpTraceJitCallInvokeDestructorImpl) + fn(this, object, nary, withFree); + m_DestructorCall(object, nary, withFree); + } + + /// Makes a call to a constructor. + ///\param[in] result - the memory address at which we construct the object + /// (placement new). + ///\param[in] nary - Use array new if we have to construct an array of + /// objects (nary > 1). + ///\param[in] args - a pointer to a argument list and argument size. + ///\param[in] is_arena - a pointer that indicates if placement new is to be + /// used + // FIXME: Change the type of withFree from int to bool in the wrapper code. + void InvokeConstructor(void* result, unsigned long nary = 1, + ArgList args = {}, void* is_arena = nullptr) const { + assert(m_Kind == kConstructorCall && "Wrong overload!"); + assert(AreArgumentsValid(result, args, /*self=*/nullptr, nary) && + "Invalid args!"); + if (auto fn = ::CppInternal::DispatchRaw::CppInterOpTraceJitCallInvokeImpl) + fn(this, result, args.m_Args, args.m_ArgSize, nullptr); + m_ConstructorCall(result, nary, args.m_ArgSize, args.m_Args, is_arena); + if (auto fn = + ::CppInternal::DispatchRaw::CppInterOpTraceJitCallInvokeReturnImpl) + fn(this, result); + } +}; + +// FIXME: Rework GetDimensions to make this enum redundant. +namespace DimensionValue { +enum : long int { + UNKNOWN_SIZE = -1, +}; +} // namespace DimensionValue + +/// @name Stream Redirection +///@{ + +enum CaptureStreamKind : char { + kStdOut = 1, ///< stdout + kStdErr, ///< stderr +}; + +///@} + +} // namespace CppImpl + +#endif // __cplusplus +#endif // CPPINTEROP_CPPINTEROPTYPES_H diff --git a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h index ddbef5b1f0d78..eba24af9dcb8a 100644 --- a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h +++ b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h @@ -12,13 +12,17 @@ // and an address lookup through a C symbol allowing clients to dlopen // CppInterOp with RTLD_LOCAL, and automatically assign the API during runtime. // +// The function list is generated by cppinterop-tblgen from CppInterOp.td +// into CppInterOpAPI.inc, which is included multiple times with different +// macro definitions (X-macro pattern). +// //===----------------------------------------------------------------------===// #ifndef CPPINTEROP_DISPATCH_H #define CPPINTEROP_DISPATCH_H #ifdef CPPINTEROP_CPPINTEROP_H -#error "To use the Dispatch mechanism, do not include CppInterOp.h directly." +#error "CppInterOp.h and Dispatch.h are mutually exclusive — include only one." #endif #ifdef _WIN32 @@ -28,12 +32,13 @@ #include #endif -#include +#include "CppInterOp/CppInterOpTypes.h" #include #include #include #include +#include using CppFnPtrTy = void (*)(); ///\param[in] procname - the name of the FunctionEntry in the symbol lookup @@ -42,170 +47,18 @@ using CppFnPtrTy = void (*)(); ///\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(GetLanguage, decltype(&CppImpl::GetLanguage)) \ - DISPATCH_API(GetLanguageStandard, decltype(&CppImpl::GetLanguageStandard)) \ - 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(IsIntegerType, decltype(&CppImpl::IsIntegerType)) \ - 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) { static auto init = std::make_unique(); static void* (*getProc)(const char*); - // magic reset to keep static init inlined in function if (!name) { init = std::make_unique(); getProc = nullptr; return 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"); @@ -246,37 +99,32 @@ inline void* dlGetProcAddress(const char* name, return getProc(name); } -// 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 +// Raw function pointers populated by LoadDispatchAPI. No default arguments. +namespace CppInternal::DispatchRaw { +using namespace CppImpl; +// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + extern Ret(*DN) RawTypes; +#include "CppInterOp/CppInterOpAPI.inc" +// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) +} // namespace CppInternal::DispatchRaw +// Inline wrappers that restore default arguments and support overloads. +// CN (CppName) may differ from DN (DispatchName) for overloaded functions, +// e.g. CN=GetFunctionAddress, DN=GetFunctionAddress_fn. 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) +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + inline Ret CN DeclArgs { return DispatchRaw::DN CallArgs; } +#include "CppInterOp/CppInterOpAPI.inc" -/// 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 +/// Initialize all CppInterOp API from the dynamically loaded library. inline bool LoadDispatchAPI(const char* customLibPath = nullptr) { #ifndef NDEBUG std::cout << "[CppInterOp Dispatch] Loading CppInterOp API from " << (customLibPath ? customLibPath : "default library path") << '\n'; -#endif // NDEBUG +#endif if (customLibPath) { void* test = dlGetProcAddress("GetInterpreter", customLibPath); if (!test) { @@ -286,29 +134,24 @@ inline bool LoadDispatchAPI(const char* customLibPath = nullptr) { } } -// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) -#define DISPATCH_API(name, type) \ - name = reinterpret_cast(dlGetProcAddress(#name)); - CPPINTEROP_API_TABLE -#undef DISPATCH_API + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + DispatchRaw::DN = reinterpret_cast(dlGetProcAddress(#DN)); +#include "CppInterOp/CppInterOpAPI.inc" // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) - // Sanity check to verify that critical (and consequently all) functions are - // loaded - if (!GetInterpreter || !CreateInterpreter) { + if (!DispatchRaw::GetInterpreter || !DispatchRaw::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 - dlGetProcAddress(nullptr, nullptr); // this is a magic reset +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + DispatchRaw::DN = nullptr; +#include "CppInterOp/CppInterOpAPI.inc" + dlGetProcAddress(nullptr, nullptr); } } // namespace CppInternal::Dispatch diff --git a/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h b/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h deleted file mode 100644 index d6eefbf721970..0000000000000 --- a/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h +++ /dev/null @@ -1,472 +0,0 @@ -// NOLINTBEGIN() -#ifndef LLVM_CLANG_C_CXCPPINTEROP_H -#define LLVM_CLANG_C_CXCPPINTEROP_H - -#include "clang-c/CXErrorCode.h" -#include "clang-c/CXString.h" -#include "clang-c/ExternC.h" -#include "clang-c/Index.h" -#include "clang-c/Platform.h" - -#include -#include -#include - -LLVM_CLANG_C_EXTERN_C_BEGIN - -/** - * \defgroup CPPINTEROP_INTERPRETER_MANIP Interpreter manipulations - * - * @{ - */ - -/** - * An opaque pointer representing an interpreter context. - */ -typedef struct CXInterpreterImpl* CXInterpreter; - -/** - * Create a Clang interpreter instance from the given arguments. - * - * \param argv The arguments that would be passed to the interpreter. - * - * \param argc The number of arguments in \c argv. - * - * \returns a \c CXInterpreter. - */ -CINDEX_LINKAGE CXInterpreter clang_createInterpreter(const char* const* argv, - int argc); - -typedef void* TInterp_t; - -/** - * Bridge between C API and C++ API. - * - * \returns a \c CXInterpreter. - */ -CINDEX_LINKAGE CXInterpreter clang_createInterpreterFromRawPtr(TInterp_t I); - -/** - * Returns a pointer to the underlying interpreter. - */ -CINDEX_LINKAGE void* clang_Interpreter_getClangInterpreter(CXInterpreter I); - -/** - * Returns a \c TInterp_t and takes the ownership. - */ -CINDEX_LINKAGE TInterp_t -clang_Interpreter_takeInterpreterAsPtr(CXInterpreter I); - -/** - * Undo N previous incremental inputs. - */ -CINDEX_LINKAGE enum CXErrorCode clang_Interpreter_undo(CXInterpreter I, - unsigned int N); - -/** - * Dispose of the given interpreter context. - */ -CINDEX_LINKAGE void clang_Interpreter_dispose(CXInterpreter I); - -/** - * Describes the return result of the different routines that do the incremental - * compilation. - */ -typedef enum { - /** - * The compilation was successful. - */ - CXInterpreter_Success = 0, - /** - * The compilation failed. - */ - CXInterpreter_Failure = 1, - /** - * More more input is expected. - */ - CXInterpreter_MoreInputExpected = 2, -} CXInterpreter_CompilationResult; - -/** - * Enum to represent the programming language of the interpreter. - */ -typedef enum { - CXInterpreterLanguage_Unknown, - CXInterpreterLanguage_Asm, - CXInterpreterLanguage_CIR, - CXInterpreterLanguage_LLVM_IR, - CXInterpreterLanguage_C, - CXInterpreterLanguage_CPlusPlus, - CXInterpreterLanguage_ObjC, - CXInterpreterLanguage_ObjCPlusPlus, - CXInterpreterLanguage_OpenCL, - CXInterpreterLanguage_OpenCLCXX, - CXInterpreterLanguage_CUDA, - CXInterpreterLanguage_HIP, - CXInterpreterLanguage_HLSL -} CXInterpreterLanguage; - -/** - * Enum to represent the language standard of the interpreter. - */ -typedef enum { - CXInterpreterLanguageStandard_c89, - CXInterpreterLanguageStandard_c94, - CXInterpreterLanguageStandard_gnu89, - CXInterpreterLanguageStandard_c99, - CXInterpreterLanguageStandard_gnu99, - CXInterpreterLanguageStandard_c11, - CXInterpreterLanguageStandard_gnu11, - CXInterpreterLanguageStandard_c17, - CXInterpreterLanguageStandard_gnu17, - CXInterpreterLanguageStandard_c23, - CXInterpreterLanguageStandard_gnu23, - CXInterpreterLanguageStandard_c2y, - CXInterpreterLanguageStandard_gnu2y, - CXInterpreterLanguageStandard_cxx98, - CXInterpreterLanguageStandard_gnucxx98, - CXInterpreterLanguageStandard_cxx11, - CXInterpreterLanguageStandard_gnucxx11, - CXInterpreterLanguageStandard_cxx14, - CXInterpreterLanguageStandard_gnucxx14, - CXInterpreterLanguageStandard_cxx17, - CXInterpreterLanguageStandard_gnucxx17, - CXInterpreterLanguageStandard_cxx20, - CXInterpreterLanguageStandard_gnucxx20, - CXInterpreterLanguageStandard_cxx23, - CXInterpreterLanguageStandard_gnucxx23, - CXInterpreterLanguageStandard_cxx26, - CXInterpreterLanguageStandard_gnucxx26, - CXInterpreterLanguageStandard_opencl10, - CXInterpreterLanguageStandard_opencl11, - CXInterpreterLanguageStandard_opencl12, - CXInterpreterLanguageStandard_opencl20, - CXInterpreterLanguageStandard_opencl30, - CXInterpreterLanguageStandard_openclcpp10, - CXInterpreterLanguageStandard_openclcpp2021, - CXInterpreterLanguageStandard_hlsl, - CXInterpreterLanguageStandard_hlsl2015, - CXInterpreterLanguageStandard_hlsl2016, - CXInterpreterLanguageStandard_hlsl2017, - CXInterpreterLanguageStandard_hlsl2018, - CXInterpreterLanguageStandard_hlsl2021, - CXInterpreterLanguageStandard_hlsl202x, - CXInterpreterLanguageStandard_hlsl202y, - CXInterpreterLanguageStandard_lang_unspecified -} CXInterpreterLanguageStandard; - -/** - * Add a search path to the interpreter. - * - * \param I The interpreter. - * - * \param dir The directory to add. - * - * \param isUser Whether the directory is a user directory. - * - * \param prepend Whether to prepend the directory to the search path. - */ -CINDEX_LINKAGE void clang_Interpreter_addSearchPath(CXInterpreter I, - const char* dir, - bool isUser, bool prepend); - -/** - * Add an include path. - * - * \param I The interpreter. - * - * \param dir The directory to add. - */ -CINDEX_LINKAGE void clang_Interpreter_addIncludePath(CXInterpreter I, - const char* dir); - -/** - * Declares a code snippet in \c code and does not execute it. - * - * \param I The interpreter. - * - * \param code The code snippet to declare. - * - * \param silent Whether to suppress the diagnostics or not - * - * \returns a \c CXErrorCode. - */ -CINDEX_LINKAGE enum CXErrorCode -clang_Interpreter_declare(CXInterpreter I, const char* code, bool silent); - -/** - * Declares and executes a code snippet in \c code. - * - * \param I The interpreter. - * - * \param code The code snippet to execute. - * - * \returns a \c CXErrorCode. - */ -CINDEX_LINKAGE enum CXErrorCode clang_Interpreter_process(CXInterpreter I, - const char* code); - -/** - * An opaque pointer representing a lightweight struct that is used for carrying - * execution results. - */ -typedef void* CXValue; - -/** - * Create a CXValue. - * - * \returns a \c CXValue. - */ -CINDEX_LINKAGE CXValue clang_createValue(void); - -/** - * Dispose of the given CXValue. - * - * \param V The CXValue to dispose. - */ -CINDEX_LINKAGE void clang_Value_dispose(CXValue V); - -/** - * Declares, executes and stores the execution result to \c V. - * - * \param[in] I The interpreter. - * - * \param[in] code The code snippet to evaluate. - * - * \param[out] V The value to store the execution result. - * - * \returns a \c CXErrorCode. - */ -CINDEX_LINKAGE enum CXErrorCode -clang_Interpreter_evaluate(CXInterpreter I, const char* code, CXValue V); - -/** - * Looks up the library if access is enabled. - * - * \param I The interpreter. - * - * \param lib_name The name of the library to lookup. - * - * \returns the path to the library. - */ -CINDEX_LINKAGE CXString clang_Interpreter_lookupLibrary(CXInterpreter I, - const char* lib_name); - -/** - * Finds \c lib_stem considering the list of search paths and loads it by - * calling dlopen. - * - * \param I The interpreter. - * - * \param lib_stem The stem of the library to load. - * - * \param lookup Whether to lookup the library or not. - * - * \returns a \c CXInterpreter_CompilationResult. - */ -CINDEX_LINKAGE CXInterpreter_CompilationResult clang_Interpreter_loadLibrary( - CXInterpreter I, const char* lib_stem, bool lookup); - -/** - * Finds \c lib_stem considering the list of search paths and unloads it by - * calling dlclose. - * - * \param I The interpreter. - * - * \param lib_stem The stem of the library to unload. - */ -CINDEX_LINKAGE void clang_Interpreter_unloadLibrary(CXInterpreter I, - const char* lib_stem); - -/** - * Returns the programming language of the interpreter. - * - * \param I The interpreter. - * - * \returns CXInterpreterLanguage value. - */ -CINDEX_LINKAGE CXInterpreterLanguage -clang_Interpreter_getLanguage(CXInterpreter I); - -/** - * Returns the language standard of the interpreter. - * - * \param I The interpreter. - * - * \returns CXInterpreterLanguageStandard value. - */ -CINDEX_LINKAGE CXInterpreterLanguageStandard -clang_Interpreter_getLanguageStandard(CXInterpreter I); - -/** - * @} - */ - -/** - * \defgroup CPPINTEROP_SCOPE_MANIP Scope manipulations - * - * @{ - */ - -/** - * A fake CXCursor for working with the interpreter. - * It has the same structure as CXCursor, but unlike CXCursor, it stores a - * handle to the interpreter in the third slot of the data field. - * This pave the way for upstreaming features to the LLVM project. - */ -typedef struct { - enum CXCursorKind kind; - int xdata; - const void* data[3]; -} CXScope; - -// for debugging purposes -CINDEX_LINKAGE void clang_scope_dump(CXScope S); - -/** - * Checks if a class has a default constructor. - */ -CINDEX_LINKAGE bool clang_hasDefaultConstructor(CXScope S); - -/** - * Returns the default constructor of a class, if any. - */ -CINDEX_LINKAGE CXScope clang_getDefaultConstructor(CXScope S); - -/** - * Returns the class destructor, if any. - */ -CINDEX_LINKAGE CXScope clang_getDestructor(CXScope S); - -/** - * Returns a stringified version of a given function signature in the form: - * void N::f(int i, double d, long l = 0, char ch = 'a'). - */ -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. - */ -CINDEX_LINKAGE bool clang_isTemplatedFunction(CXScope func); - -/** - * This function performs a lookup to check if there is a templated function of - * that type. \c parent is mandatory, the global scope should be used as the - * default value. - */ -CINDEX_LINKAGE bool clang_existsFunctionTemplate(const char* name, - CXScope parent); - -typedef struct { - void* Type; - const char* IntegralValue; -} CXTemplateArgInfo; - -/** - * Builds a template instantiation for a given templated declaration. - * Offers a single interface for instantiation of class, function and variable - * templates. - * - * \param[in] tmpl The uninstantiated template class/function. - * - * \param[in] template_args The pointer to vector of template arguments stored - * in the \c TemplateArgInfo struct - * - * \param[in] template_args_size The size of the vector of template arguments - * passed as \c template_args - * - * \returns a \c CXScope representing the instantiated templated - * class/function/variable. - */ -CINDEX_LINKAGE CXScope clang_instantiateTemplate( - CXScope tmpl, CXTemplateArgInfo* template_args, size_t template_args_size); - -/** - * A fake CXType for working with the interpreter. - * It has the same structure as CXType, but unlike CXType, it stores a - * handle to the interpreter in the second slot of the data field. - */ -typedef struct { - enum CXTypeKind kind; - void* data[2]; -} CXQualType; - -/** - * Gets the string of the type that is passed as a parameter. - */ -CINDEX_LINKAGE CXString clang_getTypeAsString(CXQualType type); - -/** - * Returns the complex of the provided type. - */ -CINDEX_LINKAGE CXQualType clang_getComplexType(CXQualType eltype); - -/** - * An opaque pointer representing the object of a given type (\c CXScope). - */ -typedef void* CXObject; - -/** - * Allocates memory for the given type. - */ -CINDEX_LINKAGE CXObject clang_allocate(unsigned int n); - -/** - * Deallocates memory for a given class. - */ -CINDEX_LINKAGE void clang_deallocate(CXObject address); - -/** - * Creates an object of class \c scope and calls its default constructor. If \c - * arena is set it uses placement new. - */ -CINDEX_LINKAGE CXObject clang_construct(CXScope scope, void* arena, - size_t count = 1UL); - -/** - * Creates a trampoline function and makes a call to a generic function or - * method. - * - * \param func The function or method to call. - * - * \param result The location where the return result will be placed. - * - * \param args The arguments to pass to the invocation. - * - * \param n The number of arguments. - * - * \param self The 'this pointer' of the object. - */ -CINDEX_LINKAGE void clang_invoke(CXScope func, void* result, void** args, - size_t n, void* self); - -/** - * Calls the destructor of object of type \c type. When withFree is true it - * calls operator delete/free. - * - * \param This The object to destruct. - * - * \param type The type of the object. - * - * \param withFree Whether to call operator delete/free or not. - * - * \returns true if wrapper generation and invocation succeeded. - */ -CINDEX_LINKAGE bool clang_destruct(CXObject This, CXScope S, - bool withFree = true, size_t nary = 0UL); - -/** - * @} - */ - -LLVM_CLANG_C_EXTERN_C_END - -#endif // LLVM_CLANG_C_CXCPPINTEROP_H - // NOLINTEND() diff --git a/interpreter/CppInterOp/lib/CppInterOp/BuildInfo.inc.in b/interpreter/CppInterOp/lib/CppInterOp/BuildInfo.inc.in new file mode 100644 index 0000000000000..c78c610bf745f --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/BuildInfo.inc.in @@ -0,0 +1,13 @@ +// Auto-generated by configure_file from lib/CppInterOp/BuildInfo.inc.in. +// Included once by CppInterOp.cpp's GetBuildInfo() as a raw-string +// literal expression. Substituted values come from CMake cache vars at +// configure time. +R"BUILDINFO( build type: @CMAKE_BUILD_TYPE@ + c++ std: @CMAKE_CXX_STANDARD@ + compiler: @CMAKE_CXX_COMPILER_ID@ @CMAKE_CXX_COMPILER_VERSION@ + cling: @CPPINTEROP_USE_CLING@ + llvm: @LLVM_PACKAGE_VERSION@ + sanitizer: @LLVM_USE_SANITIZER@ + target: @CMAKE_SYSTEM_NAME@ @CMAKE_SYSTEM_PROCESSOR@ + cmake-line: @CPPINTEROP_CMAKE_INVOCATION@ +)BUILDINFO" diff --git a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt index 0c426d645fedd..398c31be9942b 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt @@ -104,11 +104,115 @@ if (LLVM_LINK_LLVM_DYLIB) ) endif(LLVM_LINK_LLVM_DYLIB) -add_llvm_library(clangCppInterOp BUILDTREE_ONLY +# Generate .inc files from CppInterOp.td using cppinterop-tblgen. +# The .td source lives here; generated output goes to the build tree's +# include/CppInterOp/ so that CppInterOp.h can #include them. +# +# For cross-compilation (Emscripten), set CPPINTEROP_TABLEGEN_EXE to a +# pre-built native cppinterop-tblgen binary, or build one in a native +# build directory first. +set(CPPINTEROP_TD_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(CPPINTEROP_TD_FILE ${CPPINTEROP_TD_DIR}/CppInterOp.td) +set(CPPINTEROP_TD_DEPS ${CPPINTEROP_TD_FILE} + ${CPPINTEROP_TD_DIR}/CppInterOpAPI.td) + +if(CPPINTEROP_TABLEGEN_EXE) + # Cross-compilation: use the provided native binary. + set(CPPINTEROP_TBLGEN_CMD ${CPPINTEROP_TABLEGEN_EXE}) + set(CPPINTEROP_TBLGEN_DEPS ${CPPINTEROP_TD_DEPS}) +elseif(NOT EMSCRIPTEN) + # Native build: use the just-built target. + set(CPPINTEROP_TBLGEN_CMD $) + set(CPPINTEROP_TBLGEN_DEPS cppinterop-tblgen ${CPPINTEROP_TD_DEPS}) +endif() + +if(CPPINTEROP_TBLGEN_CMD) + file(MAKE_DIRECTORY ${CPPINTEROP_GEN_DIR}) + + add_custom_command( + OUTPUT ${CPPINTEROP_GEN_DIR}/CppInterOpAPI.inc + COMMAND ${CPPINTEROP_TBLGEN_CMD} + -I${CPPINTEROP_TD_DIR} -gen-cppinterop-api + ${CPPINTEROP_TD_FILE} -o ${CPPINTEROP_GEN_DIR}/CppInterOpAPI.inc + DEPENDS ${CPPINTEROP_TBLGEN_DEPS} + COMMENT "Generating CppInterOpAPI.inc...") + + add_custom_command( + OUTPUT ${CPPINTEROP_GEN_DIR}/CppInterOpDecl.inc + COMMAND ${CPPINTEROP_TBLGEN_CMD} + -I${CPPINTEROP_TD_DIR} -gen-cppinterop-decl + ${CPPINTEROP_TD_FILE} -o ${CPPINTEROP_GEN_DIR}/CppInterOpDecl.inc + DEPENDS ${CPPINTEROP_TBLGEN_DEPS} + COMMENT "Generating CppInterOpDecl.inc...") + + add_custom_command( + OUTPUT ${CPPINTEROP_GEN_DIR}/CXCppInterOpDecl.inc + COMMAND ${CPPINTEROP_TBLGEN_CMD} + -I${CPPINTEROP_TD_DIR} -gen-cx-cppinterop-decl + ${CPPINTEROP_TD_FILE} -o ${CPPINTEROP_GEN_DIR}/CXCppInterOpDecl.inc + DEPENDS ${CPPINTEROP_TBLGEN_DEPS} + COMMENT "Generating CXCppInterOpDecl.inc...") + + add_custom_command( + OUTPUT ${CPPINTEROP_GEN_DIR}/CXCppInterOpImpl.inc + COMMAND ${CPPINTEROP_TBLGEN_CMD} + -I${CPPINTEROP_TD_DIR} -gen-cx-cppinterop-impl + ${CPPINTEROP_TD_FILE} -o ${CPPINTEROP_GEN_DIR}/CXCppInterOpImpl.inc + DEPENDS ${CPPINTEROP_TBLGEN_DEPS} + COMMENT "Generating CXCppInterOpImpl.inc...") + + add_custom_target(CppInterOpTableGen DEPENDS + ${CPPINTEROP_GEN_DIR}/CppInterOpAPI.inc + ${CPPINTEROP_GEN_DIR}/CppInterOpDecl.inc + ${CPPINTEROP_GEN_DIR}/CXCppInterOpDecl.inc + ${CPPINTEROP_GEN_DIR}/CXCppInterOpImpl.inc) +else() + message(FATAL_ERROR + "Cannot generate .inc files: set CPPINTEROP_TABLEGEN_EXE to a native " + "cppinterop-tblgen binary when cross-compiling.") +endif() + +# Filter cache entries to those CMake marks with HELPSTRING="No help, +# variable specified on the command line." -- the property is left on +# -D vars the project never reclaimed via set(... CACHE), so what +# survives is the user's extra args. +# See https://gitlab.kitware.com/cmake/cmake/-/work_items/19622 +get_cmake_property(_cppinterop_cache_vars CACHE_VARIABLES) +set(CPPINTEROP_CMAKE_INVOCATION "") +foreach(_v ${_cppinterop_cache_vars}) + get_property(_help CACHE ${_v} PROPERTY HELPSTRING) + if(NOT _help STREQUAL + "No help, variable specified on the command line.") + continue() + endif() + get_property(_type CACHE ${_v} PROPERTY TYPE) + if(_type STREQUAL "UNINITIALIZED") + set(_type "") + else() + set(_type ":${_type}") + endif() + string(APPEND CPPINTEROP_CMAKE_INVOCATION + " -D${_v}${_type}=\"${${_v}}\"") +endforeach() +string(STRIP "${CPPINTEROP_CMAKE_INVOCATION}" CPPINTEROP_CMAKE_INVOCATION) +configure_file(BuildInfo.inc.in + ${CPPINTEROP_GEN_DIR}/BuildInfo.inc + @ONLY) + +# If an embedding project opts out of CppInterOp's installs rules, route the +# library through LLVM's BUILDTREE_ONLY path so add_llvm_library() does not +# register it in LLVMExports or call install(TARGETS) for it. +set(_cppinterop_lib_install_args) +if(NOT CPPINTEROP_INSTALL) + list(APPEND _cppinterop_lib_install_args BUILDTREE_ONLY) +endif() +add_llvm_library(clangCppInterOp + ${_cppinterop_lib_install_args} DISABLE_LLVM_LINK_LLVM_DYLIB CppInterOp.cpp - Dispatch.cpp + CppInterOpDispatch.cpp CXCppInterOp.cpp + CXCppInterOpGenerated.cpp Tracing.cpp ${DLM} LINK_LIBS @@ -156,6 +260,9 @@ if(EMSCRIPTEN) endif(CPPINTEROP_ENABLE_TESTING) endif(EMSCRIPTEN) +if(TARGET CppInterOpTableGen) + add_dependencies(clangCppInterOp CppInterOpTableGen) +endif() target_compile_definitions(clangCppInterOp PUBLIC "_CINDEX_LIB_") # workaround for the use of `CINDEX_LINKAGE` string(REPLACE ";" "\;" _VER CPPINTEROP_VERSION) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp index 7fc1b49e07ed0..c73e66488daa8 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp @@ -1,619 +1,27 @@ -#include "clang-c/CXCppInterOp.h" +// Hand-written C API wrappers for functions the TableGen emitter +// cannot generate mechanically. Generated wrappers live in +// CXCppInterOpGenerated.cpp. + #include "CppInterOp/CppInterOp.h" -#include "Compatibility.h" -#include "clang/AST/CXXInheritance.h" -#include "clang/AST/Decl.h" -#include "clang/AST/DeclCXX.h" -#include "clang/AST/Mangle.h" -#include "clang/AST/RecordLayout.h" -#include "clang/AST/Type.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Sema/Lookup.h" -#include "clang/Sema/Sema.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ExecutionEngine/Orc/LLJIT.h" -#include "llvm/Support/Casting.h" +#include #include -#include -#include "clang-c/CXString.h" - -// copied and tweaked from libclang -namespace clang { - -CXCursorKind cxcursor_getCursorKindForDecl(const Decl* D) { - if (!D) - return CXCursor_UnexposedDecl; - - switch (D->getKind()) { - case Decl::Enum: - return CXCursor_EnumDecl; - case Decl::EnumConstant: - return CXCursor_EnumConstantDecl; - case Decl::Field: - return CXCursor_FieldDecl; - case Decl::Function: - return CXCursor_FunctionDecl; - case Decl::CXXMethod: - return CXCursor_CXXMethod; - case Decl::CXXConstructor: - return CXCursor_Constructor; - case Decl::CXXDestructor: - return CXCursor_Destructor; - case Decl::CXXConversion: - return CXCursor_ConversionFunction; - case Decl::ParmVar: - return CXCursor_ParmDecl; - case Decl::Typedef: - return CXCursor_TypedefDecl; - case Decl::TypeAlias: - return CXCursor_TypeAliasDecl; - case Decl::TypeAliasTemplate: - return CXCursor_TypeAliasTemplateDecl; - case Decl::Var: - return CXCursor_VarDecl; - case Decl::Namespace: - return CXCursor_Namespace; - case Decl::NamespaceAlias: - return CXCursor_NamespaceAlias; - case Decl::TemplateTypeParm: - return CXCursor_TemplateTypeParameter; - case Decl::NonTypeTemplateParm: - return CXCursor_NonTypeTemplateParameter; - case Decl::TemplateTemplateParm: - return CXCursor_TemplateTemplateParameter; - case Decl::FunctionTemplate: - return CXCursor_FunctionTemplate; - case Decl::ClassTemplate: - return CXCursor_ClassTemplate; - case Decl::AccessSpec: - return CXCursor_CXXAccessSpecifier; - case Decl::ClassTemplatePartialSpecialization: - return CXCursor_ClassTemplatePartialSpecialization; - case Decl::UsingDirective: - return CXCursor_UsingDirective; - case Decl::StaticAssert: - return CXCursor_StaticAssert; - case Decl::Friend: - return CXCursor_FriendDecl; - case Decl::TranslationUnit: - return CXCursor_TranslationUnit; - - case Decl::Using: - case Decl::UnresolvedUsingValue: - case Decl::UnresolvedUsingTypename: - return CXCursor_UsingDeclaration; - - case Decl::UsingEnum: - return CXCursor_EnumDecl; - - default: - if (const auto* TD = dyn_cast(D)) { - switch (TD->getTagKind()) { - case TagTypeKind::Interface: // fall through - case TagTypeKind::Struct: - return CXCursor_StructDecl; - case TagTypeKind::Class: - return CXCursor_ClassDecl; - case TagTypeKind::Union: - return CXCursor_UnionDecl; - case TagTypeKind::Enum: - return CXCursor_EnumDecl; - } - } - } - - return CXCursor_UnexposedDecl; -} - -CXTypeKind cxtype_GetBuiltinTypeKind(const BuiltinType* BT) { -#define BTCASE(K) \ - case BuiltinType::K: \ - return CXType_##K - switch (BT->getKind()) { - BTCASE(Void); - BTCASE(Bool); - BTCASE(Char_U); - BTCASE(UChar); - BTCASE(Char16); - BTCASE(Char32); - BTCASE(UShort); - BTCASE(UInt); - BTCASE(ULong); - BTCASE(ULongLong); - BTCASE(UInt128); - BTCASE(Char_S); - BTCASE(SChar); - case BuiltinType::WChar_S: - return CXType_WChar; - case BuiltinType::WChar_U: - return CXType_WChar; - BTCASE(Short); - BTCASE(Int); - BTCASE(Long); - BTCASE(LongLong); - BTCASE(Int128); - BTCASE(Half); - BTCASE(Float); - BTCASE(Double); - BTCASE(LongDouble); - BTCASE(ShortAccum); - BTCASE(Accum); - BTCASE(LongAccum); - BTCASE(UShortAccum); - BTCASE(UAccum); - BTCASE(ULongAccum); - BTCASE(Float16); - BTCASE(Float128); - BTCASE(NullPtr); - default: - return CXType_Unexposed; - } -#undef BTCASE -} - -CXTypeKind cxtype_GetTypeKind(QualType T) { - const Type* TP = T.getTypePtrOrNull(); - if (!TP) - return CXType_Invalid; - -#define TKCASE(K) \ - case Type::K: \ - return CXType_##K - switch (TP->getTypeClass()) { - case Type::Builtin: - return cxtype_GetBuiltinTypeKind(cast(TP)); - TKCASE(Complex); - TKCASE(Pointer); - TKCASE(BlockPointer); - TKCASE(LValueReference); - TKCASE(RValueReference); - TKCASE(Record); - TKCASE(Enum); - TKCASE(Typedef); - TKCASE(ObjCInterface); - TKCASE(ObjCObject); - TKCASE(ObjCObjectPointer); - TKCASE(ObjCTypeParam); - TKCASE(FunctionNoProto); - TKCASE(FunctionProto); - TKCASE(ConstantArray); - TKCASE(IncompleteArray); - TKCASE(VariableArray); - TKCASE(DependentSizedArray); - TKCASE(Vector); - TKCASE(ExtVector); - TKCASE(MemberPointer); - TKCASE(Auto); -#if CLANG_VERSION_MAJOR < 22 - TKCASE(Elaborated); -#endif - TKCASE(Pipe); - TKCASE(Attributed); - TKCASE(BTFTagAttributed); - TKCASE(Atomic); - default: - return CXType_Unexposed; - } -#undef TKCASE -} - -// FIXME: merge with cxcursor and cxtype in the future -namespace cxscope { - -CXScope MakeCXScope(const clang::Decl* D, const CXInterpreterImpl* I, - SourceRange RegionOfInterest = SourceRange(), - bool FirstInDeclGroup = true) { - assert(D && I && "Invalid arguments!"); - - CXCursorKind K = cxcursor_getCursorKindForDecl(D); - - CXScope S = {K, 0, {D, (void*)(intptr_t)(FirstInDeclGroup ? 1 : 0), I}}; - return S; -} - -CXQualType MakeCXQualType(const clang::QualType Ty, CXInterpreterImpl* I) { - CXTypeKind TK = CXType_Invalid; - TK = cxtype_GetTypeKind(Ty); - - CXQualType CT = {TK, - {TK == CXType_Invalid ? nullptr : Ty.getAsOpaquePtr(), - static_cast(I)}}; - return CT; -} - -} // namespace cxscope - -} // namespace clang - -CXString makeCXString(const std::string& S) { - CXString Str; - if (S.empty()) { - Str.data = ""; - Str.private_flags = 0; // CXS_Unmanaged - } else { - Str.data = strdup(S.c_str()); - Str.private_flags = 1; // CXS_Malloc - } - return Str; -} - -CXStringSet* makeCXStringSet(const std::vector& Strs) { - auto* Set = new CXStringSet; // NOLINT(*-owning-memory) - Set->Count = Strs.size(); - Set->Strings = new CXString[Set->Count]; // NOLINT(*-owning-memory) - for (auto En : llvm::enumerate(Strs)) { - Set->Strings[En.index()] = makeCXString(En.value()); - } - return Set; -} - -struct CXInterpreterImpl { - std::unique_ptr Interp; - // FIXME: find a way to merge this with libclang's CXTranslationUnit - // std::unique_ptr TU; -}; - -static inline compat::Interpreter* getInterpreter(const CXInterpreterImpl* I) { - assert(I && "Invalid interpreter"); - return I->Interp.get(); -} - -CXInterpreter clang_createInterpreter(const char* const* argv, int argc) { - auto I = std::make_unique(); -#ifdef CPPINTEROP_USE_CLING - I->Interp = std::make_unique(argc, argv); -#else - I->Interp = compat::Interpreter::create(argc, argv); - if (!I->Interp) { - return nullptr; - } -#endif - // create a bridge between CXTranslationUnit and clang::Interpreter - // auto AU = std::make_unique(false); - // AU->FileMgr = I->Interp->getCompilerInstance().getFileManager(); - // AU->SourceMgr = I->Interp->getCompilerInstance().getSourceManager(); - // AU->PP = I->Interp->getCompilerInstance().getPreprocessor(); - // AU->Ctx = &I->Interp->getSema().getASTContext(); - // I->TU.reset(MakeCXTranslationUnit(static_cast(clang_createIndex(0, - // 0)), AU)); - return I.release(); -} - -CXInterpreter clang_createInterpreterFromRawPtr(TInterp_t I) { - auto* II = new CXInterpreterImpl(); // NOLINT(*-owning-memory) - II->Interp.reset(static_cast(I)); // NOLINT(*-cast) - return II; -} - -void* clang_Interpreter_getClangInterpreter(CXInterpreter I) { -#ifdef CPPINTEROP_USE_CLING - return nullptr; -#else - auto* interp = getInterpreter(I); - auto* clInterp = &static_cast(*interp); - return clInterp; -#endif // CPPINTEROP_USE_CLING -} - -TInterp_t clang_Interpreter_takeInterpreterAsPtr(CXInterpreter I) { - return static_cast(I)->Interp.release(); -} - -enum CXErrorCode clang_Interpreter_undo(CXInterpreter I, unsigned int N) { -#ifdef CPPINTEROP_USE_CLING - auto* interp = getInterpreter(I); - cling::Interpreter::PushTransactionRAII RAII(interp); - interp->unload(N); - return CXError_Success; -#else - return getInterpreter(I)->Undo(N) ? CXError_Failure : CXError_Success; -#endif // CPPINTEROP_USE_CLING -} - -void clang_Interpreter_dispose(CXInterpreter I) { - delete I; // NOLINT(*-owning-memory) -} - -void clang_Interpreter_addSearchPath(CXInterpreter I, const char* dir, - bool isUser, bool prepend) { - auto* interp = getInterpreter(I); - interp->getDynamicLibraryManager()->addSearchPath(dir, isUser, prepend); -} - -void clang_Interpreter_addIncludePath(CXInterpreter I, const char* dir) { - getInterpreter(I)->AddIncludePath(dir); -} - -namespace CppImpl { -int Declare(compat::Interpreter& interp, const char* code, bool silent); -} // namespace CppImpl - -enum CXErrorCode clang_Interpreter_declare(CXInterpreter I, const char* code, - bool silent) { - auto* interp = getInterpreter(I); - - const auto result = Cpp::Declare(*interp, code, silent); - if (result) - return CXError_Failure; - - return CXError_Success; -} - -enum CXErrorCode clang_Interpreter_process(CXInterpreter I, const char* code) { - if (getInterpreter(I)->process(code)) - return CXError_Failure; - - return CXError_Success; -} - -CXValue clang_createValue(void) { - auto val = std::make_unique(); - return val.release(); -} - -void clang_Value_dispose(CXValue V) { - delete static_cast(V); // NOLINT(*-owning-memory) -} - -enum CXErrorCode clang_Interpreter_evaluate(CXInterpreter I, const char* code, - CXValue V) { - auto* val = static_cast(V); - - if (getInterpreter(I)->evaluate(code, *val)) - return CXError_Failure; - - return CXError_Success; -} - -CXString clang_Interpreter_lookupLibrary(CXInterpreter I, - const char* lib_name) { - auto* interp = getInterpreter(I); - return makeCXString( - interp->getDynamicLibraryManager()->lookupLibrary(lib_name)); -} - -CXInterpreter_CompilationResult -clang_Interpreter_loadLibrary(CXInterpreter I, const char* lib_stem, - bool lookup) { - auto* interp = getInterpreter(I); - return static_cast( - interp->loadLibrary(lib_stem, lookup)); -} - -void clang_Interpreter_unloadLibrary(CXInterpreter I, const char* lib_stem) { - auto* interp = getInterpreter(I); - interp->getDynamicLibraryManager()->unloadLibrary(lib_stem); -} - -CXInterpreterLanguage clang_Interpreter_getLanguage(CXInterpreter I) { - return static_cast( - Cpp::GetLanguage(getInterpreter(I))); -} - -CXInterpreterLanguageStandard -clang_Interpreter_getLanguageStandard(CXInterpreter I) { - return static_cast( - Cpp::GetLanguageStandard(getInterpreter(I))); -} - -CXString clang_Interpreter_searchLibrariesForSymbol(CXInterpreter I, - const char* mangled_name, - bool search_system) { - auto* interp = getInterpreter(I); - return makeCXString( - interp->getDynamicLibraryManager()->searchLibrariesForSymbol( - mangled_name, search_system)); -} - -namespace CppImpl { -bool InsertOrReplaceJitSymbol(compat::Interpreter& I, - const char* linker_mangled_name, - uint64_t address); -} // namespace CppImpl - -bool clang_Interpreter_insertOrReplaceJitSymbol(CXInterpreter I, - const char* linker_mangled_name, - uint64_t address) { - return Cpp::InsertOrReplaceJitSymbol(*getInterpreter(I), linker_mangled_name, - address); -} - -static inline clang::QualType getType(const CXQualType& Ty) { - return clang::QualType::getFromOpaquePtr(Ty.data[0]); -} - -static inline CXInterpreterImpl* getNewTU(const CXQualType& Ty) { - return static_cast(Ty.data[1]); -} - -static inline compat::Interpreter* getInterpreter(const CXQualType& Ty) { - return getInterpreter(static_cast(Ty.data[1])); -} - -CXString clang_getTypeAsString(CXQualType type) { - const clang::QualType QT = getType(type); - const auto& C = getInterpreter(type)->getSema().getASTContext(); - clang::PrintingPolicy Policy = C.getPrintingPolicy(); - Policy.Bool = true; // Print bool instead of _Bool. - Policy.SuppressTagKeyword = true; // Do not print `class std::string`. - return makeCXString(QT.getAsString(Policy)); -} - -CXQualType clang_getComplexType(CXQualType eltype) { - const auto& C = getInterpreter(eltype)->getSema().getASTContext(); - return clang::cxscope::MakeCXQualType(C.getComplexType(getType(eltype)), - getNewTU(eltype)); -} - -static inline bool isNull(const CXScope& S) { return !S.data[0]; } - -static inline clang::Decl* getDecl(const CXScope& S) { - return const_cast(static_cast(S.data[0])); -} - -static inline const CXInterpreterImpl* getNewTU(const CXScope& S) { - return static_cast(S.data[2]); -} - -static inline CXCursorKind kind(const CXScope& S) { return S.kind; } - -static inline compat::Interpreter* getInterpreter(const CXScope& S) { - return getInterpreter(static_cast(S.data[2])); -} - -void clang_scope_dump(CXScope S) { getDecl(S)->dump(); } - -bool clang_hasDefaultConstructor(CXScope S) { - auto* D = getDecl(S); - - if (const auto* CXXRD = llvm::dyn_cast_or_null(D)) - return CXXRD->hasDefaultConstructor(); - - return false; -} - -CXScope clang_getDefaultConstructor(CXScope S) { - if (!clang_hasDefaultConstructor(S)) - return clang::cxscope::MakeCXScope(nullptr, getNewTU(S)); - - auto* CXXRD = llvm::dyn_cast_or_null(getDecl(S)); - if (!CXXRD) - return clang::cxscope::MakeCXScope(nullptr, getNewTU(S)); - - const auto* Res = - getInterpreter(S)->getSema().LookupDefaultConstructor(CXXRD); - return clang::cxscope::MakeCXScope(Res, getNewTU(S)); -} +#include -CXScope clang_getDestructor(CXScope S) { - auto* D = getDecl(S); +extern "C" { - if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { - getInterpreter(S)->getSema().ForceDeclarationOfImplicitMembers(CXXRD); - return clang::cxscope::MakeCXScope(CXXRD->getDestructor(), getNewTU(S)); +// GetClassTemplatedMethods returns bool AND fills a vector out-param. +// The C wrapper drops the bool (caller checks arr.size > 0 instead). +CPPINTEROP_API Cpp::CppInterOpArray +cppinterop_GetClassTemplatedMethods(const char* name, void* parent) { + std::vector out; + Cpp::GetClassTemplatedMethods(std::string(name), parent, out); + Cpp::CppInterOpArray arr = {nullptr, out.size()}; + if (arr.size) { + arr.data = static_cast(malloc(arr.size * sizeof(void*))); + memcpy(arr.data, out.data(), arr.size * sizeof(void*)); } - - return clang::cxscope::MakeCXScope(nullptr, getNewTU(S)); -} - -CXString clang_getFunctionSignature(CXScope func) { - if (isNull(func)) - return makeCXString(""); - - auto* D = getDecl(func); - if (const auto* FD = llvm::dyn_cast(D)) { - std::string Signature; - llvm::raw_string_ostream SS(Signature); - const auto& C = getInterpreter(func)->getSema().getASTContext(); - clang::PrintingPolicy Policy = C.getPrintingPolicy(); - // Skip printing the body - Policy.TerseOutput = true; - Policy.FullyQualifiedName = true; - Policy.SuppressDefaultTemplateArgs = false; - FD->print(SS, Policy); - SS.flush(); - return makeCXString(Signature); - } - - return makeCXString(""); + return arr; } -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)) - return true; - - if (const auto* FD = llvm::dyn_cast_or_null(D)) { - const auto TK = FD->getTemplatedKind(); - return TK == clang::FunctionDecl::TemplatedKind:: - TK_FunctionTemplateSpecialization || - TK == clang::FunctionDecl::TemplatedKind:: - TK_DependentFunctionTemplateSpecialization || - TK == clang::FunctionDecl::TemplatedKind::TK_FunctionTemplate; - } - - return false; -} - -bool clang_existsFunctionTemplate(const char* name, CXScope parent) { - if (kind(parent) == CXCursor_FirstInvalid || !name) - return false; - - const auto* Within = llvm::dyn_cast(getDecl(parent)); - - auto& S = getInterpreter(parent)->getSema(); - auto* ND = CppInternal::utils::Lookup::Named(&S, name, Within); - - if (!ND) - return false; - - if (intptr_t(ND) != (intptr_t)-1) - return clang_isTemplatedFunction( - clang::cxscope::MakeCXScope(ND, getNewTU(parent))); - - // FIXME: Cycle through the Decls and check if there is a templated - return true; -} - -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 CppImpl - -CXScope clang_instantiateTemplate(CXScope tmpl, - CXTemplateArgInfo* template_args, - size_t template_args_size) { - auto* I = getInterpreter(tmpl); - - llvm::SmallVector Info; - for (size_t i = 0; i < template_args_size; ++i) { - Info.push_back(Cpp::TemplateArgInfo(template_args[i].Type, - template_args[i].IntegralValue)); - } - - auto* D = static_cast(Cpp::InstantiateTemplate( - *I, static_cast(getDecl(tmpl)), Info.data(), template_args_size)); - - return clang::cxscope::MakeCXScope(D, getNewTU(tmpl)); -} - -CXObject clang_allocate(unsigned int n) { return ::operator new(n); } - -void clang_deallocate(CXObject address) { ::operator delete(address); } - -namespace CppImpl { -void* Construct(compat::Interpreter& interp, TCppScope_t scope, - void* arena /*=nullptr*/, TCppIndex_t count); -} // namespace CppImpl - -CXObject clang_construct(CXScope scope, void* arena, size_t count) { - return Cpp::Construct(*getInterpreter(scope), - static_cast(getDecl(scope)), arena, count); -} - -void clang_invoke(CXScope func, void* result, void** args, size_t n, - void* self) { - Cpp::MakeFunctionCallable(getInterpreter(func), getDecl(func)) - .Invoke(result, {args, n}, self); -} - -namespace CppImpl { -bool Destruct(compat::Interpreter& interp, TCppObject_t This, - const clang::Decl* Class, bool withFree, size_t nary); -} // namespace CppImpl - -bool clang_destruct(CXObject This, CXScope S, bool withFree, size_t nary) { - return Cpp::Destruct(*getInterpreter(S), This, getDecl(S), withFree, nary); -} +} // extern "C" diff --git a/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOpGenerated.cpp b/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOpGenerated.cpp new file mode 100644 index 0000000000000..10060fc82891e --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOpGenerated.cpp @@ -0,0 +1,15 @@ +// Auto-generated C API forwarding implementations. +// Each function calls the corresponding Cpp:: C++ API. + +#include "CppInterOp/CppInterOp.h" + +#include +#include + +using Cpp::CppInterOpArray; +using Cpp::CppInterOpStringArray; +using Cpp::TemplateArgInfo; + +extern "C" { +#include "CppInterOp/CXCppInterOpImpl.inc" +} diff --git a/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h b/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h index b16409f4fc07b..294c6b1b960f1 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h +++ b/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h @@ -235,11 +235,17 @@ inline void codeComplete(std::vector& Results, #include "llvm/Support/Error.h" #include "llvm/TargetParser/Host.h" -#ifdef LLVM_BUILT_WITH_OOP_JIT +#if LLVM_VERSION_MAJOR > 21 #include "clang/Basic/Version.h" +#include "clang/Interpreter/IncrementalExecutor.h" #include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#endif +#if LLVM_VERSION_MAJOR > 21 && !defined(_WIN32) +#include #include #endif @@ -365,6 +371,73 @@ inline bool detectNVPTXArch(std::string& Arch) { return true; } +#if LLVM_VERSION_MAJOR > 21 +/// Directory containing libclangCppInterOp itself, derived via +/// `dladdr` of an in-library function pointer. Returns empty when the +/// platform has no self-DSO discovery (Windows -- a `GetModuleHandleEx` +/// port can be added when Windows OOP support arrives). +inline std::string findOwnLibraryDir() { +#if !defined(_WIN32) + Dl_info info{}; + if (!dladdr(reinterpret_cast(&findOwnLibraryDir), &info) || + !info.dli_fname || !*info.dli_fname) + return {}; + llvm::SmallString<256> P(info.dli_fname); + llvm::sys::path::remove_filename(P); + return std::string(P.str()); +#else + return {}; +#endif +} + +/// Wire CppInterOp's bundled OOP runtime parts into `B`. Probes a +/// layered list of candidate directories, in priority order: +/// 1. `$CPPINTEROP_RUNTIME_DIR` -- sysadmin override. +/// 2. `/cppinterop-rt` -- relocatable, follows +/// the .so wherever a package manager moved it. +/// 3. `CPPINTEROP_RUNTIME_BUILD_DIR` -- in-tree test runs. +/// 4. `CPPINTEROP_RUNTIME_INSTALL_DIR` -- baked install path; last +/// resort when self-DSO discovery isn't available (e.g. static +/// link of CppInterOp into a host binary). +/// `UpdateOrcRuntimePathCB` is replaced with a no-op so the upstream +/// resource-dir prefix check inside +/// `IncrementalExecutorBuilder::UpdateOrcRuntimePath` +/// (`clang/lib/Interpreter/IncrementalExecutor.cpp`, the +/// `consume_front(parent_path(D.Dir))` guard) doesn't run -- our +/// runtime lives outside the host's clang resource tree. +inline bool configureBundledOOPRuntime(clang::IncrementalExecutorBuilder& B) { + llvm::SmallVector Candidates; + if (const char* Env = std::getenv("CPPINTEROP_RUNTIME_DIR")) + Candidates.emplace_back(Env); + if (std::string OwnDir = findOwnLibraryDir(); !OwnDir.empty()) { + llvm::SmallString<256> P(OwnDir); + llvm::sys::path::append(P, "cppinterop-rt"); + Candidates.emplace_back(P.str()); + } +#if defined(CPPINTEROP_RUNTIME_BUILD_DIR) + Candidates.emplace_back(CPPINTEROP_RUNTIME_BUILD_DIR); +#endif +#if defined(CPPINTEROP_RUNTIME_INSTALL_DIR) + Candidates.emplace_back(CPPINTEROP_RUNTIME_INSTALL_DIR); +#endif + for (const std::string& Dir : Candidates) { + llvm::SmallString<256> OrcRT(Dir); + llvm::sys::path::append(OrcRT, "liborc_rt.a"); + llvm::SmallString<256> Exec(Dir); + llvm::sys::path::append(Exec, "llvm-jitlink-executor"); + if (!llvm::sys::fs::exists(OrcRT) || !llvm::sys::fs::exists(Exec)) + continue; + B.OrcRuntimePath = std::string(OrcRT.str()); + B.OOPExecutor = std::string(Exec.str()); + B.UpdateOrcRuntimePathCB = [](const clang::driver::Compilation&) { + return llvm::Error::success(); + }; + return true; + } + return false; +} +#endif // LLVM_VERSION_MAJOR > 21 + inline std::unique_ptr createClangInterpreter(std::vector& args, int stdin_fd = -1, int stdout_fd = -1, int stderr_fd = -1) { @@ -392,6 +465,34 @@ createClangInterpreter(std::vector& args, int stdin_fd = -1, clang::IncrementalCompilerBuilder CB; CB.SetCompilerArgs(CompilerArgs); +#if LLVM_VERSION_MAJOR > 21 && !defined(_WIN32) + bool outOfProcess = false; + const bool oopRequested = + std::any_of(args.begin(), args.end(), [](const char* arg) { + return llvm::StringRef(arg).trim() == "--use-oop-jit"; + }); + // The IncrementalExecutorBuilder must outlive the IncrementalCompiler + // it gets attached to, so it's a unique_ptr at function scope. + std::unique_ptr OutOfProcessConfig; + if (oopRequested) { + OutOfProcessConfig = std::make_unique(); + OutOfProcessConfig->IsOutOfProcess = true; + if (configureBundledOOPRuntime(*OutOfProcessConfig)) { + outOfProcess = true; + CB.SetDriverCompilationCallback( + OutOfProcessConfig->UpdateOrcRuntimePathCB); + } else { + llvm::errs() + << "[CreateClangInterpreter]: --use-oop-jit requested but the " + "bundled OOP runtime " + "(/cppinterop-rt/{liborc_rt.a,llvm-jitlink-executor}) " + "is missing from CppInterOp's build/install tree. Falling " + "back to in-process JIT.\n"; + OutOfProcessConfig.reset(); + } + } +#endif + std::unique_ptr DeviceCI; if (CudaEnabled) { if (OffloadArch.empty()) @@ -421,56 +522,29 @@ createClangInterpreter(std::vector& args, int stdin_fd = -1, 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 LLVM_VERSION_MAJOR > 21 && !defined(_WIN32) 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 + // OrcRuntimePath and OOPExecutor were populated by + // configureBundledOOPRuntime() above; UpdateOrcRuntimePathCB was + // replaced with a no-op there too, so the upstream auto-discovery + // safety check doesn't run. + OutOfProcessConfig->UseSharedMemory = false; + OutOfProcessConfig->SlabAllocateSize = 0; + OutOfProcessConfig->CustomizeFork = [stdin_fd, stdout_fd, stderr_fd]() { 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); + CudaEnabled ? clang::Interpreter::createWithCUDA(std::move(*ciOrErr), + std::move(DeviceCI)) + : clang::Interpreter::create( + std::move(*ciOrErr), + outOfProcess ? std::move(OutOfProcessConfig) : nullptr); #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)) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 2ba129d8e193a..b00684a190c34 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -13,6 +13,22 @@ #include "Sins.h" // for access to private members #include "Tracing.h" +// MSan workaround for clang-repl <= 22: __clang_Interpreter_SetValueNoAlloc +// receives JIT-emitted values through varargs, and MSan cannot track shadow +// across that JIT/native boundary -- the returned Value carries correct bits +// but an uninit shadow, which trips downstream reads (convertTo, ~Value). +// Fixed upstream in llvm/llvm-project#196894; unpoison locally for older LLVM. +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) && LLVM_VERSION_MAJOR <= 22 +#include +#define CPPINTEROP_MSAN_UNPOISON_VALUE(v) __msan_unpoison(&(v), sizeof(v)) +#else +#define CPPINTEROP_MSAN_UNPOISON_VALUE(v) ((void)0) +#endif +#else +#define CPPINTEROP_MSAN_UNPOISON_VALUE(v) ((void)0) +#endif + #include "clang/AST/Attrs.inc" #include "clang/AST/CXXInheritance.h" #include "clang/AST/Comment.h" @@ -109,6 +125,7 @@ #include #include #endif // WIN32 +#include // Runtime symbols required if the library using JIT (Cpp::Evaluate) does // not link to llvm @@ -187,18 +204,33 @@ struct InterpreterInfo { }; static void DefaultProcessCrashHandler(void*); + +/// Set by UseExternalInterpreter to suppress llvm_shutdown at process exit +/// -- the client owns LLVM in that case. +static bool SkipShutDown = false; + +/// RAII guard whose dtor calls llvm_shutdown for the owned-interpreter case. +/// Constructed as a function-local static AFTER sInterpreters, so its dtor +/// fires FIRST (reverse-of-construction); llvm_shutdown then drains the +/// ManagedStatic registry, including sInterpreters, deterministically. +/// The llvm_shutdown call itself is gated on LLVM 23+, where +/// Platform::lookupResolvedInitSymbols (llvm/llvm-project#196874) makes +/// ~Interpreter's JIT deinit skip lazy materialization. On older LLVM +/// the same chain SEGFAULTs in cleanUp against destroyed function-local +/// statics, so the dtor is a no-op and sInterpreters leaks instead. +struct InterpreterShutdown { + ~InterpreterShutdown() { + if (!SkipShutDown) { +#if LLVM_VERSION_MAJOR > 22 + llvm::llvm_shutdown(); +#endif + } + } +}; + // Function-static storage for interpreters static std::deque& GetInterpreters(bool SetCrashHandler = true) { - // static int FakeArgc = 1; - // static const std::string VersionStr = GetVersion(); - // static const char* ArgvBuffer[] = {VersionStr.c_str(), nullptr}; - // static const char** FakeArgv = ArgvBuffer; - // static llvm::InitLLVM X(FakeArgc, FakeArgv); - // Cannot be a llvm::ManagedStatic because X will call shutdown which will - // trigger destruction on llvm::ManagedStatics and the destruction of the - // InterpreterInfos require to have llvm around. - // FIXME: Currently we never call llvm::llvm_shutdown and sInterpreters leaks. static llvm::ManagedStatic> sInterpreters; static std::once_flag ProcessInitialized; std::call_once(ProcessInitialized, [SetCrashHandler]() { @@ -215,13 +247,21 @@ GetInterpreters(bool SetCrashHandler = true) { llvm::InitializeAllAsmParsers(); llvm::InitializeAllAsmPrinters(); - if (SetCrashHandler) + // Pipe / OOM / crash handlers replicate what InitLLVM did before it was + // dropped. Skipped when the host owns LLVM -- they're its decision. + if (SetCrashHandler) { + llvm::sys::SetOneShotPipeSignalFunction( + llvm::sys::DefaultOneShotPipeSignalHandler); llvm::sys::AddSignalHandler(DefaultProcessCrashHandler, /*Cookie=*/nullptr); - - // std::atexit(llvm::llvm_shutdown); + llvm::install_out_of_memory_new_handler(); + } }); + // Constructed after sInterpreters above, so its dtor fires first at + // process exit; see InterpreterShutdown. + static InterpreterShutdown Shutdown; + return *sInterpreters; } @@ -232,9 +272,8 @@ static void DefaultProcessCrashHandler(void*) { llvm::errs() << "\n**************************************************\n"; llvm::errs() << " CppInterOp CRASH DETECTED\n"; - if (CppInterOp::Tracing::TraceInfo::TheTraceInfo) { - std::string Path = - CppInterOp::Tracing::TraceInfo::TheTraceInfo->writeToFile(); + if (CppInterOp::Tracing::TheTraceInfo) { + std::string Path = CppInterOp::Tracing::TheTraceInfo->writeToFile(); if (!Path.empty()) llvm::errs() << " Reproducer saved to: " << Path << "\n"; else @@ -299,6 +338,7 @@ TInterp_t GetInterpreter() { void UseExternalInterpreter(TInterp_t I) { INTEROP_TRACE(I); assert(GetInterpreters(false).empty() && "sInterpreter already in use!"); + SkipShutDown = true; RegisterInterpreter(static_cast(I), /*Owned=*/false); return INTEROP_VOID_RETURN(); } @@ -424,44 +464,67 @@ bool JitCall::AreArgumentsValid(void* result, ArgList args, void* self, return Valid; } -void JitCall::ReportInvokeStart(void* result, ArgList args, void* self) const { +// Trace-hook impls reached via DispatchRaw from JitCall's inline body. +// Off-trace the slot is nullptr and these never run. +void CppInterOpTraceJitCallInvokeImpl(const JitCall* JC, void* result, + void** args, std::size_t nargs, + void* self) { + auto* TI = CppInterOp::Tracing::TheTraceInfo; + if (!TI) + return; std::string Name; llvm::raw_string_ostream OS(Name); - auto* FD = (const FunctionDecl*)m_FD; + const auto* FD = static_cast(JC->m_FD); FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), /*Qualified=*/true); LLVM_DEBUG(dbgs() << "Run '" << Name << "', compiled at: " - << (void*)m_GenericCall << " with result at: " << result - << " , args at: " << args.m_Args << " , arg count: " - << args.m_ArgSize << " , self at: " << self << "\n";); - - if (auto* TI = CppInterOp::Tracing::TraceInfo::TheTraceInfo) { - std::string SelfPart = self ? TI->lookupHandle(self) : ""; - std::string Entry = - llvm::formatv(" // JitCall::Invoke {0}(nargs={1}, self={2})", Name, - args.m_ArgSize, SelfPart.empty() ? "nullptr" : SelfPart); - TI->appendToLog(Entry); - } -} - -void JitCall::ReportInvokeStart(void* object, unsigned long nary, - int withFree) const { + << (void*)JC->m_GenericCall << " with result at: " << result + << " , args at: " << args << " , arg count: " << nargs + << " , self at: " << self << "\n";); + std::string SelfPart = self ? TI->lookupHandle(self) : ""; + TI->appendToLog(llvm::formatv(" // JitCall::Invoke {0}(nargs={1}, self={2})", + Name, nargs, + SelfPart.empty() ? "nullptr" : SelfPart)); +} + +void CppInterOpTraceJitCallInvokeDestructorImpl(const JitCall* JC, void* object, + unsigned long nary, + int withFree) { + auto* TI = CppInterOp::Tracing::TheTraceInfo; + if (!TI) + return; std::string Name; llvm::raw_string_ostream OS(Name); - auto* FD = (const FunctionDecl*)m_FD; + const auto* FD = static_cast(JC->m_FD); FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), /*Qualified=*/true); LLVM_DEBUG(dbgs() << "Finish '" << Name - << "', compiled at: " << (void*)m_DestructorCall); - - if (auto* TI = CppInterOp::Tracing::TraceInfo::TheTraceInfo) { - std::string ObjPart = object ? TI->lookupHandle(object) : "nullptr"; - std::string Entry = llvm::formatv( - " // JitCall::InvokeDestructor {0}(object={1}, nary={2}, " - "withFree={3})", - Name, ObjPart, nary, withFree); - TI->appendToLog(Entry); + << "', compiled at: " << (void*)JC->m_DestructorCall); + std::string ObjPart = object ? TI->lookupHandle(object) : "nullptr"; + TI->appendToLog( + llvm::formatv(" // JitCall::InvokeDestructor {0}(object={1}, nary={2}, " + "withFree={3})", + Name, ObjPart, nary, withFree)); +} + +// Post-invoke trace hook reached via DispatchRaw. Constructors and +// pointer/reference returns deposit a fresh T* at *result; registering +// it as vN lets later trace lines render the name instead of +// `nullptr /*unknown*/`. No-op for value or void returns. +void CppInterOpTraceJitCallInvokeReturnImpl(const JitCall* JC, void* result) { + auto* TI = CppInterOp::Tracing::TheTraceInfo; + if (!TI || !result) + return; + const auto* FD = static_cast(JC->m_FD); + bool RegisterPtr = isa(FD); + if (!RegisterPtr) { + QualType RT = FD->getReturnType(); + RegisterPtr = RT->isPointerType() || RT->isReferenceType(); } + if (!RegisterPtr) + return; + if (void* p = *static_cast(result)) + TI->getOrRegisterHandle(p); } #undef DEBUG_TYPE @@ -481,6 +544,19 @@ std::string GetVersion() { "])\n"); } +std::string GetBuildInfo() { + INTEROP_TRACE(); + // The right-hand side is a raw-string literal expression generated from + // BuildInfo.inc.in via configure_file at CMake-configure time; it carries + // the filtered CACHE_VARIABLES snapshot. Kept out of the INTEROP_RETURN + // macro call so the preprocessor is not asked to expand a directive + // inside macro arguments. + std::string Info = +#include "CppInterOp/BuildInfo.inc" + ; + return INTEROP_RETURN(Info); +} + std::string Demangle(const std::string& mangled_name) { INTEROP_TRACE(mangled_name); // Both itaniumDemangle and microsoftDemangle return a malloc'd buffer @@ -1015,6 +1091,112 @@ TCppScope_t GetScopeFromCompleteName(const std::string& name) { return INTEROP_RETURN(GetScope(name.substr(start, end), curr_scope)); } +// Sema::CurScope is private, but we need to reseat it briefly to drive +// Sema::LookupName at a synthesized point inside `Within`. The +// ALLOW_ACCESS/ACCESS pair from Sins.h gets us there without patching +// clang. +ALLOW_ACCESS(clang::Sema, CurScope, clang::Scope*); + +namespace { +// Mirror DC's enclosing namespace nesting as a chain of clang::Scope* +// rooted at S.TUScope, each Scope's entity set to the matching +// DeclContext. Sema::CppLookupName walks this chain via getParent() and +// reads using-directives off each entity's NamespaceDecl, so this is +// enough to make unqualified lookup honour `using namespace ...;` +// declared inside DC. +clang::Scope* BuildSyntheticScopeChain(clang::Sema& S, clang::DeclContext* DC) { + if (!DC || DC->isTranslationUnit()) + return S.TUScope; + auto* Parent = BuildSyntheticScopeChain(S, DC->getParent()); + auto* Mine = new clang::Scope(Parent, clang::Scope::DeclScope, S.Diags); + Mine->setEntity(DC); + return Mine; +} + +// RAII: build a synthetic scope chain for `Within`, install it as +// Sema::CurScope, restore + delete on destruction. CppLookupName is +// read-only on Scope/Sema state (only getParent/getEntity/ +// getLookupEntity/isDeclScope reads, plus a stack-local +// UnqualUsingDirectiveSet); freeing the synthetic scopes is the +// entire teardown — no ActOnPopScope needed because we never pushed +// any decl onto these scopes. +class SyntheticScopeChain { +public: + SyntheticScopeChain(clang::Sema& Sema, clang::DeclContext* Within) + : S(Sema), Innermost(BuildSyntheticScopeChain(Sema, Within)), + Saved(ACCESS(Sema, CurScope)) { + ACCESS(S, CurScope) = Innermost; + } + ~SyntheticScopeChain() { + ACCESS(S, CurScope) = Saved; + while (Innermost && Innermost != S.TUScope) { + auto* Next = Innermost->getParent(); + delete Innermost; + Innermost = Next; + } + } + SyntheticScopeChain(const SyntheticScopeChain&) = delete; + SyntheticScopeChain& operator=(const SyntheticScopeChain&) = delete; + + clang::Scope* get() const { return Innermost; } + +private: + clang::Sema& S; + clang::Scope* Innermost; + clang::Scope* Saved; +}; + +// Unqualified lookup of `Name` from a synthesized point inside `Within` +// ([basic.lookup.unqual]). Honours using-directives reachable from +// Within, which Sema::LookupQualifiedName does not. +// +// FIXME: longer-term we want two distinct routes — one wrapping +// LookupQualifiedName and one this — exposed as separate operations so +// callers can pick the C++ semantics they actually want. For now +// GetNamed gates this behind a qualified-lookup-miss + reachable +// using-directive check, so the common case stays on the cheap path. +clang::NamedDecl* LookupUnqualified(clang::Sema& S, + const clang::DeclarationName& Name, + clang::DeclContext* Within) { + SyntheticScopeChain Chain(S, Within); + // NotForRedeclaration: ForVisibleRedeclaration causes Sema::CppLookupName + // to return as soon as the innermost namespace doesn't directly contain + // the name (SemaLookup.cpp:1545), which prevents using-directives from + // an enclosing common-ancestor namespace from firing. We're doing + // ordinary name lookup, not collecting redeclarations. + clang::LookupResult R(S, Name, clang::SourceLocation(), + clang::Sema::LookupOrdinaryName, + RedeclarationKind::NotForRedeclaration); + R.suppressDiagnostics(); + S.LookupName(R, Chain.get()); + // Match LookupResult2Decl from CppInterOpInterpreter.h (clang-repl-only + // header, not included in CPPINTEROP_USE_CLING builds). Empty -> null; + // single -> found decl; multi -> (D*)-1 sentinel that GetNamed treats + // as "ambiguous, give up". + if (R.empty()) + return nullptr; + R.resolveKind(); + if (R.isSingleResult()) + return llvm::dyn_cast(R.getFoundDecl()); + return (clang::NamedDecl*)-1; +} + +// Cheap probe: does any namespace from `DC` up to TU carry at least +// one using-directive? Gates the synthetic-scope-chain build below so +// the common case (no using-directives anywhere on the path) doesn't +// pay the heap-allocation tax. +bool HasReachableUsingDirective(const clang::DeclContext* DC) { + for (; DC && !DC->isTranslationUnit(); DC = DC->getParent()) { + if (const auto* NS = llvm::dyn_cast(DC)) { + auto UDs = NS->using_directives(); + if (UDs.begin() != UDs.end()) + return true; + } + } + return false; +} +} // namespace + TCppScope_t GetNamed(const std::string& name, TCppScope_t parent /*= nullptr*/) { INTEROP_TRACE(name, parent); @@ -1029,10 +1211,26 @@ TCppScope_t GetNamed(const std::string& name, Within->getPrimaryContext()->buildLookup(); #endif compat::SynthesizingCodeRAII RAII(&getInterp()); + + // Fast path: qualified lookup. Cheap, no scope-chain allocation, and + // resolves every name not brought into `Within` via a using-directive. + // Lookup::Named falls back to LookupName(R, TUScope) when Within is + // null, so TU-level using-directives are already handled there. auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); - if (ND && ND != (clang::NamedDecl*)-1) { + if (ND && ND != (clang::NamedDecl*)-1) + return INTEROP_RETURN((TCppScope_t)(ND->getCanonicalDecl())); + + // Slow path: only when qualified lookup missed AND `Within` is a + // namespace whose enclosing chain carries at least one using-directive + // (the only reason qualified-vs-unqualified disagree at namespace + // scope per [basic.lookup.unqual] vs [basic.lookup.qual]). + if (!Within || !llvm::isa(Within) || + !HasReachableUsingDirective(Within)) + return INTEROP_RETURN(nullptr); + clang::DeclarationName DName = &getSema().Context.Idents.get(name); + ND = LookupUnqualified(getSema(), DName, Within); + if (ND && ND != (clang::NamedDecl*)-1) return INTEROP_RETURN((TCppScope_t)(ND->getCanonicalDecl())); - } return INTEROP_RETURN(nullptr); } @@ -1358,6 +1556,25 @@ TCppType_t GetFunctionReturnType(TCppFunction_t func) { return INTEROP_RETURN(nullptr); } +bool IsFunctionProtoType(TCppType_t typ) { + INTEROP_TRACE(typ); + QualType QT = QualType::getFromOpaquePtr(typ); + const auto* T = QT.getTypePtr(); + return llvm::isa_and_nonnull(T); +} + +void GetFnTypeSignature(TCppType_t fn_type, std::vector& sig) { + INTEROP_TRACE(fn_type, INTEROP_OUT(sig)); + QualType QT = QualType::getFromOpaquePtr(fn_type); + const auto* FPT = QT->getAs(); + if (!FPT) + return INTEROP_VOID_RETURN(); + sig.push_back(FPT->getReturnType().getAsOpaquePtr()); + for (size_t i = 0; i < FPT->getNumParams(); i++) + sig.push_back(FPT->getParamType(i).getAsOpaquePtr()); + return INTEROP_VOID_RETURN(); +} + TCppIndex_t GetFunctionNumArgs(TCppFunction_t func) { INTEROP_TRACE(func); auto* D = (clang::Decl*)func; @@ -1680,6 +1897,9 @@ bool IsDestructor(TCppConstFunction_t method) { bool IsStaticMethod(TCppConstFunction_t method) { INTEROP_TRACE(method); const auto* D = static_cast(method); + if (const auto* FTD = llvm::dyn_cast_or_null(D)) + D = FTD->getTemplatedDecl(); + if (auto* CXXMD = llvm::dyn_cast_or_null(D)) { return INTEROP_RETURN(CXXMD->isStatic()); } @@ -2382,13 +2602,13 @@ static std::optional GetTypeInternal(Decl* D) { return {}; } -TCppType_t GetType(const std::string& name) { - INTEROP_TRACE(name); +TCppType_t GetType(const std::string& name, TCppScope_t parent /*= nullptr*/) { + INTEROP_TRACE(name, parent); QualType builtin = findBuiltinType(name, getASTContext()); if (!builtin.isNull()) return INTEROP_RETURN(builtin.getAsOpaquePtr()); - return INTEROP_RETURN(GetTypeFromScope(GetNamed(name, /*parent=*/nullptr))); + return INTEROP_RETURN(GetTypeFromScope(GetNamed(name, parent))); } TCppType_t GetComplexType(TCppType_t type) { @@ -2795,14 +3015,16 @@ void make_narg_call(const FunctionDecl* FD, const std::string& return_type, // so check for the most common case: the trivial one, but not uniquely // available, while there is a move constructor. - // include utility header if not already included for std::move - DeclarationName DMove = &getASTContext().Idents.get("move"); - auto result = getSema().getStdNamespace()->lookup(DMove); - if (result.empty()) - Cpp::Declare("#include "); - - // move construction as needed for classes (note that this is implicit) - callbuf << "std::move(*(" << type_name.c_str() << "*)args[" << i << "])"; + // Move construction as needed for classes (note that this is + // implicit). Emit `std::move`'s expansion directly rather than the + // name: a cast to T&& is the definition of std::move for + // non-reference T ([utility.swap]), and this avoids pulling + // into the user's TU just to obtain the name. It also + // sidesteps the `getSema().getStdNamespace()->lookup(...)` call, + // which dereferences a nullptr when no `std::` has been parsed + // yet in this interpreter's TU. + callbuf << "static_cast<" << type_name.c_str() << "&&>(*(" + << type_name.c_str() << "*)args[" << i << "])"; } else { // pointer falls back to non-pointer case; the argument preserves // the "pointerness" (i.e. doesn't reference the value). @@ -2884,6 +3106,13 @@ void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N, indent(callbuf, --indent_level); if (CD->isDefaultConstructor()) callbuf << "}\n"; +#if __has_feature(memory_sanitizer) + // Outside the if/else so the array-new (nary > 1) and single-new + // branches are both covered. + indent(callbuf, indent_level); + callbuf << "__msan_unpoison(*(void**)ret, sizeof(" << class_name + << ") * (nary > 1 ? nary : 1));\n"; +#endif // // Output the whole new expression and return statement. @@ -2976,6 +3205,10 @@ void make_narg_call_with_return(compat::Interpreter& I, const FunctionDecl* FD, // End the placement new. // callbuf << ");\n"; +#if __has_feature(memory_sanitizer) + indent(callbuf, indent_level); + callbuf << "__msan_unpoison(ret, sizeof(" << type_name << "));\n"; +#endif indent(callbuf, indent_level); callbuf << "return;\n"; // @@ -3389,8 +3622,14 @@ int get_wrapper_code(compat::Interpreter& I, const FunctionDecl* FD, int indent_level = 0; std::ostringstream buf; buf << "#pragma clang diagnostic push\n" - "#pragma clang diagnostic ignored \"-Wformat-security\"\n" - "__attribute__((used)) " + "#pragma clang diagnostic ignored \"-Wformat-security\"\n"; +#if __has_feature(memory_sanitizer) + // Declared (not #include'd) so the wrapper compiles with no need + // for sanitizer headers in the JIT search path. + buf << "extern \"C\" void __msan_unpoison(const volatile void*, " + "unsigned long);\n"; +#endif + buf << "__attribute__((used)) " "__attribute__((annotate(\"__cling__ptrcheck(off)\")))\n" "extern \"C\" void "; buf << wrapper_name; @@ -3442,7 +3681,7 @@ JitCall::GenericCall make_wrapper(compat::Interpreter& I, return 0; // Log the wrapper source for the crash reproducer. - if (auto* TI = CppInterOp::Tracing::TraceInfo::TheTraceInfo) { + if (auto* TI = CppInterOp::Tracing::TheTraceInfo) { std::string FuncName; llvm::raw_string_ostream FNS(FuncName); FD->getNameForDiagnostic(FNS, FD->getASTContext().getPrintingPolicy(), @@ -3714,17 +3953,21 @@ 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) { +bool DefineAbsoluteSymbol(compat::Interpreter& I, const char* unmangled_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(); + // mangleAndIntern applies the target DataLayout's symbol prefix + // (leading `_` on Mach-O, no-op on ELF) so the registered key + // matches what the JIT computes when it lowers an IR symbol + // reference for lookup. Plain ES.intern() bypasses the prefix and + // silently breaks lookup on Mach-O. llvm::orc::SymbolMap InjectedSymbols{ - {ES.intern(linker_mangled_name), + {Jit.mangleAndIntern(unmangled_name), ExecutorSymbolDef(ExecutorAddr(address), JITSymbolFlags::Exported)}}; if (Error Err = DyLib.define(absoluteSymbols(InjectedSymbols))) { @@ -3816,6 +4059,33 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, #ifdef _WIN32 // FIXME : Workaround Sema::PushDeclContext assert on windows ClingArgv.push_back("-fno-delayed-template-parsing"); +#endif +#if __has_feature(memory_sanitizer) + // Match the host stdlib (msan setup is libc++ end to end; the + // in-process clang otherwise defaults to libstdc++ on Linux and + // JIT'd `std::__cxx11::*` won't resolve against the host's + // `std::__1::*`). User Args appended below can override. + ClingArgv.push_back("-stdlib=libc++"); + // -stdlib=libc++ alone misses /include/c++/v1: in-process + // clang derives Driver::Dir from /proc/self/exe (= host binary), + // not argv[0], so the force-include of `` SIGSEGVs in + // GenModule. Derive the include from -resource-dir, which IS the + // cell. Gating on memory_sanitizer (not _LIBCPP_VERSION) keeps + // this away from generic libc++ builds where the cell-derived + // path may not be the libc++ the host actually uses. + std::string LibcxxIncDir; + if (!ResourceDir.empty()) { + SmallString<256> P(ResourceDir); + sys::path::remove_filename(P); + sys::path::remove_filename(P); + sys::path::remove_filename(P); + sys::path::append(P, "include", "c++", "v1"); + if (sys::fs::is_directory(P)) { + LibcxxIncDir = P.str().str(); + ClingArgv.push_back("-cxx-isystem"); + ClingArgv.push_back(LibcxxIncDir.c_str()); + } + } #endif ClingArgv.insert(ClingArgv.end(), Args.begin(), Args.end()); // To keep the Interpreter creation interface between cling and clang-repl @@ -4046,7 +4316,6 @@ void GetIncludePaths(std::vector& IncludePaths, bool withSystem, } namespace { - class clangSilent { public: clangSilent(clang::DiagnosticsEngine& diag) : fDiagEngine(diag) { @@ -4063,17 +4332,22 @@ class clangSilent { } // namespace int Declare(compat::Interpreter& I, const char* code, bool silent) { + // Trap diagnostics on both paths: I.declare's rc is 0 even when + // Parse recovered from emitted errors, so callers need the trap to + // distinguish "parsed cleanly" from "parsed with errors". + clang::DiagnosticsEngine& Diag = I.getSema().getDiagnostics(); + clang::DiagnosticErrorTrap Trap(Diag); if (silent) { - clang::DiagnosticsEngine& Diag = I.getSema().getDiagnostics(); clangSilent diagSuppr(Diag); - clang::DiagnosticErrorTrap Trap(Diag); auto result = I.declare(code); if (Trap.hasErrorOccurred()) return 1; return result; } - - return I.declare(code); + auto result = I.declare(code); + if (Trap.hasErrorOccurred()) + return 1; + return result; } int Declare(const char* code, bool silent) { @@ -4086,17 +4360,19 @@ int Process(const char* code) { return INTEROP_RETURN(getInterp().process(code)); } -intptr_t Evaluate(const char* code, bool* HadError /*=nullptr*/) { - INTEROP_TRACE(code, HadError); +intptr_t Evaluate(const char* code, bool* IsValueInvalid /*=nullptr*/) { + INTEROP_TRACE(code, INTEROP_OUT(IsValueInvalid)); compat::Value V; - if (HadError) - *HadError = false; + if (IsValueInvalid) + *IsValueInvalid = false; auto res = getInterp().evaluate(code, V); - if (res != 0) { // 0 is success - if (HadError) - *HadError = true; + CPPINTEROP_MSAN_UNPOISON_VALUE(V); + // 0 is success; an unset V on success means convertTo would assert. + if (res != 0 || !V.hasValue()) { + if (IsValueInvalid) + *IsValueInvalid = true; // FIXME: Make this return llvm::Expected return INTEROP_RETURN(~0UL); } @@ -4333,6 +4609,29 @@ TCppScope_t InstantiateTemplate(TCppScope_t tmpl, getInterp(), tmpl, template_args, template_args_size, instantiate_body)); } +TCppScope_t +InstantiateTemplate(TCppScope_t tmpl, + const std::vector& template_args, + bool instantiate_body) { + INTEROP_TRACE(tmpl, template_args, instantiate_body); + // Forward to the static helper directly (not the deprecated public + // overload) to avoid a nested INTEROP_TRACE. + return INTEROP_RETURN( + InstantiateTemplate(getInterp(), tmpl, template_args.data(), + template_args.size(), instantiate_body)); +} + +void GetClassTemplateArgs(TCppScope_t templ_instance, + std::vector& args) { + INTEROP_TRACE(templ_instance, INTEROP_OUT(args)); + auto* CTSD = static_cast(templ_instance); + for (const auto& TA : CTSD->getTemplateArgs().asArray()) { + // FIXME: Support cases with m_IntegralValue. + args.push_back({TA.getAsType().getAsOpaquePtr()}); + } + return INTEROP_VOID_RETURN(); +} + void GetClassTemplateInstantiationArgs(TCppScope_t templ_instance, std::vector& args) { INTEROP_TRACE(templ_instance, INTEROP_OUT(args)); @@ -4832,16 +5131,4 @@ int Undo(unsigned N) { #endif } -#ifndef _WIN32 -pid_t GetExecutorPID() { - INTEROP_TRACE(); -#ifdef LLVM_BUILT_WITH_OOP_JIT - auto& I = getInterp(); - return INTEROP_RETURN(I.getOutOfProcessExecutorPID()); -#endif - return INTEROP_RETURN(getpid()); -} - -#endif - } // namespace CppImpl diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.td b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.td new file mode 100644 index 0000000000000..d3958325eae6e --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.td @@ -0,0 +1,1562 @@ +//===- CppInterOp.td - CppInterOp API definitions --------------*- td -*-===// +// +// Part of the compiler-research project, under the Apache License v2.0 with +// LLVM Exceptions. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// TableGen definitions for the CppInterOp public API. Each def corresponds +// to one function in include/CppInterOp/CppInterOp.h and drives generation +// of the dispatch table, inline wrappers, and (future) C API wrappers. +// +//===----------------------------------------------------------------------===// + +include "CppInterOpAPI.td" + +// --- Interpreter management --- + +def CreateInterpreter : CppInterOpAPI { + let Doc = [{Creates an owned instance of the interpreter we need for the various interop +services and pushes it onto a stack. +\param[in] Args - the list of arguments for interpreter constructor. +\param[in] CPPINTEROP_EXTRA_INTERPRETER_ARGS - an env variable, if defined, + adds additional arguments to the interpreter. +\returns nullptr on failure.}]; + + let ReturnType = "TInterp_t"; + let Args = [ + Arg<"const std::vector&", "Args", "{}">, + Arg<"const std::vector&", "GpuArgs", "{}"> + ]; +} + +def GetInterpreter : CppInterOpAPI { + let Doc = [{Checks which Interpreter backend was CppInterOp library built with (Cling, +Clang-REPL, etcetera). In practice, the selected interpreter should not +matter, since the library will function in the same way. +\returns the current interpreter instance, if any.}]; + + let ReturnType = "TInterp_t"; +} + +def DeleteInterpreter : CppInterOpAPI { + let Doc = [{Deletes an instance of an interpreter. +\param[in] I - the interpreter to be deleted, if nullptr, deletes the last. +\returns false on failure or if \c I is not tracked in the stack.}]; + + let ReturnType = "bool"; + let Args = [Arg<"TInterp_t", "I", "nullptr">]; +} + +def Process : CppInterOpAPI { + let Doc = [{Declares and executes a code snippet in \c code. +\returns 0 on success}]; + + let ReturnType = "int"; + let Args = [Arg<"const char*", "code">]; +} + +def Declare : CppInterOpAPI { + let Doc = [{Only Declares a code snippet in \c code and does not execute it. +\returns 0 on success}]; + + let ReturnType = "int"; + let Args = [ + Arg<"const char*", "code">, + Arg<"bool", "silent", "false"> + ]; +} + +def Evaluate : CppInterOpAPI { + let Doc = [{Declares, executes and returns the execution result as a intptr_t. +\param[in] code - the snippet to declare and execute. +\param[out] IsValueInvalid - if non-null, set true when the result is + unusable (parse error or no-Value-after-success). +\returns the expression results as a intptr_t.}]; + + let ReturnType = "intptr_t"; + let Args = [ + Arg<"const char*", "code">, + OutArg<"bool*", "IsValueInvalid", "nullptr"> + ]; +} + +// --- Scope / type queries --- + +def GetScope : CppInterOpAPI { + let Doc = [{Gets the namespace or class (by stripping typedefs) for the name +passed as a parameter, and if the parent is not passed, +then global scope will be assumed.}]; + + let ReturnType = "TCppScope_t"; + let Args = [ + Arg<"const std::string&", "name">, + Arg<"TCppScope_t", "parent", "nullptr"> + ]; +} + +def GetNamed : CppInterOpAPI { + let Doc = [{This function performs a lookup within the specified parent, +a specific named entity (functions, enums, etcetera).}]; + + let ReturnType = "TCppScope_t"; + let Args = [ + Arg<"const std::string&", "name">, + Arg<"TCppScope_t", "parent", "nullptr"> + ]; +} + +def GetGlobalScope : CppInterOpAPI { + let Doc = "Gets the global scope of the whole C++ instance."; + + let ReturnType = "TCppScope_t"; +} + +def IsNamespace : CppInterOpAPI { + let Doc = "Checks if the scope is a namespace or not."; + + let ReturnType = "bool"; + let Args = [Arg<"TCppScope_t", "scope">]; +} + +def IsClass : CppInterOpAPI { + let Doc = "Checks if the scope is a class or not."; + + let ReturnType = "bool"; + let Args = [Arg<"TCppScope_t", "scope">]; +} + +def GetType : CppInterOpAPI { + let Doc = [{Used to either get the built-in type of the provided string, or +use the name to lookup the actual type. When `parent` is non-null, scoped +lookup honours using-directives and type aliases declared inside it; passing +`nullptr` (the default) limits lookup to TU scope.}]; + + let ReturnType = "TCppType_t"; + let Args = [Arg<"const std::string&", "name">, + Arg<"TCppScope_t", "parent", "nullptr">]; +} + +def GetTypeFromScope : CppInterOpAPI { + let Doc = [{This will convert a class into its type, so for example, you can +use it to declare variables in it.}]; + + let ReturnType = "TCppType_t"; + let Args = [Arg<"TCppScope_t", "klass">]; +} + +def GetScopeFromType : CppInterOpAPI { + let Doc = "Gets the scope of the type that is passed as a parameter."; + + let ReturnType = "TCppScope_t"; + let Args = [Arg<"TCppType_t", "type">]; +} + +def SizeOf : CppInterOpAPI { + let ReturnType = "size_t"; + let Args = [Arg<"TCppScope_t", "scope">]; +} + +// --- Functions with overloads --- + +def GetFunctionAddress_str : CppInterOpAPI { + let Doc = "\\returns the address of the function given its potentially mangled name."; + let CppName = "GetFunctionAddress"; + let ReturnType = "TCppFuncAddr_t"; + let Args = [Arg<"const char*", "mangled_name">]; +} + +def GetFunctionAddress_fn : CppInterOpAPI { + let Doc = "\\returns the address of the function given its function declaration."; + let CppName = "GetFunctionAddress"; + let ReturnType = "TCppFuncAddr_t"; + let Args = [Arg<"TCppFunction_t", "method">]; +} + +// --- Construction / destruction --- + +def Construct : CppInterOpAPI { + let Doc = [{Creates one or more objects of class \c scope by calling its default +constructor. +\param[in] scope Class to construct, or handle to Constructor +\param[in] arena If set, this API uses placement new to construct at this +address. +\param[in] is used to indicate the number of objects to construct. +\returns a pointer to the constructed object, which is arena if placement +new is used.}]; + + let ReturnType = "TCppObject_t"; + let Args = [ + Arg<"TCppScope_t", "scope">, + Arg<"void*", "arena", "nullptr">, + Arg<"TCppIndex_t", "count", "1UL"> + ]; +} + +def Destruct : CppInterOpAPI { + let Doc = [{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.}]; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppObject_t", "This">, + Arg<"TCppConstScope_t", "scope">, + Arg<"bool", "withFree", "true">, + Arg<"TCppIndex_t", "count", "0UL"> + ]; +} + +// --- Auto-generated remaining dispatch entries --- + +def GetResourceDir : CppInterOpAPI { + let Doc = "Returns the resource-dir path (for headers)."; + + let ReturnType = "const char*"; +} + +def AddIncludePath : CppInterOpAPI { + let Doc = [{Secondary search path for headers, if not found using the +GetResourceDir() function.}]; + + let ReturnType = "void"; + let Args = [ + Arg<"const char*", "dir"> + ]; +} + +def LoadLibrary : CppInterOpAPI { + let Doc = [{Finds \c lib_stem considering the list of search paths and loads it by +calling dlopen. +\returns true on success.}]; + + let ReturnType = "bool"; + let Args = [ + Arg<"const char*", "lib_stem">, + Arg<"bool", "lookup", "true"> + ]; +} + +def ObjToString : CppInterOpAPI { + let Doc = "Tries to load provided objects in a string format (prettyprint)."; + + let ReturnType = "std::string"; + let Args = [ + Arg<"const char*", "type">, + Arg<"void*", "obj"> + ]; +} + +def GetValueKind : CppInterOpAPI { + let Doc = "Get if lvalue or rvalue reference"; + + let ReturnType = "ValueKind"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def GetNonReferenceType : CppInterOpAPI { + let Doc = "Get the type that the reference refers to"; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def IsEnumType : CppInterOpAPI { + let Doc = "Checks if the passed value is an enum type or not."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def GetLanguage : CppInterOpAPI { + let Doc = "Returns the programming language of the interpreter."; + + let ReturnType = "InterpreterLanguage"; + let Args = [ + Arg<"TInterp_t", "I", "nullptr"> + ]; +} + +def GetLanguageStandard : CppInterOpAPI { + let Doc = "Returns the language standard of the interpreter."; + + let ReturnType = "InterpreterLanguageStandard"; + let Args = [ + Arg<"TInterp_t", "I", "nullptr"> + ]; +} + +def GetReferencedType : CppInterOpAPI { + let Doc = "Get lvalue referenced type, or rvalue if rvalue is true"; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppType_t", "type">, + Arg<"bool", "rvalue", "false"> + ]; +} + +def IsPointerType : CppInterOpAPI { + let Doc = "Checks if type is a pointer"; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def GetPointeeType : CppInterOpAPI { + let Doc = "Get the underlying pointee type"; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def GetPointerType : CppInterOpAPI { + let Doc = "Get the pointer to type"; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def IsReferenceType : CppInterOpAPI { + let Doc = "Checks if type is a reference"; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def GetTypeAsString : CppInterOpAPI { + let Doc = "Gets the Type (passed as a parameter) as a String value."; + + let ReturnType = "std::string"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def GetCanonicalType : CppInterOpAPI { + let Doc = [{Gets the Canonical Type string from the std string. A canonical type +is the type with any typedef names, syntactic sugars or modifiers stripped +out of it.}]; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def HasTypeQualifier : CppInterOpAPI { + let Doc = [{Checks if the passed type has qual Qualifiers +qual can be ORed value of enum QualKind}]; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "type">, + Arg<"QualKind", "qual"> + ]; +} + +def RemoveTypeQualifier : CppInterOpAPI { + let Doc = [{Returns type with the qual Qualifiers removed +qual can be ORed value of enum QualKind}]; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppType_t", "type">, + Arg<"QualKind", "qual"> + ]; +} + +def GetUnderlyingType : CppInterOpAPI { + let Doc = "Gets the pure, Underlying Type (as opposed to the Using Type)."; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def IsRecordType : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a Record (struct)."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def GetVariableType : CppInterOpAPI { + let Doc = "Gets the type of the variable that is passed as a parameter."; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppScope_t", "var"> + ]; +} + +def GetComplexType : CppInterOpAPI { + let Doc = "\\returns the complex of the provided type."; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppType_t", "element_type"> + ]; +} + +def GetUnderlyingScope : CppInterOpAPI { + let Doc = [{Strips the typedef and returns the underlying class, and if the +underlying decl is not a class it returns the input unchanged.}]; + + let ReturnType = "TCppScope_t"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def InstantiateTemplate : CppInterOpAPI { + let Doc = [{Builds a template instantiation for a given templated declaration. +Offers a single interface for instantiation of class, function and +variable templates + +\param[in] tmpl - Uninstantiated template class/function +\param[in] template_args - Pointer to vector of template arguments stored + in the \c TemplateArgInfo struct +\param[in] template_args_size - Size of the vector of template arguments + passed as \c template_args +\param[in] instantiate_body - also instantiate/define the body + +\returns Instantiated templated class/function/variable pointer}]; + + let ReturnType = "TCppScope_t"; + let Args = [ + Arg<"TCppScope_t", "tmpl">, + Arg<"const TemplateArgInfo*", "template_args">, + Arg<"size_t", "template_args_size">, + Arg<"bool", "instantiate_body", "false"> + ]; + let Deprecated = "use the const std::vector& overload"; +} + +def InstantiateTemplateVec : CppInterOpAPI { + let Doc = [{Convenience overload of \c InstantiateTemplate that takes the +template argument list by const reference. Equivalent to calling the +pointer+size overload with \c template_args.data() and +\c template_args.size().}]; + + let CppName = "InstantiateTemplate"; + let ReturnType = "TCppScope_t"; + let Args = [ + Arg<"TCppScope_t", "tmpl">, + Arg<"const std::vector&", "template_args">, + Arg<"bool", "instantiate_body", "false"> + ]; +} + +def GetParentScope : CppInterOpAPI { + let Doc = "Gets the parent of the scope that is passed as a parameter."; + + let ReturnType = "TCppScope_t"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def IsTemplate : CppInterOpAPI { + let Doc = "Checks if it is a templated class."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "handle"> + ]; +} + +def IsTypedefed : CppInterOpAPI { + let Doc = "Checks if \\c handle introduces a typedef name via \\c typedef or \\c using."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "handle"> + ]; +} + +def IsClassPolymorphic : CppInterOpAPI { + let Doc = [{Checks if the klass polymorphic. +which means that the class contains or inherits a virtual function}]; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "klass"> + ]; +} + +def Demangle : CppInterOpAPI { + let Doc = "\\returns the demangled representation of the given mangled_name"; + + let ReturnType = "std::string"; + let Args = [ + Arg<"const std::string&", "mangled_name"> + ]; +} + +def GetSizeOfType : CppInterOpAPI { + let Doc = "Gets the size of the \"type\" that is passed in to this function."; + + let ReturnType = "size_t"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def IsBuiltin : CppInterOpAPI { + let Doc = "Checks if it is a \"built-in\" or a \"complex\" type."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppConstType_t", "type"> + ]; +} + +def IsComplete : CppInterOpAPI { + let Doc = [{Checks if the class definition is present, or not. Performs a +template instantiation if necessary.}]; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def Allocate : CppInterOpAPI { + let Doc = [{Allocates memory required by an object of a given class +\param[in] scope Given class for which to allocate memory for +\param[in] count is used to indicate the number of objects to allocate for.}]; + + let ReturnType = "TCppObject_t"; + let Args = [ + Arg<"TCppScope_t", "scope">, + Arg<"TCppIndex_t", "count", "1UL"> + ]; +} + +def Deallocate : CppInterOpAPI { + let Doc = [{Deallocates memory for a given class. +\param[in] scope Class to indicate size of memory to deallocate +\param[in] count is used to indicate the number of objects to dallocate for}]; + + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "scope">, + Arg<"TCppObject_t", "address">, + Arg<"TCppIndex_t", "count", "1UL"> + ]; +} + +def IsAbstract : CppInterOpAPI { + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "klass"> + ]; +} + +def IsEnumScope : CppInterOpAPI { + let Doc = "Checks if it is an enum name (EnumDecl represents an enum name)."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "handle"> + ]; +} + +def IsEnumConstant : CppInterOpAPI { + let Doc = [{Checks if it is an enum's value (EnumConstantDecl represents +each enum constant that is defined).}]; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "handle"> + ]; +} + +def IsAggregate : CppInterOpAPI { + let Doc = [{Checks if the given class represents an aggregate type). +\returns true if \c scope is an array or a C++ tag (as per C++ +[dcl.init.aggr]) \returns true if the scope supports aggregate +initialization.}]; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def IsVariable : CppInterOpAPI { + let Doc = "Checks if the passed value is a variable."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def GetAllCppNames : CppInterOpAPI { + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "scope">, + OutArg<"std::set&", "names"> + ]; +} + +def GetUsingNamespaces : CppInterOpAPI { + let Doc = "Gets the list of namespaces utilized in the supplied scope."; + + let ReturnType = "std::vector"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def GetCompleteName : CppInterOpAPI { + let Doc = [{This is similar to GetName() function, but besides +the name, it also gets the template arguments.}]; + + let ReturnType = "std::string"; + let Args = [ + Arg<"TCppScope_t", "klass"> + ]; +} + +def GetDestructor : CppInterOpAPI { + let Doc = "\\returns the class destructor, if any."; + + let ReturnType = "TCppFunction_t"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def IsVirtualMethod : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a 'Virtual' method."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppFunction_t", "method"> + ]; +} + +def GetNumBases : CppInterOpAPI { + let Doc = [{Gets the number of Base Classes for the Derived Class that +is passed as a parameter.}]; + + let ReturnType = "TCppIndex_t"; + let Args = [ + Arg<"TCppScope_t", "klass"> + ]; +} + +def GetName : CppInterOpAPI { + let Doc = [{Gets the name of any named decl (a class, +namespace, variable, or a function).}]; + + let ReturnType = "std::string"; + let Args = [ + Arg<"TCppScope_t", "klass"> + ]; +} + +def GetBaseClass : CppInterOpAPI { + let Doc = [{Gets a specific Base Class using its index. Typically GetNumBases() +is used to get the number of Base Classes, and then that number +can be used to iterate through the index value to get each specific +base class.}]; + + let ReturnType = "TCppScope_t"; + let Args = [ + Arg<"TCppScope_t", "klass">, + Arg<"TCppIndex_t", "ibase"> + ]; +} + +def IsSubclass : CppInterOpAPI { + let Doc = [{Checks if the supplied Derived Class is a sub-class of the +provided Base Class.}]; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "derived">, + Arg<"TCppScope_t", "base"> + ]; +} + +def GetOperator : CppInterOpAPI { + let Doc = "\\returns list of operator overloads"; + + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "scope">, + Arg<"Operator", "op">, + OutArg<"std::vector&", "operators">, + Arg<"OperatorArity", "kind", "kBoth"> + ]; +} + +def GetBaseClassOffset : CppInterOpAPI { + let Doc = [{Each base has its own offset in a Derived Class. This offset can be +used to get to the Base Class fields.}]; + + let ReturnType = "int64_t"; + let Args = [ + Arg<"TCppScope_t", "derived">, + Arg<"TCppScope_t", "base"> + ]; +} + +def GetClassMethods : CppInterOpAPI { + let Doc = [{Sets a list of all the Methods that are in the Class that is +supplied as a parameter. +\param[in] klass - Pointer to the scope/class under which the methods have + to be retrieved +\param[out] methods - Vector of methods in the class}]; + + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "klass">, + OutArg<"std::vector&", "methods"> + ]; +} + +def IsFunctionProtoType : CppInterOpAPI { + let Doc = "Check if given type is FunctionProtoType."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "typ"> + ]; +} + +def GetFnTypeSignature : CppInterOpAPI { + let Doc = [{Pushed the signature type of the given function type, + where the first item is the return type.}]; + + let ReturnType = "void"; + let Args = [ + Arg<"TCppType_t", "fn_type">, + OutArg<"std::vector&", "sig"> + ]; +} + +def GetFunctionNumArgs : CppInterOpAPI { + let Doc = "Gets the number of Arguments for the provided function."; + + let ReturnType = "TCppIndex_t"; + let Args = [ + Arg<"TCppFunction_t", "func"> + ]; +} + +def GetFunctionArgName : CppInterOpAPI { + let Doc = "\\returns the argument name of function as string."; + + let ReturnType = "std::string"; + let Args = [ + Arg<"TCppFunction_t", "func">, + Arg<"TCppIndex_t", "param_index"> + ]; +} + +def GetFunctionArgType : CppInterOpAPI { + let Doc = [{For each Argument of a function, you can get the Argument Type +by providing the Argument Index, based on the number of arguments +from the GetFunctionNumArgs() function.}]; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppFunction_t", "func">, + Arg<"TCppIndex_t", "iarg"> + ]; +} + +def IsConstMethod : CppInterOpAPI { + let Doc = "Checks if a function declared is of const type or not."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppFunction_t", "method"> + ]; +} + +def IsTemplatedFunction : CppInterOpAPI { + let ReturnType = "bool"; + let Args = [ + Arg<"TCppFunction_t", "func"> + ]; +} + +def IsStaticMethod : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a 'Static' method."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppConstFunction_t", "method"> + ]; +} + +def IsFunctionDeleted : CppInterOpAPI { + let Doc = "\\returns if a function was marked as \\c =delete."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppConstFunction_t", "function"> + ]; +} + +def IsPublicMethod : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a 'Public' method."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppFunction_t", "method"> + ]; +} + +def IsProtectedMethod : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a 'Protected' method."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppFunction_t", "method"> + ]; +} + +def IsPrivateMethod : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a 'Private' method."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppFunction_t", "method"> + ]; +} + +def IsConstructor : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a Constructor."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppConstFunction_t", "method"> + ]; +} + +def IsDestructor : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a Destructor."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppConstFunction_t", "method"> + ]; +} + +def GetDatamembers : CppInterOpAPI { + let Doc = "Gets all the Fields/Data Members of a Class"; + + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "scope">, + OutArg<"std::vector&", "datamembers"> + ]; +} + +def GetStaticDatamembers : CppInterOpAPI { + let Doc = [{Gets all the Static Fields/Data Members of a Class +\param[in] scope - class +\param[out] funcs - vector of static data members}]; + + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "scope">, + OutArg<"std::vector&", "datamembers"> + ]; +} + +def LookupDatamember : CppInterOpAPI { + let Doc = "This is a Lookup function to be used specifically for data members."; + + let ReturnType = "TCppScope_t"; + let Args = [ + Arg<"const std::string&", "name">, + Arg<"TCppScope_t", "parent"> + ]; +} + +def IsLambdaClass : CppInterOpAPI { + let Doc = "Check if the given type is a lamda class"; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def GetQualifiedName : CppInterOpAPI { + let Doc = [{Gets the "qualified" name (including the namespace) of any +named decl (a class, namespace, variable, or a function).}]; + + let ReturnType = "std::string"; + let Args = [ + Arg<"TCppScope_t", "klass"> + ]; +} + +def GetVariableOffset : CppInterOpAPI { + let Doc = [{Gets the address of the variable, you can use it to get the +value stored in the variable.}]; + + let ReturnType = "intptr_t"; + let Args = [ + Arg<"TCppScope_t", "var">, + Arg<"TCppScope_t", "parent", "nullptr"> + ]; +} + +def IsPublicVariable : CppInterOpAPI { + let Doc = "Checks if the provided variable is a 'Public' variable."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "var"> + ]; +} + +def IsProtectedVariable : CppInterOpAPI { + let Doc = "Checks if the provided variable is a 'Protected' variable."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "var"> + ]; +} + +def IsPrivateVariable : CppInterOpAPI { + let Doc = "Checks if the provided variable is a 'Private' variable."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "var"> + ]; +} + +def IsStaticVariable : CppInterOpAPI { + let Doc = "Checks if the provided variable is a 'Static' variable."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "var"> + ]; +} + +def IsConstVariable : CppInterOpAPI { + let Doc = "Checks if the provided variable is a 'Constant' variable."; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppScope_t", "var"> + ]; +} + +def GetDimensions : CppInterOpAPI { + let Doc = "Gets the size/dimensions of a multi-dimension array."; + // FIXME: Rework to use a C-compatible array type. + let NoCWrapper = true; + let ReturnType = "std::vector"; + let Args = [ + Arg<"TCppType_t", "type"> + ]; +} + +def GetEnumConstants : CppInterOpAPI { + let Doc = "Gets a list of all the enum constants for an enum."; + + let ReturnType = "std::vector"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def GetEnumConstantType : CppInterOpAPI { + let Doc = "Gets the enum name when an enum constant is passed."; + + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def GetEnumConstantValue : CppInterOpAPI { + let Doc = [{Gets the index value (0,1,2, etcetera) of the enum constant +that was passed into this function.}]; + + let ReturnType = "TCppIndex_t"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def DumpScope : CppInterOpAPI { + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "scope"> + ]; +} + +def AddSearchPath : CppInterOpAPI { + let Doc = "Adds a Search Path for the Interpreter to get the libraries."; + + let ReturnType = "void"; + let Args = [ + Arg<"const char*", "dir">, + Arg<"bool", "isUser", "true">, + Arg<"bool", "prepend", "false"> + ]; +} + +def IsDebugOutputEnabled : CppInterOpAPI { + let Doc = "\\returns true if the debugging printouts on stderr are enabled."; + + let ReturnType = "bool"; +} + +def EnableDebugOutput : CppInterOpAPI { + let Doc = [{Enables or disables the debugging printouts on stderr. +Debugging output can be enabled also by the environment variable +CPPINTEROP_EXTRA_INTERPRETER_ARGS. For example, +CPPINTEROP_EXTRA_INTERPRETER_ARGS="-mllvm -debug-only=jitcall" to produce +only debug output for jitcall events.}]; + + let ReturnType = "void"; + let Args = [ + Arg<"bool", "value", "true"> + ]; +} + +def GetDoxygenComment : CppInterOpAPI { + let Doc = [{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.}]; + + let ReturnType = "std::string"; + let Args = [ + Arg<"TCppScope_t", "scope">, + Arg<"bool", "strip_comment_markers", "true"> + ]; +} + +def IsExplicit : CppInterOpAPI { + let Doc = "Checks if the provided constructor or conversion operator is explicit"; + + let ReturnType = "bool"; + let Args = [ + Arg<"TCppConstFunction_t", "method"> + ]; +} + + +// --- Functions not previously in the dispatch table --- + +def ActivateInterpreter : CppInterOpAPI { + let Doc = [{Activates an instance of an interpreter to handle subsequent API +requests.}]; + let ReturnType = "bool"; + let Args = [Arg<"TInterp_t", "I">]; +} + +def AddTypeQualifier : CppInterOpAPI { + let Doc = [{Returns type with the qual Qualifiers added. +qual can be ORed value of enum QualKind.}]; + let ReturnType = "TCppType_t"; + let Args = [ + Arg<"TCppType_t", "type">, + Arg<"QualKind", "qual"> + ]; +} + +def BeginStdStreamCapture : CppInterOpAPI { + let Doc = [{Begins recording the given standard stream. +\param[in] fd_kind The stream to be captured.}]; + let ReturnType = "void"; + let Args = [Arg<"CaptureStreamKind", "fd_kind">]; +} + +def BestOverloadFunctionMatch : CppInterOpAPI { + let Doc = [{Finds best overload match based on explicit template parameters +(if any) and argument types.}]; + let ReturnType = "TCppFunction_t"; + let Args = [ + Arg<"const std::vector&", "candidates">, + Arg<"const std::vector&", "explicit_types">, + Arg<"const std::vector&", "arg_types"> + ]; +} + +def CodeComplete : CppInterOpAPI { + let Doc = [{Append all code completion suggestions to Results. +\param[out] Results CC suggestions for code fragment (appended). +\param[in] code Code fragment to complete. +\param[in] complete_line Position (line) in code for suggestion. +\param[in] complete_column Position (column) in code for suggestion.}]; + let ReturnType = "void"; + let Args = [ + OutArg<"std::vector&", "Results">, + Arg<"const char*", "code">, + Arg<"unsigned", "complete_line", "1U">, + Arg<"unsigned", "complete_column", "1U"> + ]; +} + +def DetectResourceDir : CppInterOpAPI { + let Doc = [{Uses the underlying clang compiler to detect the resource directory. +In essence it asks clang to print its resource-dir and returns the path. +\param[in] ClangBinaryName The name or full path of the compiler to ask.}]; + let ReturnType = "std::string"; + let Args = [Arg<"const char*", "ClangBinaryName", "\"clang\"">]; +} + +def DetectSystemCompilerIncludePaths : CppInterOpAPI { + let Doc = [{Asks the system compiler for its default include paths. +\param[out] Paths The list of include paths returned. +\param[in] CompilerName The name or full path of the compiler binary.}]; + let ReturnType = "void"; + let Args = [ + OutArg<"std::vector&", "Paths">, + Arg<"const char*", "CompilerName", "\"c++\""> + ]; +} + +def EndStdStreamCapture : CppInterOpAPI { + let Doc = "Ends recording the standard stream and returns the result as a string."; + let ReturnType = "std::string"; +} + +def ExistsFunctionTemplate : CppInterOpAPI { + let Doc = [{This function performs a lookup to check if there is a +templated function of that type within the parent scope.}]; + let ReturnType = "bool"; + let Args = [ + Arg<"const std::string&", "name">, + Arg<"TCppScope_t", "parent", "nullptr"> + ]; +} + +def GetClassTemplateArgs : CppInterOpAPI { + let Doc = [{Sets the class template arguments of \c templ_instance. +\param[in] templ_instance Pointer to the template instance. +\param[out] args Vector of instantiation arguments.}]; + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "templ_instance">, + OutArg<"std::vector&", "args"> + ]; +} + +def GetClassTemplateInstantiationArgs : CppInterOpAPI { + let Doc = [{Sets the class template instantiation arguments of \c templ_instance. +\param[in] templ_instance Pointer to the template instance. +\param[out] args Vector of instantiation arguments.}]; + // FIXME: Needs a dedicated array type — TemplateArgInfo is not pointer-sized. + let NoCWrapper = true; + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "templ_instance">, + OutArg<"std::vector&", "args"> + ]; +} + +def GetClassTemplatedMethods : CppInterOpAPI { + let Doc = [{Sets a list of all the Templated Methods that are in the Class +that is supplied as a parameter.}]; + // Hand-written C wrapper: bool return dropped, caller checks arr.size. + let NoCWrapper = true; + let ReturnType = "bool"; + let Args = [ + Arg<"const std::string&", "name">, + Arg<"TCppScope_t", "parent">, + OutArg<"std::vector&", "funcs"> + ]; +} + +def GetDefaultConstructor : CppInterOpAPI { + let Doc = "\\returns the default constructor of a class, if any."; + let ReturnType = "TCppFunction_t"; + let Args = [Arg<"TCppScope_t", "scope">]; +} + +def GetEnumConstantDatamembers : CppInterOpAPI { + let Doc = [{Gets all enum constant data members of a class. +\param[in] scope Class to query. +\param[out] datamembers Vector of enum constant members. +\param[in] include_enum_class Whether to include enum class members.}]; + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "scope">, + OutArg<"std::vector&", "datamembers">, + Arg<"bool", "include_enum_class", "true"> + ]; +} + +def GetEnums : CppInterOpAPI { + let Doc = "Extracts enum declarations from a specified scope and stores them in a vector."; + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "scope">, + OutArg<"std::vector&", "Result"> + ]; +} + +def GetFunctionArgDefault : CppInterOpAPI { + let Doc = "\\returns the default argument value as string."; + let ReturnType = "std::string"; + let Args = [ + Arg<"TCppFunction_t", "func">, + Arg<"TCppIndex_t", "param_index"> + ]; +} + +def GetFunctionRequiredArgs : CppInterOpAPI { + let Doc = "Gets the number of Required Arguments for the provided function."; + let ReturnType = "TCppIndex_t"; + let Args = [Arg<"TCppConstFunction_t", "func">]; +} + +def GetFunctionReturnType : CppInterOpAPI { + let Doc = "Gets the return type of the provided function."; + let ReturnType = "TCppType_t"; + let Args = [Arg<"TCppFunction_t", "func">]; +} + +def GetFunctionSignature : CppInterOpAPI { + let Doc = [{\\returns a stringified version of a given function signature in the +form: void N::f(int i, double d, long l = 0, char ch = 'a').}]; + let ReturnType = "std::string"; + let Args = [Arg<"TCppFunction_t", "func">]; +} + +def GetFunctionTemplatedDecls : CppInterOpAPI { + let Doc = [{Template function pointer list to add proxies for +un-instantiated/non-overloaded templated methods.}]; + let ReturnType = "void"; + let Args = [ + Arg<"TCppScope_t", "klass">, + OutArg<"std::vector&", "methods"> + ]; +} + +def GetFunctionsUsingName : CppInterOpAPI { + let Doc = [{Looks up all the functions that have the name that is +passed as a parameter in this function.}]; + let ReturnType = "std::vector"; + let Args = [ + Arg<"TCppScope_t", "scope">, + Arg<"const std::string&", "name"> + ]; +} + +def GetIncludePaths : CppInterOpAPI { + let Doc = [{\\param[out] IncludePaths The list of include paths. +\\param[in] withSystem Include system paths. +\\param[in] withFlags Include flag-based paths.}]; + let ReturnType = "void"; + let Args = [ + OutArg<"std::vector&", "IncludePaths">, + Arg<"bool", "withSystem", "false">, + Arg<"bool", "withFlags", "false"> + ]; +} + +def GetIntegerTypeFromEnumScope : CppInterOpAPI { + let Doc = [{For the given "class", get the integer type that the enum +represents, so that you can store it properly in a box.}]; + let ReturnType = "TCppType_t"; + let Args = [Arg<"TCppScope_t", "handle">]; +} + +def GetIntegerTypeFromEnumType : CppInterOpAPI { + let Doc = [{For the given "type", this function gets the integer type that +the enum represents.}]; + let ReturnType = "TCppType_t"; + let Args = [Arg<"TCppType_t", "handle">]; +} + +def GetOperatorArity : CppInterOpAPI { + let Doc = "\\returns arity of the operator or kNone."; + let ReturnType = "OperatorArity"; + let Args = [Arg<"TCppFunction_t", "op">]; +} + +def GetOperatorFromSpelling : CppInterOpAPI { + let Doc = "\\returns operator representing the string."; + let ReturnType = "Operator"; + let Args = [Arg<"const std::string&", "op">]; +} + +def GetQualifiedCompleteName : CppInterOpAPI { + let Doc = [{This is similar to GetQualifiedName() function, but besides +the "qualified" name, it also gets the template arguments.}]; + let ReturnType = "std::string"; + let Args = [Arg<"TCppScope_t", "klass">]; +} + +def GetScopeFromCompleteName : CppInterOpAPI { + let Doc = [{When the namespace is known, then the parent doesn't need +to be specified. This will probably be phased-out in +future versions of the interop library.}]; + let ReturnType = "TCppScope_t"; + let Args = [Arg<"const std::string&", "name">]; +} + +def GetSpellingFromOperator : CppInterOpAPI { + let Doc = "\\returns string representation of the operator."; + let ReturnType = "std::string"; + let Args = [Arg<"Operator", "op">]; +} + +def GetVersion : CppInterOpAPI { + let Doc = "\\returns the version string information of the library."; + let ReturnType = "std::string"; +} + +def GetBuildInfo : CppInterOpAPI { + let Doc = [{\returns a multi-line snapshot of the CppInterOp configure- +time build context (build type, compiler, LLVM version, target, the +filtered cmake cache flags). Embedded in trace reproducers and bug +reports so a reviewer can compare their build to the reporter's.}]; + + let ReturnType = "std::string"; +} + +def HasDefaultConstructor : CppInterOpAPI { + let Doc = "\\returns if a class has a default constructor."; + let ReturnType = "bool"; + let Args = [Arg<"TCppScope_t", "scope">]; +} + +def InsertOrReplaceJitSymbol : CppInterOpAPI { + let Doc = [{Inserts or replaces a symbol in the JIT with the one provided. +This is useful for providing implementations for symbols not linked in.}]; + let ReturnType = "bool"; + let Args = [ + Arg<"const char*", "linker_mangled_name">, + Arg<"uint64_t", "address"> + ]; +} + +def InstantiateTemplateFunctionFromString : CppInterOpAPI { + let Doc = [{Instantiates a function template from a given string representation. +This function parses and instantiates a template function.}]; + let ReturnType = "TCppFunction_t"; + let Args = [Arg<"const char*", "function_template">]; +} + +def IsFunction : CppInterOpAPI { + let Doc = "Checks if the scope is a function."; + let ReturnType = "bool"; + let Args = [Arg<"TCppScope_t", "scope">]; +} + +def IsFunctionPointerType : CppInterOpAPI { + let Doc = "Checks if the type is a function pointer."; + let ReturnType = "bool"; + let Args = [Arg<"TCppType_t", "type">]; +} + +def IsMethod : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a method."; + let ReturnType = "bool"; + let Args = [Arg<"TCppConstFunction_t", "method">]; +} + +def IsPODType : CppInterOpAPI { + let Doc = "Checks if the provided parameter is a Plain Old Data Type (POD)."; + let ReturnType = "bool"; + let Args = [Arg<"TCppType_t", "type">]; +} + +def IsSmartPtrType : CppInterOpAPI { + let Doc = [{We assume that smart pointer types define both operator* and +operator->.}]; + let ReturnType = "bool"; + let Args = [Arg<"TCppType_t", "type">]; +} + +def IsTemplateSpecialization : CppInterOpAPI { + let Doc = "Checks if it is a class template specialization class."; + let ReturnType = "bool"; + let Args = [Arg<"TCppScope_t", "handle">]; +} + +def IsTypeDerivedFrom : CppInterOpAPI { + let Doc = "Checks if a C++ type derives from another."; + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "derived">, + Arg<"TCppType_t", "base"> + ]; +} + +def LookupConstructors : CppInterOpAPI { + let Doc = [{Sets a list of all the constructor for a scope/class that is +supplied as a parameter.}]; + let ReturnType = "void"; + let Args = [ + Arg<"const std::string&", "name">, + Arg<"TCppScope_t", "parent">, + OutArg<"std::vector&", "funcs"> + ]; +} + +def LookupLibrary : CppInterOpAPI { + let Doc = [{Looks up the library if access is enabled. +\\returns the path to the library.}]; + let ReturnType = "std::string"; + let Args = [Arg<"const char*", "lib_name">]; +} + +def MakeFunctionCallable_interp : CppInterOpAPI { + let CppName = "MakeFunctionCallable"; + let Doc = [{Creates a trampoline function using a specific interpreter and returns +a uniform interface to call it from compiled code.}]; + // JitCall is a C++ class; no mechanical C mapping. + let NoCWrapper = true; + let ReturnType = "JitCall"; + let Args = [ + Arg<"TInterp_t", "I">, + Arg<"TCppConstFunction_t", "func"> + ]; +} + +def SearchLibrariesForSymbol : CppInterOpAPI { + let Doc = [{Scans all libraries on the library search path for a given +potentially mangled symbol name.}]; + let ReturnType = "std::string"; + let Args = [ + Arg<"const char*", "mangled_name">, + Arg<"bool", "search_system", "true"> + ]; +} + +def Undo : CppInterOpAPI { + let Doc = [{Reverts the last N operations performed by the interpreter. +\\param[in] N The number of operations to undo. Defaults to 1. +\\returns 0 on success, non-zero on failure.}]; + let ReturnType = "int"; + let Args = [Arg<"unsigned", "N", "1">]; +} + +def UnloadLibrary : CppInterOpAPI { + let Doc = [{Finds \\c lib_stem considering the list of search paths and unloads it +by calling dlclose.}]; + let ReturnType = "void"; + let Args = [Arg<"const char*", "lib_stem">]; +} + +def UseExternalInterpreter : CppInterOpAPI { + let Doc = [{Sets the Interpreter instance with an external interpreter, +meant to be called before any other API calls.}]; + let ReturnType = "void"; + let Args = [Arg<"TInterp_t", "I">]; +} + +def MakeFunctionCallable : CppInterOpAPI { + let Doc = [{Creates a trampoline function by using the interpreter and returns a +uniform interface to call it from compiled code.}]; + // JitCall is a C++ class; no mechanical C mapping. + let NoCWrapper = true; + let ReturnType = "JitCall"; + let Args = [Arg<"TCppConstFunction_t", "func">]; +} + +def IsIntegerType : CppInterOpAPI { + let Doc = [{Checks if type has an integer representation. +If \p s is non-null, it is set to the signedness of the type.}]; + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "type">, + Arg<"Signedness*", "s", "nullptr"> + ]; +} + +def IsFloatingType : CppInterOpAPI { + let Doc = "Checks if type has a floating representation."; + let ReturnType = "bool"; + let Args = [Arg<"TCppType_t", "type">]; +} + +def IsSameType : CppInterOpAPI { + let Doc = [{Checks if two types are the equivalent, +i.e. have the same canonical type.}]; + let ReturnType = "bool"; + let Args = [ + Arg<"TCppType_t", "type_a">, + Arg<"TCppType_t", "type_b"> + ]; +} + +def IsVoidPointerType : CppInterOpAPI { + let Doc = "Checks if type is a void pointer."; + let ReturnType = "bool"; + let Args = [Arg<"TCppType_t", "type">]; +} + +// Trace-hook impls invoked by JitCall's inline fast path through +// ::CppInternal::DispatchRaw slots. Routing through the X-macro means +// Dispatch.h consumers pick up the slots via LoadDispatchAPI without +// any source change of their own. +def CppInterOpTraceJitCallInvokeImpl : CppInterOpAPI { + let Doc = "Trace hook for the JitCall::Invoke generic call path."; + let ReturnType = "void"; + let Args = [ + Arg<"const CppImpl::JitCall*", "JC">, + Arg<"void*", "result">, + Arg<"void**", "args">, + Arg<"std::size_t", "nargs">, + Arg<"void*", "self"> + ]; +} + +def CppInterOpTraceJitCallInvokeDestructorImpl : CppInterOpAPI { + let Doc = "Trace hook for the JitCall::InvokeDestructor path."; + let ReturnType = "void"; + let Args = [ + Arg<"const CppImpl::JitCall*", "JC">, + Arg<"void*", "object">, + Arg<"unsigned long", "nary">, + Arg<"int", "withFree"> + ]; +} + +def CppInterOpTraceJitCallInvokeReturnImpl : CppInterOpAPI { + let Doc = "Trace hook for post-Invoke pointer-return handle registration."; + let ReturnType = "void"; + let Args = [ + Arg<"const CppImpl::JitCall*", "JC">, + Arg<"void*", "result"> + ]; +} diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpAPI.td b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpAPI.td new file mode 100644 index 0000000000000..b2ed594b6eea9 --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpAPI.td @@ -0,0 +1,205 @@ +//===- CppInterOpAPI.td - Base classes for CppInterOp API -------*- td -*-===// +// +// Part of the compiler-research project, under the Apache License v2.0 with +// LLVM Exceptions. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Base TableGen classes for describing CppInterOp's public API. Each API +// function is a record that inherits from CppInterOpAPI. +// +// cppinterop-tblgen emits a single CppInterOpAPI.inc file containing one +// CPPINTEROP_API_FUNC(...) invocation per function. Each use site defines +// the macro to expand differently (raw pointers, wrappers, map entries, etc). +// +//===----------------------------------------------------------------------===// + +/// Semantic kind for an API type — used for future strong-type enforcement. +/// "None" means the argument is a plain C/C++ type with no semantic wrapper. +class APITypeKind { + string Name = name; +} +def NoKind : APITypeKind<"None">; +def ScopeKind : APITypeKind<"Scope">; +def TypeKind : APITypeKind<"Type">; +def FuncKind : APITypeKind<"Function">; +def ObjectKind : APITypeKind<"Object">; +def InterpKind : APITypeKind<"Interp">; + +// An argument to an API function. +class Arg { + string Type = type; + string Name = name; + string Default = default_; + APITypeKind Kind = NoKind; + // True when the function writes through this argument (output parameter). + // Drives the INTEROP_OUT-coverage runtime check. + bit IsOut = 0; +} + +// An output argument -- the function writes to *this argument. +class OutArg + : Arg { + let IsOut = 1; +} + +// A typed argument — carries semantic kind for future strong-type generation. +class TypedArg : Arg { + let Kind = kind; +} + +//===----------------------------------------------------------------------===// +// C type mapping — drives the generated C API wrappers. +// +// Each CTypeMap record maps a C++ type string to its C-compatible equivalent. +// The emitter reads these records instead of using a hardcoded table, so +// adding support for a new type only requires a new def here. +// +// CppType — the C++ type string as it appears in Arg<> or ReturnType. +// CType — the C-compatible type to use in the generated wrapper. +// ArgConversion — expression to convert a C argument to C++ before passing +// to the Cpp:: function. Use $name as the placeholder for +// the argument name. Empty means pass through unchanged. +// RetConversion — expression to convert the C++ return value to C. Use +// $result as the placeholder. Empty means pass through. +// +// Collection support (CollectionKind != ""): +// CollectionKind — "return", "outparam", or "inparam": +// "return" — return type is a collection; wrap into array struct. +// "outparam" — mutable reference param; remove from C args, change C +// return type to CType, create tmp collection in body. +// "inparam" — const reference param; expand to (ptr, size) in C args, +// construct collection from pointer range in body. +// ElementCType — C type of each element (e.g. "void*", "char*"). +//===----------------------------------------------------------------------===// +class CTypeMap { + string CppType = cpp; + string CType = c; + string ArgConversion = argconv; + string RetConversion = retconv; + string CollectionKind = coll; + string ElementCType = elem; +} + +// Identity mappings — C type == C++ type, no conversion needed. +def CMap_bool : CTypeMap<"bool", "bool">; +def CMap_int : CTypeMap<"int", "int">; +def CMap_void : CTypeMap<"void", "void">; +def CMap_voidp : CTypeMap<"void*", "void*">; +def CMap_constcharp: CTypeMap<"const char*", "const char*">; +def CMap_size_t : CTypeMap<"size_t", "size_t">; +def CMap_unsigned : CTypeMap<"unsigned", "unsigned">; +def CMap_intptr_t : CTypeMap<"intptr_t", "intptr_t">; +def CMap_int64_t : CTypeMap<"int64_t", "int64_t">; +def CMap_uint64_t : CTypeMap<"uint64_t", "uint64_t">; +def CMap_boolp : CTypeMap<"bool*", "bool*">; + +// Opaque pointer types — erase to void* at the C boundary. +def CMap_TCppScope_t : CTypeMap<"TCppScope_t", "void*">; +def CMap_TCppConstScope_t : CTypeMap<"TCppConstScope_t", "const void*">; +def CMap_TCppType_t : CTypeMap<"TCppType_t", "void*">; +def CMap_TCppConstType_t : CTypeMap<"TCppConstType_t", "const void*">; +def CMap_TCppFunction_t : CTypeMap<"TCppFunction_t", "void*">; +def CMap_TCppConstFunc_t : CTypeMap<"TCppConstFunction_t", "const void*">; +def CMap_TCppFuncAddr_t : CTypeMap<"TCppFuncAddr_t", "void*">; +def CMap_TInterp_t : CTypeMap<"TInterp_t", "void*">; +def CMap_TCppObject_t : CTypeMap<"TCppObject_t", "void*">; +def CMap_TCppIndex_t : CTypeMap<"TCppIndex_t", "size_t">; + +// String types — need conversion at the boundary. +def CMap_string : CTypeMap<"std::string", "char*", + /*ArgConv=*/"", + /*RetConv=*/"strdup($result.c_str())">; +def CMap_stringref : CTypeMap<"const std::string&", "const char*", + /*ArgConv=*/"std::string($name)">; + +// Enums — cast to/from the underlying integer type. +// Conversions use Cpp:: qualification because the generated C wrappers +// live outside the CppImpl namespace. +def CMap_QualKind : CTypeMap<"QualKind", "unsigned", + /*ArgConv=*/"static_cast($name)", + /*RetConv=*/"static_cast($result)">; +def CMap_Operator : CTypeMap<"Operator", "unsigned char", + /*ArgConv=*/"static_cast($name)", + /*RetConv=*/"static_cast($result)">; +def CMap_OperatorArity : CTypeMap<"OperatorArity", "unsigned char", + /*ArgConv=*/"static_cast($name)", + /*RetConv=*/"static_cast($result)">; +def CMap_CaptureStream : CTypeMap<"CaptureStreamKind", "unsigned char", + /*ArgConv=*/"static_cast($name)">; +def CMap_InterpLang : CTypeMap<"InterpreterLanguage", "unsigned char", + /*ArgConv=*/"", + /*RetConv=*/"static_cast($result)">; +def CMap_InterpLangStd : CTypeMap<"InterpreterLanguageStandard", "unsigned char", + /*ArgConv=*/"", + /*RetConv=*/"static_cast($result)">; +def CMap_ValueKind : CTypeMap<"ValueKind", "unsigned char", + /*ArgConv=*/"", + /*RetConv=*/"static_cast($result)">; +def CMap_Signedness : CTypeMap<"Signedness*", "unsigned*", + /*ArgConv=*/"reinterpret_cast($name)">; + +// TemplateArgInfo is standard-layout and C-compatible (constructor is +// #ifdef __cplusplus guarded). Pointer passes through unchanged. +def CMap_TemplateArgInfoPtr : CTypeMap<"const TemplateArgInfo*", + "const TemplateArgInfo*">; + +//===----------------------------------------------------------------------===// +// Collection type maps — trigger signature rewriting in the emitter. +//===----------------------------------------------------------------------===// + +// Vector return types → CppInterOpArray. +def CMap_VecScope_ret : CTypeMap<"std::vector", + "CppInterOpArray", "", "", "return", "void*">; +def CMap_VecFunc_ret : CTypeMap<"std::vector", + "CppInterOpArray", "", "", "return", "void*">; + +// Vector out-param types → removed from C args, becomes CppInterOpArray return. +def CMap_VecScope_out : CTypeMap<"std::vector&", + "CppInterOpArray", "", "", "outparam", "void*">; +def CMap_VecFunc_out : CTypeMap<"std::vector&", + "CppInterOpArray", "", "", "outparam", "void*">; + +// String collection out-params → CppInterOpStringArray return. +def CMap_VecStr_out : CTypeMap<"std::vector&", + "CppInterOpStringArray", "", "", "outparam", "char*">; +def CMap_SetStr_out : CTypeMap<"std::set&", + "CppInterOpStringArray", "", "", "outparam", "char*">; + +// Vector in-param types → expanded to (ptr, size) in C args. +def CMap_VecCharPP_in : CTypeMap<"const std::vector&", + "const char**", "", "", "inparam", "const char*">; +def CMap_VecFunc_in : CTypeMap<"const std::vector&", + "void**", "", "", "inparam", "void*">; +def CMap_VecTAI_in : CTypeMap<"const std::vector&", + "const TemplateArgInfo*", "", "", "inparam", "TemplateArgInfo">; + +// FIXME: std::vector& needs a dedicated array type — +// CppInterOpArray.data is void** but TemplateArgInfo is not pointer-sized. +// GetClassTemplateInstantiationArgs needs a hand-written wrapper. + +// Base class for all CppInterOp API functions. +class CppInterOpAPI { + // The C++ function name as it appears in CppImpl::. + string CppName = NAME; + // Return type. + string ReturnType; + // Ordered list of arguments. + list Args = []; + // The name used in the dispatch table. Defaults to CppName. Override for + // overloaded functions (e.g. GetFunctionAddress_fn). + string DispatchName = NAME; + // Semantic kind of the return type. + APITypeKind ReturnKind = NoKind; + // Doxygen documentation string (may contain newlines). + string Doc = ""; + // If non-empty, emit `[[deprecated(message)]]` on the declaration. + string Deprecated = ""; + // If true, skip C wrapper generation for this function (e.g. because + // it uses types that cannot be mapped to C). The emitter will warn + // for any skipped function so gaps are visible. + bit NoCWrapper = false; +} diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpDispatch.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpDispatch.cpp new file mode 100644 index 0000000000000..c02b5382cdbe8 --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpDispatch.cpp @@ -0,0 +1,46 @@ +// Implementation of the dispatch symbol table. Maps function names to +// CppImpl:: addresses for dlopen consumers via CppGetProcAddress. +// This is a library internal — it includes CppInterOp.h (not Dispatch.h) +// because it needs the CppImpl:: function declarations. + +#include "CppInterOp/CppInterOp.h" + +#include +#include +#include + +using namespace CppImpl; +using CppFnPtrTy = void (*)(); + +// NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast) +// Suppress deprecation: the dispatch table intentionally exposes all +// overloads, including those marked [[deprecated]] in the public API. +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4996) +#endif +static const std::unordered_map DispatchMap = { +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + {#DN, (CppFnPtrTy) static_cast(&CppImpl::CN)}, +#include "CppInterOp/CppInterOpAPI.inc" +}; +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif +// NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast) + +extern "C" CPPINTEROP_API 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 CppInterOp.td\n"; + return nullptr; + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast(it->second); +} diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h index 380e0b6dd69e7..8c3a464182235 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,8 @@ class Interpreter { std::unique_ptr inner; std::unique_ptr io_context; + mutable std::unique_ptr sDLM; + mutable std::once_flag sDLMInit; bool outOfProcess; public: @@ -216,9 +219,7 @@ class Interpreter { auto io_ctx = std::make_unique(); bool outOfProcess = false; -#if defined(_WIN32) || !defined(LLVM_BUILT_WITH_OOP_JIT) - outOfProcess = false; -#else +#if LLVM_VERSION_MAJOR > 21 && !defined(_WIN32) outOfProcess = std::any_of(vargs.begin(), vargs.end(), [](const char* arg) { return llvm::StringRef(arg).trim() == "--use-oop-jit"; }); @@ -335,15 +336,6 @@ 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 @@ -458,14 +450,13 @@ class Interpreter { const DynamicLibraryManager* getDynamicLibraryManager() const { assert(compat::getExecutionEngine(*inner) && "We must have an executor"); - static std::unique_ptr DLM = nullptr; - if (!DLM) { - DLM.reset(new DynamicLibraryManager()); - DLM->initializeDyld([](llvm::StringRef) { /*ignore*/ return false; }); - } - return DLM.get(); - // TODO: Add DLM to InternalExecutor and use executor->getDML() - // return inner->getExecutionEngine()->getDynamicLibraryManager(); + // Replaces the C++11 magic-static thread-safe init the previous + // function-local DLM had for free. + std::call_once(sDLMInit, [this] { + sDLM = std::make_unique(); + sDLM->initializeDyld([](llvm::StringRef) { /*ignore*/ return false; }); + }); + return sDLM.get(); } DynamicLibraryManager* getDynamicLibraryManager() { diff --git a/interpreter/CppInterOp/lib/CppInterOp/Dispatch.cpp b/interpreter/CppInterOp/lib/CppInterOp/Dispatch.cpp deleted file mode 100644 index e0bda99155711..0000000000000 --- a/interpreter/CppInterOp/lib/CppInterOp/Dispatch.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#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/Tracing.cpp b/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp index 06caeff3fee77..ed23697f5c09d 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/Tracing.cpp @@ -10,6 +10,7 @@ #include "CppInterOp/CppInterOp.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/Path.h" @@ -17,19 +18,70 @@ #include "llvm/Support/raw_ostream.h" #include +#include #include +#if defined(__linux__) || defined(__APPLE__) +#include +#endif + +// libclangCppInterOp's own trace-hook slot storage; Dispatch.h +// consumers carry their per-DSO copies, populated by LoadDispatchAPI. +namespace CppInternal { +namespace DispatchRaw { +CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeImpl)( + const CppImpl::JitCall*, void*, void**, std::size_t, void*) = nullptr; +CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeDestructorImpl)( + const CppImpl::JitCall*, void*, unsigned long, int) = nullptr; +CPPINTEROP_API void (*CppInterOpTraceJitCallInvokeReturnImpl)( + const CppImpl::JitCall*, void*) = nullptr; +} // namespace DispatchRaw +} // namespace CppInternal + namespace CppInterOp { namespace Tracing { -TraceInfo* TraceInfo::TheTraceInfo = nullptr; +CPPINTEROP_TRACE_API TraceInfo* TheTraceInfo = nullptr; void InitTracing() { - assert(!TraceInfo::TheTraceInfo); + assert(!TheTraceInfo); static llvm::ManagedStatic TI; - TraceInfo::TheTraceInfo = &*TI; + TheTraceInfo = &*TI; + // Wire libclangCppInterOp's own slots; Dispatch.h consumers do + // the equivalent via the X-macro in LoadDispatchAPI. + ::CppInternal::DispatchRaw::CppInterOpTraceJitCallInvokeImpl = + &CppImpl::CppInterOpTraceJitCallInvokeImpl; + ::CppInternal::DispatchRaw::CppInterOpTraceJitCallInvokeDestructorImpl = + &CppImpl::CppInterOpTraceJitCallInvokeDestructorImpl; + ::CppInternal::DispatchRaw::CppInterOpTraceJitCallInvokeReturnImpl = + &CppImpl::CppInterOpTraceJitCallInvokeReturnImpl; } +std::optional TraceRegion::lookupOutMask(llvm::StringRef Name) { + // Built once from CppInterOpAPI.inc via the OUT-only X-macro; + // CPPINTEROP_API_FUNC stays a no-op. + static const llvm::StringMap Map = { +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DA, CA, RT) +#define CPPINTEROP_API_OUT(CN, OutMask) {#CN, OutMask}, +#include "CppInterOp/CppInterOpAPI.inc" + }; + auto It = Map.find(Name); + if (It == Map.end()) + return std::nullopt; + return It->second; +} + +/// RAII: hold m_Dumping while the reproducer is being emitted. +namespace { +class DumpScope { + TraceInfo& TI; + +public: + explicit DumpScope(TraceInfo& T) : TI(T) { TI.setDumping(true); } + ~DumpScope() { TI.setDumping(false); } +}; +} // namespace + /// Helper: emit version info as comment lines. static void WriteVersionComment(llvm::raw_ostream& OS, const std::string& Version) { @@ -42,6 +94,103 @@ static void WriteVersionComment(llvm::raw_ostream& OS, } } +/// dladdr-based path to libclangCppInterOp, used as the dispatch-mode +/// fallback. Empty for statically-linked images (where dladdr returns +/// the executable, not a real shared object) so the reproducer falls +/// through to CPPINTEROP_LIBRARY_PATH. +static std::string GetCppInterOpLibPath() { +#if defined(__linux__) || defined(__APPLE__) + Dl_info info; + if (dladdr(reinterpret_cast(&CppImpl::GetVersion), &info) && + info.dli_fname) { + llvm::StringRef Name(info.dli_fname); + if (Name.contains(".so") || Name.ends_with(".dylib") || + Name.ends_with(".dll")) + return info.dli_fname; + } +#endif + return {}; +} + +/// Stream Cpp::GetBuildInfo() into the prologue as `//` comments. +static void WriteBuildContext(llvm::raw_ostream& OS) { + OS << "// Build context (CppInterOp configure-time snapshot):\n"; + // Bind to a local: StringRef of the GetBuildInfo() rvalue would + // dangle past the next `;`. + std::string Snapshot = CppImpl::GetBuildInfo(); + llvm::StringRef Info(Snapshot); + while (!Info.empty()) { + auto [Line, Rest] = Info.split('\n'); + if (!Line.empty()) + OS << "//" << Line << "\n"; + Info = Rest; + } +} + +/// Emit a prologue compilable in two modes selected by -D: +/// default , static-link +/// -DCPPINTEROP_USE_DISPATCH , dlopen-based +/// The dispatch path reads CPPINTEROP_LIBRARY_PATH and falls back to +/// the dladdr-captured libclangCppInterOp path (when available). +static void WriteReproducerPrologue(llvm::raw_ostream& OS, + llvm::StringRef Title, + const std::string& Version, + llvm::StringRef Footnote) { + OS << "// CppInterOp " << Title << "\n"; + WriteVersionComment(OS, Version); + if (!Footnote.empty()) + OS << "// " << Footnote << "\n"; + OS << "//\n"; + OS << "// Replay scope: re-runs recorded CppInterOp API calls. JIT\n"; + OS << "// wrappers (Cpp::JitCall::Invoke) are not replayed -- their\n"; + OS << "// pointer args reference the original process's memory. The\n"; + OS << "// trailing `// JitCall::Invoke ...` comment, if present,\n"; + OS << "// names the call active at the crash site.\n"; + OS << "//\n"; + WriteBuildContext(OS); + OS << "//\n"; + OS << "// Build (default, static link):\n"; + OS << "// c++ -std=c++17 -I" + " reproducer.cpp -lclangCppInterOp -o reproducer\n"; + OS << "// Build (dlopen via Dispatch.h):\n"; + OS << "// c++ -std=c++17 -DCPPINTEROP_USE_DISPATCH" + " -I" + " reproducer.cpp -ldl -o reproducer\n"; + std::string LibPath = GetCppInterOpLibPath(); + OS << "// Run: ./reproducer"; + if (LibPath.empty()) + OS << " (dispatch build: set " + "CPPINTEROP_LIBRARY_PATH=)"; + else + OS << " (dispatch build: CPPINTEROP_LIBRARY_PATH overrides the captured " + "path)"; + OS << "\n\n"; + + OS << "#ifdef CPPINTEROP_USE_DISPATCH\n"; + OS << "#include \n\n"; + // X-macro defines one DispatchRaw slot per public API entry. + OS << "using namespace CppImpl;\n"; + OS << "#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, " + "RawTypes) \\\n" + " Ret(*CppInternal::DispatchRaw::DN) RawTypes = nullptr;\n" + "#include \"CppInterOp/CppInterOpAPI.inc\"\n\n"; + // Lazy: lets a cling-driven harness call reproducer() before main(). + OS << "static void initCppInterOpReproducer() {\n"; + OS << " static bool inited = false;\n"; + OS << " if (inited) return;\n"; + OS << " const char* path = std::getenv(\"CPPINTEROP_LIBRARY_PATH\");\n"; + if (!LibPath.empty()) + OS << " if (!path) path = R\"PATH(" << LibPath << ")PATH\";\n"; + OS << " Cpp::LoadDispatchAPI(path);\n"; + OS << " inited = true;\n"; + OS << "}\n"; + OS << "#else\n"; + OS << "#include \n"; + // Static-link path: no-op so reproducer()'s init call is harmless. + OS << "static void initCppInterOpReproducer() {}\n"; + OS << "#endif\n\n"; +} + std::string TraceInfo::writeToFile(const std::string& Version) { llvm::SmallString<128> TmpDir; llvm::sys::path::system_temp_directory(/*ErasedOnReboot=*/true, TmpDir); @@ -54,13 +203,13 @@ std::string TraceInfo::writeToFile(const std::string& Version) { return ""; llvm::raw_fd_ostream OS(FD, /*shouldClose=*/true); + DumpScope Guard(*this); - OS << "// CppInterOp crash reproducer\n"; std::string Ver = Version.empty() ? CppImpl::GetVersion() : Version; - WriteVersionComment(OS, Ver); - OS << "// Generated automatically — re-run to reproduce the crash.\n"; - OS << "#include \n\n"; + WriteReproducerPrologue(OS, "crash reproducer", Ver, + "Generated automatically — re-run to reproduce."); OS << "void reproducer() {\n"; + OS << " initCppInterOpReproducer();\n"; for (const auto& Line : m_Log) OS << Line << "\n"; OS << "}\n\n"; @@ -107,13 +256,11 @@ void TraceInfo::StopRegion(const std::string& Version) { if (EC) return; + DumpScope Guard(*this); std::string Ver = Version.empty() ? CppImpl::GetVersion() : Version; - - OS << "// CppInterOp trace region\n"; - WriteVersionComment(OS, Ver); - OS << "// Generated automatically.\n"; - OS << "#include \n\n"; + WriteReproducerPrologue(OS, "trace region", Ver, "Generated automatically."); OS << "void reproducer() {\n"; + OS << " initCppInterOpReproducer();\n"; for (size_t i = m_RegionStart; i < m_Log.size(); ++i) OS << m_Log[i] << "\n"; OS << "}\n\n"; diff --git a/interpreter/CppInterOp/lib/CppInterOp/Tracing.h b/interpreter/CppInterOp/lib/CppInterOp/Tracing.h index 1bc27b88cb246..b17dd31d71fb9 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/Tracing.h +++ b/interpreter/CppInterOp/lib/CppInterOp/Tracing.h @@ -23,6 +23,8 @@ #define CPPINTEROP_TRACE_API #endif +#include "CppInterOp/CppInterOpTypes.h" + #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" @@ -31,9 +33,12 @@ #include "llvm/Support/raw_ostream.h" #include +#include #include #include +#include #include +#include #include #include #include @@ -41,17 +46,41 @@ namespace CppInterOp { namespace Tracing { +class TraceInfo; + +/// Process-global tracer pointer. Exported because TracingTests and +/// the crash handler (linked-mode consumers) read it directly; cppyy +/// and Dispatch.h consumers go through the DispatchRaw trace slots +/// declared in CppInterOpTypes.h instead. +extern CPPINTEROP_TRACE_API TraceInfo* TheTraceInfo; + class TraceInfo { llvm::TimerGroup m_TG; llvm::StringMap> m_Timers; std::vector m_TimerStack; - std::unordered_map m_HandleMap; + std::unordered_map m_HandleMap; unsigned m_VarCount = 0; + /// Monotonic suffix for `_retN` placeholders -- one slot per call + /// that returns std::vector. Separate from m_VarCount so the + /// vN handle namespace stays dense. + unsigned m_RetCount = 0; + /// Monotonic suffix for `_outN` -- one slot per distinct OUT + /// pointer-container source. Multiple calls passing the same + /// container alias the same slot (see m_OutAliases). + unsigned m_OutCount = 0; + /// Source address of an OUT container -> its `_outN` index. Lets + /// the reproducer faithfully replay the API's append-on-call + /// contract: a vector reused across calls keeps its single buffer. + std::unordered_map m_OutAliases; std::vector m_Log; size_t m_RegionStart = 0; ///< Log index where current region began. bool m_InRegion = false; ///< True between StartTracing/StopTracing. + /// Set while the reproducer is being emitted. Gates appendToLog and + /// setLogEntry so the dumper's own INTEROP_TRACE-wrapped calls + /// (GetVersion, GetBuildInfo) don't recurse into m_Log. + bool m_Dumping = false; public: TraceInfo() : m_TG("CppInterOp", "CppInterOp Timing Report") {} @@ -70,6 +99,9 @@ class TraceInfo { return *T; } + /// True when at least one TraceRegion is currently active. + bool insideTracedRegion() const { return !m_TimerStack.empty(); } + void pushTimer(llvm::Timer* T) { if (!m_TimerStack.empty()) m_TimerStack.back()->stopTimer(); @@ -86,7 +118,7 @@ class TraceInfo { m_TimerStack.back()->startTimer(); } - std::string getOrRegisterHandle(void* p) { + std::string getOrRegisterHandle(const void* p) { if (!p) return ""; auto it = m_HandleMap.find(p); @@ -95,18 +127,55 @@ class TraceInfo { return m_HandleMap[p] = "v" + std::to_string(++m_VarCount); } - std::string lookupHandle(void* p) { + /// Resolve a pointer to a printable form. + /// - "nullptr" for an actual null pointer. + /// - "vN" for a pointer registered via getOrRegisterHandle. + /// - "" (empty) for a non-null pointer never seen by the tracer -- + /// the caller decides how to render the unknown case (e.g. + /// `nullptr /*unknown*/` in argument lists, "is this new?" in the + /// producer-side `auto vN = ...` gating logic). + std::string lookupHandle(const void* p) { if (!p) return "nullptr"; auto it = m_HandleMap.find(p); - return (it != m_HandleMap.end()) ? it->second : "nullptr"; + return (it != m_HandleMap.end()) ? it->second : ""; } - void appendToLog(const std::string& line) { + /// Allocate the next `_retN` index for a vector-return placeholder. + unsigned nextRetIndex() { return m_RetCount++; } + + /// Resolve an OUT-container source address to its `_outN` index. + /// First call with a given address allocates a fresh slot; later + /// calls return the same slot so the reproducer reuses the buffer. + /// \pre \p Addr is non-null (MakeOutParam captures `&C`). + /// \returns {idx, true} on first use, {idx, false} on alias. + std::pair outIndexFor(const void* Addr) { + assert(Addr && "OutParam without a source address"); + auto it = m_OutAliases.find(Addr); + if (it != m_OutAliases.end()) + return {it->second, false}; + unsigned idx = m_OutCount++; + m_OutAliases.emplace(Addr, idx); + return {idx, true}; + } + + /// Append a line; the returned index pairs with setLogEntry to + /// rewrite the same slot later (TraceRegion's placeholder pattern). + size_t appendToLog(const std::string& line) { + if (m_Dumping) + return 0; + size_t idx = m_Log.size(); m_Log.push_back(line); if (m_InRegion && m_WriteOnStdErr) llvm::errs() << line << "\n"; + return idx; } + void setLogEntry(size_t idx, const std::string& line) { + if (m_Dumping) + return; + m_Log[idx] = line; + } + void setDumping(bool v) { m_Dumping = v; } const std::vector& getLog() const { return m_Log; } std::string getLastLogEntry() const { return m_Log.empty() ? "" : m_Log.back(); @@ -141,9 +210,10 @@ class TraceInfo { m_HandleMap.clear(); m_Log.clear(); m_VarCount = 0; + m_RetCount = 0; + m_OutCount = 0; + m_OutAliases.clear(); } - - CPPINTEROP_TRACE_API static TraceInfo* TheTraceInfo; }; /// Activate tracing. Called once during process initialization. @@ -154,16 +224,16 @@ CPPINTEROP_TRACE_API void InitTracing(); /// it. Returns the path where StopTracing() will write the reproducer. /// \param WriteOnStdErr if true, also emit the reproducer to stderr on stop. inline std::string StartTracing(bool WriteOnStdErr = true) { - if (!TraceInfo::TheTraceInfo) + if (!TheTraceInfo) InitTracing(); - return TraceInfo::TheTraceInfo->StartRegion(WriteOnStdErr); + return TheTraceInfo->StartRegion(WriteOnStdErr); } /// End the traced region and write the reproducer file containing only the /// calls made between StartTracing() and this call. inline void StopTracing(const std::string& Version = "") { - if (TraceInfo::TheTraceInfo) - TraceInfo::TheTraceInfo->StopRegion(Version); + if (TheTraceInfo) + TheTraceInfo->StopRegion(Version); } /// Marks a function parameter as an output container (e.g. std::vector& @@ -178,25 +248,61 @@ inline void StopTracing(const std::string& Version = "") { /// all downstream code (ReproBuffer, TraceRegion) works with a single /// concrete type — no template specializations or detection traits needed. struct OutParam { - /// Callback that registers the container's pointer elements as handles. - /// Null when the container doesn't hold pointers (e.g. vector). - std::function RegisterHandles; + /// Register the container's pointer elements as handles and emit + /// one `void* vN = _outN[i] : nullptr;` decl per newly-registered + /// element so a later call referencing the registered name finds a + /// real binding. + std::function RegisterHandles; + /// Drives the `_outN` preamble + alias path. Non-pointer containers + /// stay on the legacy "skip in arg list" rendering -- their type is + /// not erasable to void*, so the preamble decl would not type-check. + bool IsPointerContainer = false; + /// Scalar pointer OUT (e.g. `bool*`); rendered as `nullptr`. + bool IsScalarPointer = false; + /// Address of the source container object (not its data buffer); + /// multiple calls with the same container alias the same `_outN` + /// buffer in the reproducer so accumulation across calls replays + /// faithfully. Stable across capacity growth -- a `push_back` that + /// reallocates the heap buffer leaves the object's address put. + const void* SourceAddr = nullptr; }; /// Create an OutParam for any container. Only sets up handle registration /// when the container's value_type is a pointer. template OutParam MakeOutParam(const Container& C) { OutParam OP; + OP.SourceAddr = static_cast(&C); using Value = typename Container::value_type; if constexpr (std::is_pointer_v) { - OP.RegisterHandles = [&C](TraceInfo& TI) { - for (const auto& Elem : C) - TI.getOrRegisterHandle(reinterpret_cast(Elem)); + OP.IsPointerContainer = true; + OP.RegisterHandles = [&C](TraceInfo& TI, unsigned OutIdx) { + size_t I = 0; + for (const auto& Elem : C) { + const void* P = static_cast(Elem); + bool isNew = TI.lookupHandle(P).empty(); + std::string Name = TI.getOrRegisterHandle(P); + // Bounds-guard with `.size() > i` for replays where the + // function fills fewer elements than the original call did. + if (isNew && P) + TI.appendToLog(llvm::formatv(" void* {0} = _out{1}.size() > {2} ? " + "_out{1}[{2}] : nullptr;", + Name, OutIdx, I) + .str()); + ++I; + } }; } return OP; } +/// Scalar pointer overload (e.g. `bool* HadError`). Rendered as +/// `nullptr` at the call site -- replay only needs the call sequence. +template OutParam MakeOutParam(T*) { + OutParam OP; + OP.IsScalarPointer = true; + return OP; +} + /// Internal helper to stringify arguments into a C++ call format. struct ReproBuffer { llvm::SmallString<128> Buffer; @@ -205,11 +311,49 @@ struct ReproBuffer { ReproBuffer() : OS(Buffer) {} // Opaque handle pointers — resolved to their registered name. - void append(void* p) { OS << TraceInfo::TheTraceInfo->lookupHandle(p); } + // const void* subsumes void* via the standard add-const conversion, + // so one overload covers both TCppFunction_t (= void*) and + // TCppConstFunction_t (= const void*) public typedefs. + void append(const void* p) { + if (!p) { + OS << "nullptr"; + return; + } + auto h = TheTraceInfo->lookupHandle(p); + if (h.empty()) + OS << "nullptr /*unknown*/"; + else + OS << h; + } - // Strings — quoted. - void append(const char* s) { OS << "\"" << s << "\""; } - void append(const std::string& s) { OS << "\"" << s << "\""; } + // Strings -- emit a plain `"..."` literal when the content is + // printable ASCII without `"`, `\`, or control chars; otherwise + // fall back to `R"CPPI(...)CPPI"`, which passes the bytes through + // verbatim. The raw form is reserved for the cases that actually + // need it (quotes, backslashes, newlines from Cpp::Process / + // Cpp::Declare source blocks); plain literals keep the trace lines + // short and human-readable for the common case (identifiers, + // paths, simple expressions). + void appendRaw(std::string_view s) { + bool NeedsRaw = false; + for (char c : s) { + auto u = static_cast(c); + // 0x20 is space: anything below it is a C0 control char (incl. \n, + // \t, \r) that would break a plain quoted literal; 0x7f is DEL. + if (c == '"' || c == '\\' || u < 0x20 || u == 0x7f) { + NeedsRaw = true; + break; + } + } + if (NeedsRaw) + OS << "R\"CPPI(" << s << ")CPPI\""; + else + OS << '"' << s << '"'; + } + void append(const char* s) { + appendRaw(s ? std::string_view(s) : std::string_view()); + } + void append(const std::string& s) { appendRaw(s); } // Numeric types — printed directly. void append(bool v) { OS << (v ? "true" : "false"); } @@ -222,17 +366,104 @@ struct ReproBuffer { void append(double d) { OS << llvm::formatv("{0:f}", d); } void append(float f) { OS << llvm::formatv("{0:f}", f); } - // Containers — not meaningfully printable; emit a placeholder. - template void append(const std::vector&) { OS << "{...}"; } + // Vector input args -- emit a braced init list of recursively-formatted + // elements so the reproducer's call-site literal compiles. + template void append(const std::vector& V) { + OS << "{"; + bool First = true; + for (const auto& E : V) { + if (!First) + OS << ", "; + First = false; + append(E); + } + OS << "}"; + } - // Anything else we haven't accounted for. - template void append(const T&) { OS << "?"; } + // TemplateArgInfo: brace-init through the (TCppScope_t, const char*) + // ctor so the reproducer compiles. m_Type takes the void* path + // (renders as vN); nullptr m_IntegralValue must render as `nullptr` + // (the ctor's default), not the empty string the const char* path + // would produce. + void append(const CppImpl::TemplateArgInfo& tai) { + OS << "Cpp::TemplateArgInfo{"; + append(static_cast(tai.m_Type)); + OS << ", "; + if (tai.m_IntegralValue == nullptr) + OS << "nullptr"; + else + appendRaw(tai.m_IntegralValue); + OS << "}"; + } - /// Format a comma-separated argument list, skipping OutParam entries. - template void format(Args&&... args) { + // Enums: emit "static_cast(N)" so the reproducer compiles. + // EnumName comes from the compiler's pretty signature -- no RTTI. + // Compute outside any lambda: gcc/clang substitute T into a lambda's + // signature, which drops the "T = " marker we parse from. + static std::string parseEnumName(const char* sig) { + llvm::StringRef s(sig); +#ifdef _MSC_VER + // MSVC: "... ReproBuffer::append(enum QualKind)" + auto pos = s.find("append<"); + if (pos == llvm::StringRef::npos) + return {}; + s = s.drop_front(pos + 7); + for (auto kw : {"enum ", "class ", "struct "}) + if (s.consume_front(kw)) + break; + return s.take_until([](char c) { return c == '>'; }).str(); +#else + // gcc: "... [with T = QualKind; ...]" + // clang: "... [T = QualKind]" + auto pos = s.find("T = "); + if (pos == llvm::StringRef::npos) + return {}; + return s.drop_front(pos + 4) + .take_until([](char c) { return c == ',' || c == ';' || c == ']'; }) + .str(); +#endif + } + template , int> = 0> + void append(T v) { +#ifdef _MSC_VER + static const std::string TN = parseEnumName(__FUNCSIG__); +#else + static const std::string TN = parseEnumName(__PRETTY_FUNCTION__); +#endif + OS << "static_cast<" << TN << ">(" + << +static_cast>(v) << ")"; + } + // Catch-all for non-enum, non-pointer types. + template < + typename T, + std::enable_if_t && !std::is_pointer_v, int> = 0> + void append(const T&) { + OS << "?"; + } + + /// Format a comma-separated argument list. Pointer-container OUTs + /// emit `_outN` (next index from \p OutIndices); scalar pointer OUTs + /// emit `nullptr` (the replay does not consume the value); non-pointer + /// containers are skipped, matching the legacy rendering. + template + void format(llvm::ArrayRef OutIndices, Args&&... args) { bool first = true; + size_t nextOut = 0; auto appendOne = [&](auto&& val) { - if constexpr (!std::is_same_v, OutParam>) { + using V = std::decay_t; + if constexpr (std::is_same_v) { + if (val.IsPointerContainer) { + if (!first) + OS << ", "; + first = false; + OS << "_out" << OutIndices[nextOut++]; + } else if (val.IsScalarPointer) { + if (!first) + OS << ", "; + first = false; + OS << "nullptr"; + } + } else { if (!first) OS << ", "; first = false; @@ -243,6 +474,16 @@ struct ReproBuffer { } }; +/// Matches std::vector for any pointer element type. Used by +/// TraceRegion::record to recognise functions whose return value is a +/// vector of opaque handles (e.g. GetFunctionsUsingName), so the +/// reproducer can name the vector and read its elements out by index. +template struct is_pointer_vector : std::false_type {}; +template +struct is_pointer_vector> : std::true_type {}; +template +inline constexpr bool is_pointer_vector_v = is_pointer_vector::value; + /// Holds all the data that is only needed when tracing is active. /// Heap-allocated only when TheTraceInfo != nullptr, so disabled tracing /// pays zero cost beyond a single pointer + bool on the stack. @@ -251,16 +492,46 @@ struct TraceData { llvm::SmallString<128> ArgStr; void* Result = nullptr; bool HasPtrResult = false; + /// Set when the call returned std::vector; ~TraceRegion uses this + /// to spell `auto _retN = Cpp::Foo(...)` so element decls can read + /// `_retN[i]` and the replay sees the original pointers. + bool HasRetVec = false; + /// Snapshot of the returned vector's pointer elements, taken at + /// record() time -- the source vector goes out of scope before + /// ~TraceRegion runs. + llvm::SmallVector RetVecPtrs; double StartTime = 0; bool Returned = false; - llvm::SmallVector, 2> OutCallbacks; + llvm::SmallVector, 2> OutCallbacks; + /// `_outN` indices for this call's OUT pointer-containers, in + /// left-to-right order. Consumed by format() at the call site and + /// by ~TraceRegion to emit the preamble decls. + llvm::SmallVector OutIndices; + /// Per-OUT argument flag: true on the first call with this source + /// container (preamble decl needed), false on later calls reusing + /// the same slot. + llvm::SmallVector OutFirstUse; + /// Set when another TraceRegion was already active at construction. + /// Nested calls skip log emission but still register handles. + bool Nested = false; + /// Slot for the ctor-emitted placeholder; the dtor rewrites it. + size_t LogIndex = 0; }; class TraceRegion { std::unique_ptr m_Data; - // Capture an OutParam's handle-registration callback; ignore everything else. + /// Resolve the OUT pointer-container's `_outN` index up-front so + /// format() can render the call site's alias and the dtor knows + /// whether to emit the preamble decl (only on first use of a given + /// source container). Skipped for nested calls (no emission); the + /// handle callback still queues so the outer call can resolve names. void captureArg(OutParam&& op) { + if (!m_Data->Nested && op.IsPointerContainer) { + auto [idx, firstUse] = TheTraceInfo->outIndexFor(op.SourceAddr); + m_Data->OutIndices.push_back(idx); + m_Data->OutFirstUse.push_back(firstUse); + } if (op.RegisterHandles) m_Data->OutCallbacks.push_back(std::move(op.RegisterHandles)); } @@ -268,17 +539,36 @@ class TraceRegion { public: template TraceRegion(const char* Name, Args&&... args) { - if (!TraceInfo::TheTraceInfo) + if (!TheTraceInfo) return; m_Data = std::make_unique(); m_Data->Name = Name; + TraceInfo& TI = *TheTraceInfo; + // Detect nesting before pushing this call's frame. + m_Data->Nested = TI.insideTracedRegion(); + // captureArg before format(): it fills OutIndices that format() + // consumes. (captureArg(std::forward(args)), ...); - if constexpr (sizeof...(args) > 0) { - ReproBuffer RB; - RB.format(std::forward(args)...); - m_Data->ArgStr = RB.Buffer; + if (!m_Data->Nested) { + if constexpr (sizeof...(args) > 0) { + ReproBuffer RB; + RB.format(m_Data->OutIndices, std::forward(args)...); + m_Data->ArgStr = RB.Buffer; + } + // `_outN` preamble + placeholder so an abort before the dtor + // still leaves a record of the failing call. Emit the preamble + // only on first use of each source container; reused ones alias + // the existing slot. + for (size_t i = 0; i < m_Data->OutIndices.size(); ++i) + if (m_Data->OutFirstUse[i]) + TI.appendToLog(llvm::formatv(" std::vector _out{0};", + m_Data->OutIndices[i]) + .str()); + m_Data->LogIndex = TI.appendToLog( + llvm::formatv(" Cpp::{0}({1}); // [aborted before return]", + m_Data->Name, m_Data->ArgStr) + .str()); } - TraceInfo& TI = *TraceInfo::TheTraceInfo; TI.pushTimer(&TI.getTimer(Name)); m_Data->StartTime = llvm::TimeRecord::getCurrentTime(false).getWallTime(); } @@ -297,28 +587,67 @@ class TraceRegion { auto EndTime = llvm::TimeRecord::getCurrentTime(false).getWallTime(); auto Dur = static_cast((EndTime - m_Data->StartTime) * 1e9); - TraceInfo& TI = *TraceInfo::TheTraceInfo; + TraceInfo& TI = *TheTraceInfo; TI.popTimer(); - // Register out-param handles now that the function has filled them. - for (auto& cb : m_Data->OutCallbacks) - cb(TI); + // Nested calls don't reach the log -- their args reference + // outer-scope locals the reproducer doesn't have. + if (m_Data->Nested) { + m_Data.reset(); + return; + } + + // Allocate a `_retN` slot up-front for vector-of-pointer returns so + // the call line spells `auto _retN = ...` and the per-element decls + // below can read `_retN[i]` (the replay sees the original + // pointers, not literal nulls). + int RetIdx = -1; + llvm::SmallVector, 8> RetDecls; + if (m_Data->HasRetVec) { + RetIdx = static_cast(TI.nextRetIndex()); + for (size_t i = 0; i < m_Data->RetVecPtrs.size(); ++i) { + const void* p = m_Data->RetVecPtrs[i]; + if (!p) + continue; + bool isNew = TI.lookupHandle(p).empty(); + std::string Name = TI.getOrRegisterHandle(p); + if (isNew) + RetDecls.emplace_back(i, std::move(Name)); + } + } std::string VarPart; if (m_Data->Result) { - bool isNew = TI.lookupHandle(m_Data->Result) == "nullptr"; + bool isNew = TI.lookupHandle(m_Data->Result).empty(); std::string HandleName = TI.getOrRegisterHandle(m_Data->Result); VarPart = llvm::formatv(isNew ? "auto {0} = " : "/*{0}*/ ", HandleName).str(); } else if (m_Data->HasPtrResult) { VarPart = "/*nullptr*/ "; + } else if (RetIdx >= 0) { + VarPart = llvm::formatv("auto _ret{0} = ", RetIdx).str(); } + // Rewrite the placeholder with the completed call line. std::string Call = llvm::formatv(" {0}Cpp::{1}({2}); // [{3} ns]", VarPart, m_Data->Name, m_Data->ArgStr, Dur); - - // Store in log for the reproducer file. - TI.appendToLog(Call); + TI.setLogEntry(m_Data->LogIndex, Call); + + // After the call: register OUT-element handles and emit one + // `void* vN = _outN[i] : nullptr;` decl per newly-seen element so a + // later call referencing the registered name finds a real binding. + for (size_t i = 0; i < m_Data->OutCallbacks.size(); ++i) + m_Data->OutCallbacks[i](TI, m_Data->OutIndices[i]); + + // Per-element extractions for vector returns. Bounds-guard so the + // line is safe even if the replay produces a shorter vector than + // the original. + for (auto& [Idx, Name] : RetDecls) + TI.appendToLog( + llvm::formatv( + " void* {0} = _ret{1}.size() > {2} ? _ret{1}[{2}] : nullptr;", + Name, RetIdx, Idx) + .str()); m_Data.reset(); } @@ -342,6 +671,12 @@ class TraceRegion { m_Data->Result = const_cast(static_cast(val)); } else if constexpr (std::is_null_pointer_v) { m_Data->HasPtrResult = true; + } else if constexpr (is_pointer_vector_v) { + // Snapshot the element pointers; the source vector is owned by + // the API call site and goes out of scope before ~TraceRegion. + m_Data->HasRetVec = true; + for (auto* p : val) + m_Data->RetVecPtrs.push_back(static_cast(p)); } return val; } @@ -402,6 +737,18 @@ class TraceRegion { return count; } + /// Bit i set iff Args[i] is OutParam (i.e. wrapped in INTEROP_OUT). + /// The trailing `false` keeps the array non-empty for sizeof...(Args)==0. + template static constexpr uint64_t computeOutMask() { + constexpr bool isOut[] = {std::is_same_v, OutParam>..., + false}; + uint64_t mask = 0; + for (std::size_t i = 0; i < sizeof...(Args); ++i) + if (isOut[i]) + mask |= 1ULL << i; + return mask; + } + /// Helper to allow INTEROP_TRACE() to work with zero or more arguments /// without relying on non-standard ##__VA_ARGS__ comma elision. struct Proxy { @@ -422,9 +769,44 @@ class TraceRegion { "parameters. Update the INTEROP_TRACE call."); } #endif + // OUT-mask check runs in any build but only when tracing is active, + // so the per-call cost off-trace is one load + branch. The diagnostic + // is unconditional; assert() is a no-op in NDEBUG, so Release reports + // the drift via stderr without aborting. + if (TheTraceInfo) { + if (auto Expected = lookupOutMask(Name)) { + uint64_t actual_out = computeOutMask(); + if (*Expected != actual_out) { + llvm::errs() << formatOutMaskMismatchMessage(Name, *Expected, + actual_out); + assert( + *Expected == actual_out && + "INTEROP_TRACE OUT-arg coverage does not match the .td " + "OutArg<...> declarations. Wrap or unwrap with INTEROP_OUT."); + } + } + } return TraceRegion(Name, std::forward(args)...); } }; + + /// .td-declared OUT-arg bitmask for \p Name (matches CppName / + /// __func__), or std::nullopt for non-public-API tracepoints. + CPPINTEROP_TRACE_API static std::optional + lookupOutMask(llvm::StringRef Name); + + /// Pure-function diagnostic for the OUT-mask mismatch. Extracted so a + /// non-death test can cover the format path -- the assert path itself + /// runs in a forked child whose coverage data is not merged back. + static std::string formatOutMaskMismatchMessage(llvm::StringRef Name, + uint64_t expected, + uint64_t actual) { + return llvm::formatv("ERROR: INTEROP_OUT coverage mismatch in '{0}': .td " + "OutArg mask {1:X} vs INTEROP_OUT-wrapped args " + "{2:X}.\n", + Name, expected, actual) + .str(); + } }; } // namespace Tracing diff --git a/interpreter/CppInterOp/lib/CppInterOp/exports.ld b/interpreter/CppInterOp/lib/CppInterOp/exports.ld index 762a463096e68..8ed72ec64af1b 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/exports.ld +++ b/interpreter/CppInterOp/lib/CppInterOp/exports.ld @@ -55,3 +55,5 @@ -Wl,--export=_ZN5clang11Interpreter6createENSt3__210unique_ptrINS_16CompilerInstanceENS1_14default_deleteIS3_EEEENS2_IN4llvm3orc12LLJITBuilderENS4_IS9_EEEE -Wl,--export=_ZNK5clang13CXXRecordDecl19isInjectedClassNameEv -Wl,--export=_ZNK5clang8QualType11getAsStringERKNS_14PrintingPolicyE +-Wl,--export=_ZN5clang11Interpreter6createENSt3__210unique_ptrINS_16CompilerInstanceENS1_14default_deleteIS3_EEEENS2_INS_26IncrementalExecutorBuilderENS4_IS7_EEEE +-Wl,--export=_ZNK5clang7TagDecl13getDefinitionEv diff --git a/interpreter/CppInterOp/patches/llvm/README.md b/interpreter/CppInterOp/patches/llvm/README.md deleted file mode 100644 index 4a70de66405fb..0000000000000 --- a/interpreter/CppInterOp/patches/llvm/README.md +++ /dev/null @@ -1 +0,0 @@ -LLVM/Clang patches diff --git a/interpreter/CppInterOp/patches/llvm/clang20-1-out-of-process.patch b/interpreter/CppInterOp/patches/llvm/clang20-1-out-of-process.patch deleted file mode 100644 index 198afb99a0530..0000000000000 --- a/interpreter/CppInterOp/patches/llvm/clang20-1-out-of-process.patch +++ /dev/null @@ -1,966 +0,0 @@ -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/cling1.2-LookupHelper.patch b/interpreter/CppInterOp/patches/llvm/cling1.2-LookupHelper.patch deleted file mode 100644 index b0881729697b7..0000000000000 --- a/interpreter/CppInterOp/patches/llvm/cling1.2-LookupHelper.patch +++ /dev/null @@ -1,42 +0,0 @@ -diff --git a/include/cling/Interpreter/LookupHelper.h b/include/cling/Interpreter/LookupHelper.h -index 6e6e2814..cd79b2a6 100644 ---- a/include/cling/Interpreter/LookupHelper.h -+++ b/include/cling/Interpreter/LookupHelper.h -@@ -56,7 +56,7 @@ namespace cling { - WithDiagnostics - }; - private: -- std::unique_ptr m_Parser; -+ clang::Parser* m_Parser; - Interpreter* m_Interpreter; // we do not own. - std::array m_StringTy = {{}}; - /// A map containing the hash of the lookup buffer. This allows us to avoid -diff --git a/lib/Interpreter/Interpreter.cpp b/lib/Interpreter/Interpreter.cpp -index 6af90108..89ca360b 100644 ---- a/lib/Interpreter/Interpreter.cpp -+++ b/lib/Interpreter/Interpreter.cpp -@@ -265,13 +265,6 @@ namespace cling { - } - - Sema& SemaRef = getSema(); -- Preprocessor& PP = SemaRef.getPreprocessor(); -- -- m_LookupHelper.reset(new LookupHelper(new Parser(PP, SemaRef, -- /*SkipFunctionBodies*/false, -- /*isTemp*/true), this)); -- if (!m_LookupHelper) -- return; - - if (!isInSyntaxOnlyMode() && !m_Opts.CompilerOpts.CUDADevice) { - m_Executor.reset(new IncrementalExecutor(SemaRef.Diags, *getCI(), -@@ -317,6 +310,10 @@ namespace cling { - return; - } - -+ m_LookupHelper.reset(new LookupHelper(m_IncrParser->getParser(), this)); -+ if (!m_LookupHelper) -+ return; -+ - // When not using C++ modules, we now have a PCH and we can safely setup - // our callbacks without fearing that they get overwritten by clang code. - // The modules setup is handled above. diff --git a/interpreter/CppInterOp/patches/llvm/emscripten-clang20-2-shift-temporary-files-to-tmp-dir.patch b/interpreter/CppInterOp/patches/llvm/emscripten-clang20-2-shift-temporary-files-to-tmp-dir.patch deleted file mode 100644 index 32ac45c13f3ae..0000000000000 --- a/interpreter/CppInterOp/patches/llvm/emscripten-clang20-2-shift-temporary-files-to-tmp-dir.patch +++ /dev/null @@ -1,15 +0,0 @@ -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-clang20-3-enable_exception_handling.patch b/interpreter/CppInterOp/patches/llvm/emscripten-clang20-3-enable_exception_handling.patch deleted file mode 100644 index 85e369c2cd369..0000000000000 --- a/interpreter/CppInterOp/patches/llvm/emscripten-clang20-3-enable_exception_handling.patch +++ /dev/null @@ -1,67 +0,0 @@ -diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp -index 3b81f9d70..2704edb8a 100644 ---- a/clang/lib/Interpreter/Interpreter.cpp -+++ b/clang/lib/Interpreter/Interpreter.cpp -@@ -142,6 +142,48 @@ CreateCI(const llvm::opt::ArgStringList &Argv) { - return std::move(Clang); - } - -+static llvm::Error HandleFrontendOptions(const CompilerInstance &CI) { -+ const auto &FrontendOpts = CI.getFrontendOpts(); -+ -+ if (FrontendOpts.ShowHelp) { -+ driver::getDriverOptTable().printHelp( -+ llvm::outs(), "clang -cc1 [options] file...", -+ "LLVM 'Clang' Compiler: http://clang.llvm.org", -+ /*ShowHidden=*/false, /*ShowAllAliases=*/false, -+ llvm::opt::Visibility(driver::options::CC1Option)); -+ return llvm::createStringError(llvm::errc::not_supported, "Help displayed"); -+ } -+ -+ if (FrontendOpts.ShowVersion) { -+ llvm::cl::PrintVersionMessage(); -+ return llvm::createStringError(llvm::errc::not_supported, -+ "Version displayed"); -+ } -+ -+ if (!FrontendOpts.LLVMArgs.empty()) { -+ unsigned NumArgs = FrontendOpts.LLVMArgs.size(); -+ auto Args = std::make_unique(NumArgs + 2); -+ Args[0] = "clang-repl (LLVM option parsing)"; -+ for (unsigned i = 0; i != NumArgs; ++i) { -+ Args[i + 1] = FrontendOpts.LLVMArgs[i].c_str(); -+ // remove the leading '-' from the option name -+ if (Args[i + 1][0] == '-') { -+ auto *option = static_cast *>( -+ llvm::cl::getRegisteredOptions()[Args[i + 1] + 1]); -+ if (option) { -+ option->setInitialValue(true); -+ } else { -+ llvm::errs() << "Unknown LLVM option: " << Args[i + 1] << "\n"; -+ } -+ } -+ } -+ Args[NumArgs + 1] = nullptr; -+ llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); -+ } -+ -+ return llvm::Error::success(); -+} -+ - } // anonymous namespace - - namespace clang { -@@ -456,7 +498,12 @@ const char *const Runtimes = R"( - - llvm::Expected> - Interpreter::create(std::unique_ptr CI) { -- llvm::Error Err = llvm::Error::success(); -+ -+ llvm::Error Err = HandleFrontendOptions(*CI); -+ if (Err) { -+ return std::move(Err); -+ } -+ - auto Interp = - std::unique_ptr(new Interpreter(std::move(CI), Err)); - if (Err) 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 deleted file mode 100644 index 32ac45c13f3ae..0000000000000 --- a/interpreter/CppInterOp/patches/llvm/emscripten-clang21-1-shift-temporary-files-to-tmp-dir.patch +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 9a57cec72ee3d..0000000000000 --- a/interpreter/CppInterOp/patches/llvm/emscripten-clang21-2-enable_exception_handling.patch +++ /dev/null @@ -1,100 +0,0 @@ -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 deleted file mode 100644 index 100e07f349d40..0000000000000 --- a/interpreter/CppInterOp/patches/llvm/emscripten-clang21-3-webassembly_target_machine_reordering.patch +++ /dev/null @@ -1,14 +0,0 @@ -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 057e8f6781915..77ad000d5f151 100644 --- a/interpreter/CppInterOp/unittests/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CMakeLists.txt @@ -4,11 +4,7 @@ enable_testing() # LLVM builds (not installed llvm) provides gtest. if (NOT TARGET GTest::gtest AND NOT TARGET gtest) - find_package(GTest) - if (NOT GTest_FOUND) - message(WARNING "CppInterOp could not find GTest. Provide the package or set CPPINTEROP_ENABLE_TESTING=OFF to disable this warning.") - return() - endif() + include(GoogleTest) endif() if(EMSCRIPTEN) @@ -34,18 +30,10 @@ add_dependencies(CppInterOpUnitTests clangCppInterOp) set (TIMEOUT_VALUE 2400) function(add_cppinterop_unittest name) - cmake_parse_arguments(ARG - "ENABLE_DISPATCH" - "" - "" - ${ARGN} - ) - # gtest_main is dropped from the Emscripten link set, so every Emscripten - # test binary needs its own main.cpp entry point. if(EMSCRIPTEN) - add_executable(${name} EXCLUDE_FROM_ALL ${ARG_UNPARSED_ARGUMENTS} main.cpp) + add_executable(${name} EXCLUDE_FROM_ALL ${ARGN} main.cpp) else() - add_executable(${name} EXCLUDE_FROM_ALL ${ARG_UNPARSED_ARGUMENTS}) + add_executable(${name} EXCLUDE_FROM_ALL ${ARGN}) endif() if(NOT LLVM_ENABLE_RTTI) if(MSVC) @@ -55,6 +43,10 @@ function(add_cppinterop_unittest name) endif() endif() add_dependencies(CppInterOpUnitTests ${name}) + # Ensure .inc files are generated before test compilation starts. + if(TARGET CppInterOpTableGen) + add_dependencies(${name} CppInterOpTableGen) + endif() target_include_directories(${name} PRIVATE ${CMAKE_CURRENT_BINARY_DIR} ${GTEST_INCLUDE_DIR}) set_property(TARGET ${name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) export_executable_symbols(${name}) @@ -89,16 +81,6 @@ function(add_cppinterop_unittest name) set_tests_properties(cppinterop-${name} PROPERTIES TIMEOUT "${TIMEOUT_VALUE}" ENVIRONMENT "CPLUS_INCLUDE_PATH=${CMAKE_BINARY_DIR}/etc") - # 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/CAPITest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/CAPITest.cpp new file mode 100644 index 0000000000000..1da0adcda5e2a --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/CAPITest.cpp @@ -0,0 +1,299 @@ +// Tests for the generated C API wrappers (cppinterop_* functions). +// Each test verifies the C wrapper forwards correctly to Cpp::. + +#include "Utils.h" + +#include "gtest/gtest.h" + +#include +#include + +// Pull in the generated C declarations. +extern "C" { +#include "CppInterOp/CXCppInterOpDecl.inc" + +// Hand-written wrappers not in the .inc file. +CPPINTEROP_API Cpp::CppInterOpArray +cppinterop_GetClassTemplatedMethods(const char* name, void* parent); +} + +using namespace TestUtils; + +TYPED_TEST(CppInterOpTest, CAPI_DeclareAndProcess) { + TestFixture::CreateInterpreter(); + EXPECT_EQ(0, cppinterop_Declare("int capi_var = 42;", false)); + EXPECT_EQ(0, cppinterop_Process("capi_var++;")); +} + +TYPED_TEST(CppInterOpTest, CAPI_ScopeQueries) { + TestFixture::CreateInterpreter(); + Cpp::Declare("namespace CAPINs { class Foo {}; }"); + + auto* global = cppinterop_GetGlobalScope(); + EXPECT_NE(global, nullptr); + + auto* ns = cppinterop_GetScope("CAPINs", nullptr); + EXPECT_NE(ns, nullptr); + EXPECT_TRUE(cppinterop_IsNamespace(ns)); + EXPECT_FALSE(cppinterop_IsClass(ns)); + + auto* foo = cppinterop_GetScope("Foo", ns); + EXPECT_NE(foo, nullptr); + EXPECT_TRUE(cppinterop_IsClass(foo)); + EXPECT_TRUE(cppinterop_IsComplete(foo)); + EXPECT_GT(cppinterop_SizeOf(foo), 0U); + + auto* parent = cppinterop_GetParentScope(foo); + EXPECT_EQ(parent, ns); + + char* name = cppinterop_GetName(foo); + EXPECT_STREQ(name, "Foo"); + free(name); // NOLINT +} + +TYPED_TEST(CppInterOpTest, CAPI_TypeQueries) { + TestFixture::CreateInterpreter(); + Cpp::Declare("class CAPIType { int x; };"); + + auto* scope = cppinterop_GetNamed("CAPIType", nullptr); + auto* type = cppinterop_GetTypeFromScope(scope); + EXPECT_NE(type, nullptr); + + auto* back = cppinterop_GetScopeFromType(type); + EXPECT_EQ(back, scope); + + EXPECT_FALSE(cppinterop_IsPointerType(type)); + EXPECT_FALSE(cppinterop_IsReferenceType(type)); + EXPECT_FALSE(cppinterop_IsEnumScope(scope)); + EXPECT_FALSE(cppinterop_IsBuiltin(type)); + EXPECT_FALSE(cppinterop_IsAbstract(scope)); + EXPECT_TRUE(cppinterop_IsPODType(type)); + EXPECT_GT(cppinterop_GetSizeOfType(type), 0U); +} + +TYPED_TEST(CppInterOpTest, CAPI_FunctionQueries) { + TestFixture::CreateInterpreter(); + Cpp::Declare("int capi_add(int a, int b) { return a + b; }"); + + auto* fn = cppinterop_GetNamed("capi_add", nullptr); + EXPECT_NE(fn, nullptr); + EXPECT_TRUE(cppinterop_IsFunction(fn)); + EXPECT_FALSE(cppinterop_IsMethod(fn)); + EXPECT_EQ(cppinterop_GetFunctionNumArgs(fn), 2U); + EXPECT_EQ(cppinterop_GetFunctionRequiredArgs(fn), 2U); + + auto* ret = cppinterop_GetFunctionReturnType(fn); + EXPECT_NE(ret, nullptr); + + char* sig = cppinterop_GetFunctionSignature(fn); + EXPECT_NE(sig, nullptr); + EXPECT_TRUE(strstr(sig, "capi_add") != nullptr); + free(sig); // NOLINT +} + +TYPED_TEST(CppInterOpTest, CAPI_VariableQueries) { + TestFixture::CreateInterpreter(); + Cpp::Declare("namespace CAPIVarNs { int gvar = 99; }"); + + auto* ns = cppinterop_GetNamed("CAPIVarNs", nullptr); + auto* var = cppinterop_GetNamed("gvar", ns); + EXPECT_NE(var, nullptr); + EXPECT_TRUE(cppinterop_IsVariable(var)); +} + +TYPED_TEST(CppInterOpTest, CAPI_EnumQueries) { + TestFixture::CreateInterpreter(); + Cpp::Declare("enum CAPIEnum { A, B, C };"); + + auto* e = cppinterop_GetNamed("CAPIEnum", nullptr); + EXPECT_TRUE(cppinterop_IsEnumScope(e)); +} + +TYPED_TEST(CppInterOpTest, CAPI_TemplateQueries) { + TestFixture::CreateInterpreter(); + Cpp::Declare("template struct CAPITmpl { T val; };"); + + auto* tmpl = cppinterop_GetNamed("CAPITmpl", nullptr); + EXPECT_TRUE(cppinterop_IsTemplate(tmpl)); + EXPECT_FALSE(cppinterop_IsTemplateSpecialization(tmpl)); +} + +TYPED_TEST(CppInterOpTest, CAPI_ClassMemberQueries) { + TestFixture::CreateInterpreter(); + Cpp::Declare(R"( + class CAPICls { + public: + int pub_var; + void pub_fn() {} + virtual void virt_fn() {} + static void static_fn() {} + }; + )"); + + auto* cls = cppinterop_GetNamed("CAPICls", nullptr); + EXPECT_TRUE(cppinterop_HasDefaultConstructor(cls)); + EXPECT_TRUE(cppinterop_IsClassPolymorphic(cls)); + + auto* dtor = cppinterop_GetDestructor(cls); + EXPECT_NE(dtor, nullptr); +} + +TYPED_TEST(CppInterOpTest, CAPI_Construct) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscripten builds"; +#endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + std::vector args = {"-include", "new"}; + TestFixture::CreateInterpreter(args); + Cpp::Declare("struct CAPIObj { int x = 7; };"); + + auto* scope = cppinterop_GetNamed("CAPIObj", nullptr); + auto* obj = cppinterop_Construct(scope, nullptr, 1); + EXPECT_NE(obj, nullptr); + cppinterop_Destruct(obj, scope, true, 0); +} + +TYPED_TEST(CppInterOpTest, CAPI_EnumFunctions) { + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Evaluate not supported in OOP JIT"; + TestFixture::CreateInterpreter(); + Cpp::Declare("const int ci = 42;"); + + auto* scope = cppinterop_GetNamed("ci", nullptr); + auto* type = cppinterop_GetVariableType(scope); + + // HasTypeQualifier takes QualKind (unsigned in C). + // QualKind::kConst == 1U + EXPECT_TRUE(cppinterop_HasTypeQualifier(type, /*kConst=*/1U)); + EXPECT_FALSE(cppinterop_HasTypeQualifier(type, /*kVolatile=*/2U)); + + // RemoveTypeQualifier returns the unqualified type. + auto* unqual = cppinterop_RemoveTypeQualifier(type, /*kConst=*/1U); + EXPECT_FALSE(cppinterop_HasTypeQualifier(unqual, /*kConst=*/1U)); + + // GetLanguage returns an InterpreterLanguage enum as unsigned char. + // CXX == 5 (Unknown=0, Asm=1, CIR=2, LLVM_IR=3, C=4, CXX=5) + EXPECT_EQ(cppinterop_GetLanguage(nullptr), 5); + + // Evaluate with bool* HadError. + bool HadError = false; + EXPECT_EQ(cppinterop_Evaluate("42", &HadError), 42); + EXPECT_FALSE(HadError); + // Evaluate error path tested in Interpreter_DeclareSilent; not + // duplicated here to avoid LLVM 22 crash and Windows MSBuild issues. +} + +TYPED_TEST(CppInterOpTest, CAPI_CollectionReturn) { + TestFixture::CreateInterpreter(); + Cpp::Declare("enum CAPIColEnum { X, Y, Z };"); + + auto* e = cppinterop_GetNamed("CAPIColEnum", nullptr); + Cpp::CppInterOpArray constants = cppinterop_GetEnumConstants(e); + EXPECT_EQ(constants.size, 3U); + EXPECT_NE(constants.data, nullptr); + cppinterop_DisposeArray(constants); +} + +TYPED_TEST(CppInterOpTest, CAPI_CollectionOutParam) { + TestFixture::CreateInterpreter(); + Cpp::Declare(R"( + struct CAPIMembers { + int a; + double b; + static int c; + }; + )"); + + auto* cls = cppinterop_GetNamed("CAPIMembers", nullptr); + Cpp::CppInterOpArray members = cppinterop_GetDatamembers(cls); + EXPECT_EQ(members.size, 2U); // a and b (not static c) + cppinterop_DisposeArray(members); + + Cpp::CppInterOpArray statics = cppinterop_GetStaticDatamembers(cls); + EXPECT_EQ(statics.size, 1U); // only c + cppinterop_DisposeArray(statics); +} + +TYPED_TEST(CppInterOpTest, CAPI_StringCollectionOutParam) { + TestFixture::CreateInterpreter(); + Cpp::Declare("namespace CAPIStrNs { enum E1 { P, Q }; enum E2 { R }; }"); + + auto* ns = cppinterop_GetNamed("CAPIStrNs", nullptr); + Cpp::CppInterOpStringArray enums = cppinterop_GetEnums(ns); + EXPECT_EQ(enums.size, 2U); + // Verify the enum names are present (order may vary). + bool foundE1 = false, foundE2 = false; + for (size_t i = 0; i < enums.size; ++i) { + if (strcmp(enums.data[i], "E1") == 0) + foundE1 = true; + if (strcmp(enums.data[i], "E2") == 0) + foundE2 = true; + } + EXPECT_TRUE(foundE1); + EXPECT_TRUE(foundE2); + cppinterop_DisposeStringArray(enums); + + // Disposing an empty array should not crash. + Cpp::CppInterOpArray empty = {nullptr, 0}; + cppinterop_DisposeArray(empty); +} + +TYPED_TEST(CppInterOpTest, CAPI_CreateInterpreterInParam) { + // Test the vector in-param pattern: CreateInterpreter(ptr, size, ptr, size). + const char* args[] = {"-std=c++17"}; + auto* I = cppinterop_CreateInterpreter(args, 1, nullptr, 0); + EXPECT_NE(I, nullptr); +} + +TYPED_TEST(CppInterOpTest, CAPI_GetClassTemplatedMethods) { + TestFixture::CreateInterpreter(); + Cpp::Declare(R"( + struct CAPITmplMethods { + template T get() { return T{}; } + template void set(T) {} + void plain() {} + }; + )"); + + auto* cls = cppinterop_GetNamed("CAPITmplMethods", nullptr); + ASSERT_NE(cls, nullptr); + + // "get" has templated methods. + Cpp::CppInterOpArray gets = cppinterop_GetClassTemplatedMethods("get", cls); + EXPECT_EQ(gets.size, 1U); + cppinterop_DisposeArray(gets); + + // "set" has templated methods. + Cpp::CppInterOpArray sets = cppinterop_GetClassTemplatedMethods("set", cls); + EXPECT_EQ(sets.size, 1U); + cppinterop_DisposeArray(sets); + + // "plain" is not templated — should return empty. + Cpp::CppInterOpArray plains = + cppinterop_GetClassTemplatedMethods("plain", cls); + EXPECT_EQ(plains.size, 0U); + cppinterop_DisposeArray(plains); + + // Non-existent method — should return empty. + Cpp::CppInterOpArray none = + cppinterop_GetClassTemplatedMethods("nonexistent", cls); + EXPECT_EQ(none.size, 0U); + cppinterop_DisposeArray(none); +} + +TYPED_TEST(CppInterOpTest, CAPI_StringFunctions) { + TestFixture::CreateInterpreter(); + + char* version = cppinterop_GetVersion(); + EXPECT_NE(version, nullptr); + EXPECT_NE(version[0], '\0'); + free(version); // NOLINT + +#ifndef _WIN32 + // Itanium mangling; Windows uses MSVC mangling. + char* demangled = cppinterop_Demangle("_Z3foov"); + EXPECT_STREQ(demangled, "foo()"); + free(demangled); // NOLINT +#endif +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CAPITestC.c b/interpreter/CppInterOp/unittests/CppInterOp/CAPITestC.c new file mode 100644 index 0000000000000..02d4f474a638a --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/CAPITestC.c @@ -0,0 +1,53 @@ +/* Pure C compilation test for the generated cppinterop_* C API. + * This file verifies that CppInterOpTypes.h and ALL generated function + * declarations in CXCppInterOp.inc are valid C. If any generated + * signature uses a C++-only type, this file will fail to compile. + */ + +#include "CppInterOp/CppInterOpTypes.h" + +/* Pull in ALL generated C declarations. Every generated function + * signature is compiled as C here — this is the primary check. + */ +#include "CppInterOp/CXCppInterOpDecl.inc" + +/* Verify the C-compatible structs work without the 'struct' keyword + * (thanks to the typedef). + */ +static void test_types(void) { + CppInterOpArray arr; + arr.data = (void**)0; + arr.size = 0; + (void)arr; + + CppInterOpStringArray sarr; + sarr.data = (char**)0; + sarr.size = 0; + (void)sarr; + + TemplateArgInfo tai; + tai.m_Type = (void*)0; + tai.m_IntegralValue = (const char*)0; + (void)tai; +} + +/* Take the address of representative C API functions to verify they + * are valid C symbols. The full set is already compiled above; these + * confirm linkability at the object level. + */ +int capi_c_test_main(void) { + test_types(); + (void)&cppinterop_CreateInterpreter; + (void)&cppinterop_Declare; + (void)&cppinterop_Process; + (void)&cppinterop_IsClass; + (void)&cppinterop_GetName; + (void)&cppinterop_GetEnumConstants; + (void)&cppinterop_DisposeArray; + (void)&cppinterop_GetEnums; + (void)&cppinterop_DisposeStringArray; + (void)&cppinterop_InstantiateTemplate; + (void)&cppinterop_HasTypeQualifier; + (void)&cppinterop_GetLanguage; + return 0; +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt index 6a50260279fad..09703a09fe6c7 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt @@ -43,7 +43,7 @@ if (EMSCRIPTEN) endif() add_cppinterop_unittest(CppInterOpTests - ENABLE_DISPATCH + CAPITest.cpp CommentReflectionTest.cpp EnumReflectionTest.cpp FunctionReflectionTest.cpp @@ -65,6 +65,20 @@ if(NOT WIN32) ) endif() +# CAPITestC.c is a pure C compilation test. Build it as a separate object +# library so it does not inherit C++-only flags (-Wno-deprecated-copy, +# -fno-rtti) that GCC's C frontend rejects under -Werror. +add_library(CAPITestC OBJECT CAPITestC.c) +target_compile_options(CAPITestC PRIVATE + $<$>:-Werror -Wall -Wno-unused-command-line-argument> + $<$:/WX /W3>) +target_include_directories(CAPITestC PRIVATE + ${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include) +if(TARGET CppInterOpTableGen) + add_dependencies(CAPITestC CppInterOpTableGen) +endif() +target_link_libraries(CppInterOpTests PRIVATE CAPITestC) + set_source_files_properties(InterpreterTest.cpp PROPERTIES COMPILE_DEFINITIONS "LLVM_BINARY_DIR=\"${LLVM_BINARY_DIR}\"" ) @@ -85,12 +99,9 @@ add_cppinterop_unittest(TracingTests TracingTests.cpp Utils.cpp ) - -set_property(TEST cppinterop-TracingTests APPEND PROPERTY - ENVIRONMENT "GTEST_DEATH_TEST_STYLE=threadsafe") - target_compile_definitions(TracingTests PRIVATE "CPPINTEROP_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../\"" + "CPPINTEROP_BINARY_DIR=\"${CMAKE_CURRENT_BINARY_DIR}/../../\"" ) # GCC 12 and (less often) 13/14 emit a -Wmaybe-uninitialized false positive # inside libstdc++'s headers when the std::function subobject of @@ -105,3 +116,72 @@ endif() add_subdirectory(TestSharedLib) add_dependencies(DynamicLibraryManagerTests TestSharedLib) + +# Downstream-DSO dlopen check that mirrors libcppyy-backend's contract: +# a SHARED lib using without linking against +# clangCppInterOp, dlopen'd by a standalone exec that also doesn't link. +# Any inline-header UND ref that the host lib can't satisfy at runtime +# trips here before the cppyy integration job. +if(NOT EMSCRIPTEN AND BUILD_SHARED_LIBS) + add_subdirectory(TestDownstreamLib) + + set(_downstream_lib_path + "${CMAKE_CURRENT_BINARY_DIR}/bin/$/${CMAKE_SHARED_LIBRARY_PREFIX}TestDownstreamLib${CMAKE_SHARED_LIBRARY_SUFFIX}") + + add_executable(TestDownstreamLoader EXCLUDE_FROM_ALL TestDownstreamLoader.cpp) + add_dependencies(CppInterOpUnitTests TestDownstreamLoader TestDownstreamLib) + target_compile_definitions(TestDownstreamLoader PRIVATE + TEST_DOWNSTREAM_LIB_PATH="${_downstream_lib_path}") + if(UNIX AND NOT APPLE) + target_link_libraries(TestDownstreamLoader PRIVATE dl) + endif() + set_output_directory(TestDownstreamLoader + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin/$/ + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin/$/ + ) + add_test(NAME cppinterop-DownstreamLoaderTest + COMMAND TestDownstreamLoader $) + set_tests_properties(cppinterop-DownstreamLoaderTest PROPERTIES + TIMEOUT "${TIMEOUT_VALUE}") +endif() + +# Dispatch Tests. +# Load libclangCppInterOp via dlopen(RTLD_LOCAL) and must NOT link against it +# directly, to verify true symbol isolation. +if(NOT EMSCRIPTEN AND BUILD_SHARED_LIBS) + add_executable(DispatchTests EXCLUDE_FROM_ALL + DispatchInit.cpp + DispatchTest.cpp + DispatchSmokeTest.cpp + ) + add_dependencies(CppInterOpUnitTests DispatchTests) + if(TARGET CppInterOpTableGen) + add_dependencies(DispatchTests CppInterOpTableGen) + endif() + target_include_directories(DispatchTests PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} ${GTEST_INCLUDE_DIR}) + target_link_libraries(DispatchTests PRIVATE ${gtest_libs} ${link_pthreads_lib}) + # Deliberately no target_link_libraries(... clangCppInterOp). + target_compile_definitions(DispatchTests PRIVATE + CPPINTEROP_LIB_PATH="${CMAKE_BINARY_DIR}/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" + ) + set_output_directory(DispatchTests + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin/$/ + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/bin/$/ + ) + add_test(NAME cppinterop-DispatchTests COMMAND DispatchTests) + set_tests_properties(cppinterop-DispatchTests PROPERTIES + TIMEOUT "${TIMEOUT_VALUE}" + ENVIRONMENT "CPLUS_INCLUDE_PATH=${CMAKE_BINARY_DIR}/etc") + # Verify the binary is NOT linked against libclangCppInterOp. If this + # check fails, the dispatch tests are not truly testing RTLD_LOCAL. + if(UNIX AND NOT APPLE) + add_test(NAME cppinterop-DispatchTests-isolation + COMMAND sh -c "! ldd $ | grep -q $" + ) + elseif(APPLE) + add_test(NAME cppinterop-DispatchTests-isolation + COMMAND sh -c "! otool -L $ | grep -q $" + ) + endif() +endif() diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp index 5c6fc0fecd555..0a1d5554c57bd 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp @@ -26,12 +26,12 @@ class CUDATest : public ::testing::Test { if (!I) return; if (Cpp::Declare("__global__ void test_func() {}" - "test_func<<<1,1>>>();" DFLT_FALSE) != 0) + "test_func<<<1,1>>>();") != 0) return; HasSDK = true; - if (Cpp::Declare("cudaDeviceSynchronize();" DFLT_FALSE) != 0) { + if (Cpp::Declare("cudaDeviceSynchronize();") != 0) { Cpp::DeleteInterpreter(I); return; } @@ -102,7 +102,7 @@ TEST_F(CUDATest, CUDAH) { SKIP_IF_NO_SDK; Cpp::CreateInterpreter({}, {"--cuda"}); - EXPECT_EQ(0, Cpp::Declare("#include " DFLT_FALSE)); + EXPECT_EQ(0, Cpp::Declare("#include ")); } TEST_F(CUDATest, CUDARuntime) { @@ -112,7 +112,7 @@ TEST_F(CUDATest, CUDARuntime) { EXPECT_EQ(0, Cpp::Declare(R"( int deviceCount = 0; cudaGetDeviceCount(&deviceCount); - )" DFLT_FALSE)); + )")); bool err = false; intptr_t count = Cpp::Evaluate("deviceCount", &err); EXPECT_FALSE(err); @@ -138,8 +138,7 @@ TEST_F(CUDATest, SimpleKernelExecution) { addOne<<<1,1>>>(d); cudaDeviceSynchronize(); int kernelErr = (int)cudaGetLastError(); - )" DFLT_FALSE)) - << "Failed to declare/launch kernel"; + )")) << "Failed to declare/launch kernel"; bool err = false; int cudaErr = (int)Cpp::Evaluate("kernelErr", &err); @@ -152,7 +151,7 @@ TEST_F(CUDATest, SimpleKernelExecution) { EXPECT_EQ((int)result, 42); } - Cpp::Declare("cudaFree(d);" DFLT_FALSE); + Cpp::Declare("cudaFree(d);"); } // demonstrate incremental CUDA compilation with CUB BlockReduce @@ -180,8 +179,7 @@ TEST_F(CUDATest, CUB_BlockReduceWithDeviceFunction) { if (threadIdx.x == 0) atomicAdd(output, block_sum); } - )" DFLT_FALSE)) - << "Failed to declare kernel"; + )")) << "Failed to declare kernel"; EXPECT_EQ(0, Cpp::Declare(R"( const int N = 256; @@ -195,8 +193,7 @@ TEST_F(CUDATest, CUB_BlockReduceWithDeviceFunction) { sumOfSquaresKernel<<>>(d_in, d_out, N); cudaError_t syncErr = cudaDeviceSynchronize(); cudaError_t lastErr = cudaGetLastError(); - )" DFLT_FALSE)) - << "Failed to launch kernel"; + )")) << "Failed to launch kernel"; bool err = false; EXPECT_EQ((int)Cpp::Evaluate("(int)syncErr", &err), 0); @@ -209,5 +206,5 @@ TEST_F(CUDATest, CUB_BlockReduceWithDeviceFunction) { EXPECT_FALSE(err); EXPECT_EQ((int)result, 5625216); - Cpp::Declare("cudaFree(d_in); cudaFree(d_out);" DFLT_FALSE); + Cpp::Declare("cudaFree(d_in); cudaFree(d_out);"); } diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CommentReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/CommentReflectionTest.cpp index 7550667ed7d16..95d296284b3cb 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/CommentReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/CommentReflectionTest.cpp @@ -1,8 +1,6 @@ #include "Utils.h" #include "CppInterOp/CppInterOp.h" -#include "clang-c/CXCppInterOp.h" -#include "clang-c/CXString.h" #include "gtest/gtest.h" #include @@ -39,21 +37,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, CommentReflection_DoxygenBlockAndLine) { 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) { diff --git a/interpreter/CppInterOp/unittests/CppInterOp/DispatchInit.cpp b/interpreter/CppInterOp/unittests/CppInterOp/DispatchInit.cpp new file mode 100644 index 0000000000000..c47ccd8f1f8da --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/DispatchInit.cpp @@ -0,0 +1,25 @@ +// Storage definitions and initialization for dispatch function pointers. +// Linked only by DispatchTests — not by the normal test suite. + +#include "CppInterOp/Dispatch.h" + +// Define storage for all raw dispatch function pointers. +using namespace CppImpl; +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + Ret(*CppInternal::DispatchRaw::DN) RawTypes = nullptr; +#include "CppInterOp/CppInterOpAPI.inc" + +namespace { +struct DispatchInitializer { + DispatchInitializer() { + if (!Cpp::LoadDispatchAPI(CPPINTEROP_LIB_PATH)) { + std::abort(); + } + } +}; +DispatchInitializer& GetDispatchInitializer() { + static DispatchInitializer instance; + return instance; +} +const DispatchInitializer& g_dispatch_init = GetDispatchInitializer(); +} // namespace diff --git a/interpreter/CppInterOp/unittests/CppInterOp/DispatchSmokeTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/DispatchSmokeTest.cpp new file mode 100644 index 0000000000000..63924449c64df --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/DispatchSmokeTest.cpp @@ -0,0 +1,241 @@ +#include "CppInterOp/Dispatch.h" + +#include "gtest/gtest.h" + +#include +#include +#include +#include + +// Smoke tests that verify the dispatch wrappers forward correctly for +// representative API functions across all categories. The full test +// suite runs against the direct API; these confirm the dispatch layer +// doesn't introduce regressions. + +// --- Interpreter --- + +TEST(DispatchSmokeTest, CreateAndDeleteInterpreter) { + auto* I = Cpp::CreateInterpreter({}); + EXPECT_NE(I, nullptr); + EXPECT_TRUE(Cpp::DeleteInterpreter()); +} + +TEST(DispatchSmokeTest, DeclareAndProcess) { + Cpp::CreateInterpreter({}); + EXPECT_EQ(0, Cpp::Declare("int dispatch_x = 42;")); + EXPECT_EQ(0, Cpp::Process("dispatch_x++;")); +} + +// --- Scope queries --- + +TEST(DispatchSmokeTest, ScopeLookup) { + Cpp::CreateInterpreter({}); + Cpp::Declare("namespace DispNS { class Foo {}; }"); + + auto* global = Cpp::GetGlobalScope(); + EXPECT_NE(global, nullptr); + + auto* ns = Cpp::GetNamed("DispNS"); + EXPECT_NE(ns, nullptr); + EXPECT_TRUE(Cpp::IsNamespace(ns)); + + auto* foo = Cpp::GetScope("Foo", ns); + EXPECT_NE(foo, nullptr); + EXPECT_TRUE(Cpp::IsClass(foo)); + EXPECT_FALSE(Cpp::IsNamespace(foo)); +} + +// --- Type queries --- + +TEST(DispatchSmokeTest, TypeReflection) { + Cpp::CreateInterpreter({}); + Cpp::Declare("class DispType { int x; };"); + + auto* scope = Cpp::GetNamed("DispType"); + auto* type = Cpp::GetTypeFromScope(scope); + EXPECT_NE(type, nullptr); + + auto* scope_back = Cpp::GetScopeFromType(type); + EXPECT_EQ(scope, scope_back); + + EXPECT_GT(Cpp::SizeOf(scope), 0U); + EXPECT_FALSE(Cpp::IsPointerType(type)); + EXPECT_FALSE(Cpp::IsEnumType(type)); +} + +// --- Function reflection --- + +TEST(DispatchSmokeTest, FunctionReflection) { + Cpp::CreateInterpreter({}); + Cpp::Declare("namespace DispFn { int add(int a, int b) { return a+b; } }"); + + auto* ns = Cpp::GetNamed("DispFn"); + auto fns = Cpp::GetFunctionsUsingName(ns, "add"); + EXPECT_EQ(fns.size(), 1U); + + EXPECT_EQ(Cpp::GetFunctionNumArgs(fns[0]), 2U); + EXPECT_EQ(Cpp::GetName(fns[0]), "add"); +} + +// --- Variable reflection --- + +TEST(DispatchSmokeTest, VariableReflection) { + Cpp::CreateInterpreter({}); + Cpp::Declare("namespace DispVar { int gval = 99; }"); + + auto* ns = Cpp::GetNamed("DispVar"); + // GetNamed finds variables inside a namespace scope. + auto* var = Cpp::GetNamed("gval", ns); + EXPECT_NE(var, nullptr); + EXPECT_TRUE(Cpp::IsVariable(var)); +} + +// --- Templates --- + +TEST(DispatchSmokeTest, TemplateInstantiation) { + Cpp::CreateInterpreter({}); + Cpp::Declare("template struct DispTmpl { T val; };"); + + auto* tmpl = Cpp::GetNamed("DispTmpl"); + EXPECT_TRUE(Cpp::IsTemplate(tmpl)); +} + +// --- Construct / Destruct --- + +TEST(DispatchSmokeTest, ConstructDestruct) { + Cpp::CreateInterpreter({"-include", "new"}); + Cpp::Declare("struct DispObj { int x = 7; };"); + + auto* scope = Cpp::GetNamed("DispObj"); + EXPECT_TRUE(Cpp::IsComplete(scope)); + + auto* obj = Cpp::Construct(scope); + EXPECT_NE(obj, nullptr); + Cpp::Destruct(obj, scope, /*withFree=*/true); +} + +// --- Enum --- + +TEST(DispatchSmokeTest, EnumReflection) { + Cpp::CreateInterpreter({}); + Cpp::Declare("enum DispEnum { A, B, C };"); + + auto* e = Cpp::GetNamed("DispEnum"); + EXPECT_TRUE(Cpp::IsEnumScope(e)); + + auto constants = Cpp::GetEnumConstants(e); + EXPECT_EQ(constants.size(), 3U); +} + +// --- Demangle --- + +TEST(DispatchSmokeTest, Demangle) { + Cpp::CreateInterpreter({}); + // _Z3foov is the mangled name for "foo()" + auto result = Cpp::Demangle("_Z3foov"); + EXPECT_EQ(result, "foo()"); +} + +// --- Version --- + +TEST(DispatchSmokeTest, GetVersion) { + auto ver = Cpp::GetVersion(); + EXPECT_FALSE(ver.empty()); +} + +// --- C API through dlsym --- +// The cppinterop_* functions are extern "C" symbols exported from +// libclangCppInterOp. A real C consumer would dlopen the library and +// dlsym them directly — that is what these tests verify. They open a +// separate handle (RTLD_LOCAL) to prove the symbols are accessible +// without linking the library. + +// NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + +namespace { +/// RAII wrapper for a dlopen handle used by the C API dispatch tests. +class DlHandle { + void* H = nullptr; + +public: + DlHandle(const DlHandle&) = delete; + DlHandle& operator=(const DlHandle&) = delete; + explicit DlHandle(const char* path) { +#ifndef _WIN32 + H = dlopen(path, RTLD_LOCAL | RTLD_NOW); +#endif + } + ~DlHandle() { +#ifndef _WIN32 + if (H) + dlclose(H); +#endif + } + void* sym(const char* name) const { +#ifndef _WIN32 + return H ? dlsym(H, name) : nullptr; +#else + return nullptr; +#endif + } + explicit operator bool() const { return H != nullptr; } +}; +} // namespace + +TEST(DispatchSmokeTest, CAPI_ScalarFunctions) { + Cpp::CreateInterpreter({}); + Cpp::Declare("namespace DispCAPI { class Cls {}; int var = 1; }"); + + DlHandle lib(CPPINTEROP_LIB_PATH); + ASSERT_TRUE(lib) << "Failed to dlopen " << CPPINTEROP_LIB_PATH; + + using IsClassFn = bool (*)(void*); + using GetNameFn = char* (*)(void*); + using GetNamedFn = void* (*)(const char*, void*); + + auto* isClass = reinterpret_cast(lib.sym("cppinterop_IsClass")); + auto* getName = reinterpret_cast(lib.sym("cppinterop_GetName")); + auto* getNamed = reinterpret_cast(lib.sym("cppinterop_GetNamed")); + ASSERT_NE(isClass, nullptr); + ASSERT_NE(getName, nullptr); + ASSERT_NE(getNamed, nullptr); + + auto* cls = getNamed("DispCAPI", nullptr); + EXPECT_FALSE(isClass(cls)); // namespace, not class + + auto* inner = getNamed("Cls", cls); + EXPECT_TRUE(isClass(inner)); + + char* name = getName(inner); + EXPECT_STREQ(name, "Cls"); + free(name); // NOLINT +} + +TEST(DispatchSmokeTest, CAPI_CollectionFunctions) { + Cpp::CreateInterpreter({}); + Cpp::Declare("enum DispCAPIEnum { DA, DB, DC };"); + + DlHandle lib(CPPINTEROP_LIB_PATH); + ASSERT_TRUE(lib) << "Failed to dlopen " << CPPINTEROP_LIB_PATH; + + using GetNamedFn = void* (*)(const char*, void*); + using GetEnumConstantsFn = Cpp::CppInterOpArray (*)(void*); + using DisposeArrayFn = void (*)(Cpp::CppInterOpArray); + + auto* getNamed = reinterpret_cast(lib.sym("cppinterop_GetNamed")); + auto* getEnumConstants = reinterpret_cast( + lib.sym("cppinterop_GetEnumConstants")); + auto* disposeArray = + reinterpret_cast(lib.sym("cppinterop_DisposeArray")); + ASSERT_NE(getNamed, nullptr); + ASSERT_NE(getEnumConstants, nullptr); + ASSERT_NE(disposeArray, nullptr); + + auto* e = getNamed("DispCAPIEnum", nullptr); + auto arr = getEnumConstants(e); + EXPECT_EQ(arr.size, 3U); + EXPECT_NE(arr.data, nullptr); + disposeArray(arr); +} + +// NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) diff --git a/interpreter/CppInterOp/unittests/CppInterOp/DispatchTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/DispatchTest.cpp index 13a313b29f781..32c47f04c3555 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/DispatchTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/DispatchTest.cpp @@ -1,86 +1,56 @@ -#include "Utils.h" - #include "CppInterOp/Dispatch.h" -#include "clang/AST/Decl.h" -#include "clang/AST/GlobalDecl.h" - #include "gtest/gtest.h" -#include +#include -using namespace TestUtils; -using namespace llvm; -using namespace clang; +// These tests verify the dispatch mechanism itself (dlGetProcAddress, +// LoadDispatchAPI, UnloadDispatchAPI). They must NOT link against +// libclangCppInterOp to ensure true RTLD_LOCAL isolation. // 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); +TEST(DispatchTest, DlGetProcAddress_Basic) { + Cpp::CreateInterpreter({}); + Cpp::Declare("namespace N {} class C{}; int I;"); - 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); + using IsClassFn_t = bool (*)(Cpp::TCppScope_t); + auto* IsClassFn = reinterpret_cast(dlGetProcAddress("IsClass")); + EXPECT_NE(IsClassFn, nullptr) << "Failed to obtain API function pointer"; - auto DemangleFn = - reinterpret_cast(CppGetProcAddress("Demangle")); - auto GetQualifiedCompleteNameFn = - reinterpret_cast( - CppGetProcAddress("GetQualifiedCompleteName")); + auto* ns = Cpp::GetNamed("N"); + auto* cls = Cpp::GetNamed("C"); + auto* var = Cpp::GetNamed("I"); - std::string demangled_add_int = DemangleFn(mangled_add_int); - std::string demangled_add_double = DemangleFn(mangled_add_double); + EXPECT_FALSE(IsClassFn(ns)); + EXPECT_TRUE(IsClassFn(cls)); + EXPECT_FALSE(IsClassFn(var)); +} - EXPECT_NE(demangled_add_int.find(GetQualifiedCompleteNameFn(Decls[0])), - std::string::npos); - EXPECT_NE(demangled_add_double.find(GetQualifiedCompleteNameFn(Decls[1])), - std::string::npos); +TEST(DispatchTest, DlGetProcAddress_Unimplemented) { + // dlGetProcAddress finds the library, dlsym's CppGetProcAddress, and calls + // it with the unknown name. CppGetProcAddress returns nullptr and prints + // a diagnostic to stderr. + testing::internal::CaptureStderr(); + void* ptr = dlGetProcAddress("NonExistentFunction"); + std::string err = testing::internal::GetCapturedStderr(); + EXPECT_FALSE(err.empty()); + EXPECT_EQ(ptr, nullptr); } -TEST(CPPINTEROP_TEST_MODE, DispatchAPI_LoadUnloadCycle) { - Cpp::UnloadDispatchAPI(); // make sure we're no loaded already... +TEST(DispatchTest, LoadUnloadCycle) { + Cpp::UnloadDispatchAPI(); // make sure we're not loaded already... EXPECT_FALSE(Cpp::LoadDispatchAPI("some/random/invalid/directory.so")); - Cpp::UnloadDispatchAPI(); // should reset for next load to be successfull + Cpp::UnloadDispatchAPI(); // should reset for next load to be successful EXPECT_TRUE(Cpp::LoadDispatchAPI(CPPINTEROP_LIB_PATH)); Cpp::UnloadDispatchAPI(); // should reset for next load to fail EXPECT_FALSE(Cpp::LoadDispatchAPI("some/other/random/invalid/directory.so")); // NOTE: minimize side-effects: reload assuming the static set is still - // and other test may depend of this being loaded + // and other test may depend on this being loaded EXPECT_TRUE(Cpp::LoadDispatchAPI(CPPINTEROP_LIB_PATH)); } + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) diff --git a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp index a53949bd6e7af..8b0771a7793d5 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -7,10 +7,9 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Sema/Sema.h" +#include #include -#include "clang-c/CXCppInterOp.h" - #include "gtest/gtest.h" #include @@ -161,20 +160,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetClassMethods) { EXPECT_EQ(get_method_name(templ_methods2[5]), "inline TT &TT::operator=(TT &&)"); EXPECT_EQ(get_method_name(templ_methods2[6]), "inline TT::~TT()"); - - // C API - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - auto C_API_SHIM = [&](Cpp::TCppFunction_t method) { - auto Str = clang_getFunctionSignature( - make_scope(static_cast(method), I)); - auto Res = std::string(get_c_string(Str)); - dispose_string(Str); - return Res; - }; - EXPECT_EQ(C_API_SHIM(methods0[0]), "int A::f1(int a, int b)"); - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); } TYPED_TEST(CPPINTEROP_TEST_MODE, @@ -232,15 +217,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_HasDefaultConstructor) { EXPECT_TRUE(Cpp::HasDefaultConstructor(Decls[0])); EXPECT_TRUE(Cpp::HasDefaultConstructor(Decls[1])); EXPECT_FALSE(Cpp::HasDefaultConstructor(Decls[3])); - - // C API - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - EXPECT_TRUE(clang_hasDefaultConstructor(make_scope(Decls[0], I))); - EXPECT_TRUE(clang_hasDefaultConstructor(make_scope(Decls[1], I))); - EXPECT_FALSE(clang_hasDefaultConstructor(make_scope(Decls[3], I))); - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetDestructor) { @@ -269,14 +245,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetDestructor) { EXPECT_TRUE(DeletedDtor); EXPECT_TRUE(Cpp::IsFunctionDeleted(DeletedDtor)); EXPECT_FALSE(Cpp::GetDestructor(Decls[3])); - - // C API - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - EXPECT_TRUE(clang_getDestructor(make_scope(Decls[0], I)).data[0]); - EXPECT_TRUE(clang_getDestructor(make_scope(Decls[1], I)).data[0]); - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionsUsingName) { @@ -490,8 +458,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionReturnType) { std::vector args2 = {C.DoubleTy.getAsOpaquePtr()}; EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Cpp::GetNamed( - "func", Cpp::InstantiateTemplate(Decls[15], args2.data(), - 1 DFLT_FALSE)))), + "func", Cpp::InstantiateTemplate(Decls[15], args2)))), "double"); } @@ -589,6 +556,48 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgType) { EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[2], 0)), "NULL TYPE"); } +TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_FunctionTypes) { + std::vector Decls; + std::string code = R"( + typedef int myint; + int (*f1)(int, double) = nullptr; + void f2(int (&f)(myint, double)) {} + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Decls.size(), 3); + + Cpp::TCppType_t typ1 = Cpp::GetVariableType(Decls[1]); + EXPECT_TRUE(typ1); + EXPECT_FALSE(Cpp::IsFunctionProtoType(typ1)); + EXPECT_TRUE(Cpp::IsFunctionProtoType(Cpp::GetPointeeType(typ1))); + + typ1 = Cpp::GetPointeeType(typ1); + EXPECT_TRUE(typ1); + + std::vector sig; + Cpp::GetFnTypeSignature(typ1, sig); + EXPECT_EQ(sig.size(), 3); + EXPECT_EQ(Cpp::GetTypeAsString(sig[0]), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(sig[1]), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(sig[2]), "double"); + + Cpp::TCppType_t typ2 = Cpp::GetFunctionArgType(Decls[2], 0); + EXPECT_TRUE(typ1); + + typ2 = Cpp::GetNonReferenceType(typ2); + EXPECT_TRUE(typ2); + + sig.clear(); + Cpp::GetFnTypeSignature(typ2, sig); + EXPECT_EQ(sig.size(), 3); + EXPECT_EQ(Cpp::GetTypeAsString(sig[0]), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(sig[1]), "myint"); + EXPECT_EQ(Cpp::GetTypeAsString(sig[2]), "double"); + + EXPECT_TRUE(Cpp::IsSameType(typ1, typ2)); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionSignature) { std::vector Decls; std::string code = R"( @@ -660,17 +669,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsTemplatedFunction) { EXPECT_FALSE(Cpp::IsTemplatedFunction(Decls[3])); EXPECT_FALSE(Cpp::IsTemplatedFunction(SubDeclsC1[1])); EXPECT_TRUE(Cpp::IsTemplatedFunction(SubDeclsC1[2])); - - // C API - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - EXPECT_FALSE(clang_isTemplatedFunction(make_scope(Decls[0], I))); - EXPECT_TRUE(clang_isTemplatedFunction(make_scope(Decls[1], I))); - EXPECT_FALSE(clang_isTemplatedFunction(make_scope(Decls[3], I))); - EXPECT_FALSE(clang_isTemplatedFunction(make_scope(SubDeclsC1[1], I))); - EXPECT_TRUE(clang_isTemplatedFunction(make_scope(SubDeclsC1[2], I))); - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ExistsFunctionTemplate) { @@ -691,20 +689,10 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ExistsFunctionTemplate) { EXPECT_TRUE(Cpp::ExistsFunctionTemplate("f", 0)); EXPECT_TRUE(Cpp::ExistsFunctionTemplate("f", Decls[1])); EXPECT_FALSE(Cpp::ExistsFunctionTemplate("f", Decls[2])); - - // C API - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - EXPECT_TRUE(clang_existsFunctionTemplate("f", make_scope(Decls[1], I))); - EXPECT_FALSE(clang_existsFunctionTemplate("f", make_scope(Decls[2], I))); - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_InstantiateTemplateFunctionFromString) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector interpreter_args = { "-include", "new" }; TestFixture::CreateInterpreter(interpreter_args); std::string code = R"(#include )"; @@ -725,9 +713,7 @@ 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() DFLT_FALSE); + auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1); EXPECT_TRUE(isa((Decl*)Instance1)); FunctionDecl* FD = cast((Decl*)Instance1); FunctionDecl* FnTD1 = FD->getTemplateInstantiationPattern(); @@ -754,9 +740,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_InstantiateTemplateMethod) { ASTContext& C = Interp->getCI()->getASTContext(); std::vector args1 = {C.IntTy.getAsOpaquePtr()}; - auto Instance1 = - Cpp::InstantiateTemplate(Decls[1], args1.data(), - /*type_size*/ args1.size() DFLT_FALSE); + auto Instance1 = Cpp::InstantiateTemplate(Decls[1], args1); EXPECT_TRUE(isa((Decl*)Instance1)); FunctionDecl* FD = cast((Decl*)Instance1); FunctionDecl* FnTD1 = FD->getTemplateInstantiationPattern(); @@ -766,8 +750,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_InstantiateTemplateMethod) { } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_LookupConstructors) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector Decls; std::string code = R"( @@ -807,8 +789,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_LookupConstructors) { } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetClassTemplatedMethods) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector Decls; std::string code = R"( @@ -937,9 +917,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, std::vector args1 = {C.DoubleTy.getAsOpaquePtr(), C.IntTy.getAsOpaquePtr()}; - auto Instance1 = - Cpp::InstantiateTemplate(Decls[1], args1.data(), - /*type_size*/ args1.size() DFLT_FALSE); + auto Instance1 = Cpp::InstantiateTemplate(Decls[1], args1); EXPECT_TRUE(Cpp::IsTemplatedFunction(Instance1)); EXPECT_EQ(Cpp::GetFunctionSignature(Instance1), "template<> void VariadicFn<>(double args, int args)"); @@ -961,8 +939,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, C.DoubleTy.getAsOpaquePtr()}; // instantiate VariadicFnExtended - auto Instance2 = - Cpp::InstantiateTemplate(Decls[2], args2.data(), args2.size(), true); + auto Instance2 = Cpp::InstantiateTemplate(Decls[2], args2, true); EXPECT_TRUE(Cpp::IsTemplatedFunction(Instance2)); FunctionDecl* FD2 = cast((Decl*)Instance2); @@ -1026,12 +1003,10 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, fn = Cpp::BestOverloadFunctionMatch(candidates, explicit_args2, args0); EXPECT_EQ(fn, fn0); - fn = Cpp::InstantiateTemplate(Decls[0], explicit_args1.data(), - explicit_args1.size() DFLT_FALSE); + fn = Cpp::InstantiateTemplate(Decls[0], explicit_args1); EXPECT_EQ(fn, fn0); - fn = Cpp::InstantiateTemplate(Decls[0], explicit_args2.data(), - explicit_args2.size() DFLT_FALSE); + fn = Cpp::InstantiateTemplate(Decls[0], explicit_args2); EXPECT_EQ(fn, fn0); } @@ -1153,12 +1128,12 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, std::vector args1 = {C.IntTy.getAsOpaquePtr()}; std::vector args2 = { - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; + Cpp::GetVariableType(Cpp::GetNamed("a"))}; std::vector args3 = {C.IntTy.getAsOpaquePtr(), C.IntTy.getAsOpaquePtr()}; std::vector args4 = { - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; + Cpp::GetVariableType(Cpp::GetNamed("a")), + Cpp::GetVariableType(Cpp::GetNamed("a"))}; std::vector args5 = {C.IntTy.getAsOpaquePtr(), C.DoubleTy.getAsOpaquePtr()}; @@ -1226,14 +1201,12 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ASTContext& C = Interp->getCI()->getASTContext(); std::vector args1 = { - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; + Cpp::GetVariableType(Cpp::GetNamed("a")), + Cpp::GetVariableType(Cpp::GetNamed("a"))}; std::vector args2 = { - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), - C.IntTy.getAsOpaquePtr()}; + Cpp::GetVariableType(Cpp::GetNamed("a")), C.IntTy.getAsOpaquePtr()}; std::vector args3 = { - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), - C.DoubleTy.getAsOpaquePtr()}; + Cpp::GetVariableType(Cpp::GetNamed("a")), C.DoubleTy.getAsOpaquePtr()}; std::vector explicit_args; Cpp::TCppFunction_t func1 = @@ -1244,14 +1217,14 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Cpp::BestOverloadFunctionMatch(candidates, explicit_args, args3); candidates.clear(); - Cpp::GetOperator(Cpp::GetScopeFromType( - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))), - Cpp::Operator::OP_Minus, candidates DFLT_OP_ARITY); + Cpp::GetOperator( + Cpp::GetScopeFromType(Cpp::GetVariableType(Cpp::GetNamed("a"))), + Cpp::Operator::OP_Minus, candidates); EXPECT_EQ(candidates.size(), 1); std::vector args4 = { - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; + Cpp::GetVariableType(Cpp::GetNamed("a"))}; Cpp::TCppFunction_t func4 = Cpp::BestOverloadFunctionMatch(candidates, explicit_args, args4); @@ -1305,13 +1278,13 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, std::vector args1 = {}; std::vector args2 = {C.IntTy.getAsOpaquePtr()}; std::vector args3 = { - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; + Cpp::GetVariableType(Cpp::GetNamed("a"))}; std::vector args4 = { - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), - Cpp::GetVariableType(Cpp::GetNamed("b" DFLT_NULLPTR))}; + Cpp::GetVariableType(Cpp::GetNamed("a")), + Cpp::GetVariableType(Cpp::GetNamed("b"))}; std::vector args5 = { - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR)), - Cpp::GetVariableType(Cpp::GetNamed("a" DFLT_NULLPTR))}; + Cpp::GetVariableType(Cpp::GetNamed("a")), + Cpp::GetVariableType(Cpp::GetNamed("a"))}; std::vector explicit_args1; std::vector explicit_args2 = {C.IntTy.getAsOpaquePtr(), @@ -1363,8 +1336,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, C.IntTy.getAsOpaquePtr(), }; - Cpp::TCppScope_t callme = Cpp::InstantiateTemplate( - Decls[0], explicit_params.data(), explicit_params.size() DFLT_FALSE); + Cpp::TCppScope_t callme = Cpp::InstantiateTemplate(Decls[0], explicit_params); EXPECT_TRUE(callme); std::vector arg_types = { @@ -1537,6 +1509,10 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsStaticMethod) { class C { void f1() {} static void f2() {} + template + static void f3() {} + template + void f4() {} }; )"; @@ -1546,11 +1522,11 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsStaticMethod) { EXPECT_FALSE(Cpp::IsStaticMethod(Decls[0])); EXPECT_FALSE(Cpp::IsStaticMethod(SubDecls[1])); EXPECT_TRUE(Cpp::IsStaticMethod(SubDecls[2])); + EXPECT_TRUE(Cpp::IsStaticMethod(SubDecls[3])); + EXPECT_FALSE(Cpp::IsStaticMethod(SubDecls[4])); } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionAddress) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif @@ -1590,8 +1566,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_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() DFLT_FALSE); + Cpp::TCppScope_t add1_double = Cpp::InstantiateTemplate(funcs[0], argument); EXPECT_TRUE(add1_double); EXPECT_TRUE(Cpp::GetFunctionAddress(add1_double)); @@ -1621,9 +1596,11 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallAdvanced) { #if CLANG_VERSION_MAJOR == 20 && defined(CPPINTEROP_USE_CLING) && defined(_WIN32) GTEST_SKIP() << "Test fails with Cling on Windows"; #endif - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; - +#ifdef EMSCRIPTEN +#if CLANG_VERSION_MAJOR > 21 + GTEST_SKIP() << "Test fails for Emscipten builds using LLVM 22"; +#endif +#endif if (TypeParam::isOutOfProcess) GTEST_SKIP() << "Test fails for OOP JIT builds"; @@ -1650,25 +1627,12 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_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] DFLT_TRUE DFLT_0)); - - // C API - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - auto S = clang_getDefaultConstructor(make_scope(Decls[0], I)); - void* object_c = nullptr; - clang_invoke(S, &object_c, nullptr, 0, nullptr); - EXPECT_TRUE(object_c) << "Failed to call the ctor."; - clang_destruct(object_c, make_scope(Decls[1], I), true); - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); + EXPECT_TRUE(Cpp::Destruct(object, Decls[1])); } #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST #ifndef _WIN32 // Death tests do not work on Windows TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallDebug) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; if (TypeParam::isOutOfProcess) GTEST_SKIP() << "Test fails for OOP JIT builds"; @@ -1695,13 +1659,13 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallDebug) { { JC.InvokeConstructor(/*result=*/nullptr); }, "Must pass the location of the created object!"); - void* result = Cpp::Allocate(Decls[0] DFLT_1); + void* result = Cpp::Allocate(Decls[0]); EXPECT_DEATH( { JC.InvokeConstructor(&result, 0UL); }, "Number of objects to construct should be atleast 1"); // InvokeConstructor below uses is_arena=nullptr and so does its own new[5], // overwriting result. Release the throw-away arena first. - Cpp::Deallocate(Decls[0], result DFLT_1); + Cpp::Deallocate(Decls[0], result); // Succeeds; with is_arena=nullptr and nary>1 the ctor wrapper picks its // array-new branch and overwrites result with the new[5] pointer. The @@ -1741,8 +1705,8 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallDebug) { EXPECT_TRUE(*obj == 42); // Destructors - Cpp::TCppScope_t scope_C = Cpp::GetNamed("C" DFLT_0); - Cpp::TCppObject_t object_C = Cpp::Construct(scope_C DFLT_NULLPTR DFLT_1); + Cpp::TCppScope_t scope_C = Cpp::GetNamed("C"); + Cpp::TCppObject_t object_C = Cpp::Construct(scope_C); // Make destructor callable and pass arguments JC = Cpp::MakeFunctionCallable(SubDecls[4]); @@ -1773,8 +1737,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; #endif - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; #if defined(CPPINTEROP_USE_CLING) && defined(_WIN32) GTEST_SKIP() << "Disabled, invoking functions containing printf does not work with Cling on Windows"; #endif @@ -1809,14 +1771,13 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { Cpp::JitCall FCI1 = Cpp::MakeFunctionCallable(Decls[0]); EXPECT_TRUE(FCI1.getKind() == Cpp::JitCall::kGenericCall); - Cpp::JitCall FCI2 = - Cpp::MakeFunctionCallable(Cpp::GetNamed("f2" DFLT_NULLPTR)); + Cpp::JitCall FCI2 = Cpp::MakeFunctionCallable(Cpp::GetNamed("f2")); EXPECT_TRUE(FCI2.getKind() == Cpp::JitCall::kGenericCall); - Cpp::JitCall FCI3 = Cpp::MakeFunctionCallable( - Cpp::GetNamed("f3", Cpp::GetNamed("NS" DFLT_NULLPTR))); + Cpp::JitCall FCI3 = + Cpp::MakeFunctionCallable(Cpp::GetNamed("f3", Cpp::GetNamed("NS"))); EXPECT_TRUE(FCI3.getKind() == Cpp::JitCall::kGenericCall); - Cpp::JitCall FCI4 = Cpp::MakeFunctionCallable( - Cpp::GetNamed("f4", Cpp::GetNamed("NS" DFLT_NULLPTR))); + Cpp::JitCall FCI4 = + Cpp::MakeFunctionCallable(Cpp::GetNamed("f4", Cpp::GetNamed("NS"))); EXPECT_TRUE(FCI4.getKind() == Cpp::JitCall::kGenericCall); int i = 9, ret1, ret3, ret4; @@ -1838,8 +1799,8 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { FCI4.Invoke(&ret4); EXPECT_EQ(ret4, 4); - Cpp::JitCall FCI5 = Cpp::MakeFunctionCallable( - Cpp::GetNamed("f5", Cpp::GetNamed("NS" DFLT_NULLPTR))); + Cpp::JitCall FCI5 = + Cpp::MakeFunctionCallable(Cpp::GetNamed("f5", Cpp::GetNamed("NS"))); EXPECT_TRUE(FCI5.getKind() == Cpp::JitCall::kGenericCall); typedef int (*int_func)(); @@ -1857,7 +1818,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { }; )"); - clang::NamedDecl* ClassC = (clang::NamedDecl*)Cpp::GetNamed("C" DFLT_NULLPTR); + clang::NamedDecl* ClassC = (clang::NamedDecl*)Cpp::GetNamed("C"); auto *CtorD = (clang::CXXConstructorDecl*)Cpp::GetDefaultConstructor(ClassC); auto FCI_Ctor = Cpp::MakeFunctionCallable(CtorD); @@ -1891,9 +1852,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { ASTContext& C = Interp->getCI()->getASTContext(); std::vector argument = {C.IntTy.getAsOpaquePtr()}; - auto Instance1 = - Cpp::InstantiateTemplate(Decls1[0], argument.data(), - /*type_size*/ argument.size() DFLT_FALSE); + auto Instance1 = Cpp::InstantiateTemplate(Decls1[0], argument); EXPECT_TRUE(isa((Decl*)Instance1)); auto* CTSD1 = static_cast(Instance1); auto* Add_D = Cpp::GetNamed("Add", CTSD1); @@ -1913,7 +1872,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { } )"); - Cpp::TCppScope_t set_5 = Cpp::GetNamed("set_5" DFLT_NULLPTR); + Cpp::TCppScope_t set_5 = Cpp::GetNamed("set_5"); EXPECT_TRUE(set_5); Cpp::JitCall set_5_f = Cpp::MakeFunctionCallable(set_5); @@ -1939,7 +1898,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { )"); Cpp::TCppScope_t TypedefToPrivateClass = - Cpp::GetNamed("TypedefToPrivateClass" DFLT_NULLPTR); + Cpp::GetNamed("TypedefToPrivateClass"); EXPECT_TRUE(TypedefToPrivateClass); Cpp::TCppScope_t f = Cpp::GetNamed("f", TypedefToPrivateClass); @@ -1960,7 +1919,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { bool operator<(T t) { return true; } }; )"); - Cpp::TCppScope_t TOperator = Cpp::GetNamed("TOperator" DFLT_NULLPTR); + Cpp::TCppScope_t TOperator = Cpp::GetNamed("TOperator"); auto* TOperatorCtor = Cpp::GetDefaultConstructor(TOperator); auto FCI_TOperatorCtor = Cpp::MakeFunctionCallable(TOperatorCtor); @@ -1969,13 +1928,12 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { EXPECT_TRUE(toperator); std::vector operators; - Cpp::GetOperator(TOperator, Cpp::Operator::OP_Less, operators DFLT_OP_ARITY); + Cpp::GetOperator(TOperator, Cpp::Operator::OP_Less, operators); 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 DFLT_FALSE); + Cpp::TCppScope_t op = Cpp::InstantiateTemplate(op_templated, {TAI}); auto FCI_op = Cpp::MakeFunctionCallable(op); bool boolean = false; FCI_op.Invoke((void*)&boolean, {args, /*args_size=*/1}, toperator); @@ -2011,11 +1969,11 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { N1::N2::Klass1 K2; )"); - Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1" DFLT_NULLPTR)); - Cpp::TCppType_t K2 = Cpp::GetTypeFromScope(Cpp::GetNamed("K2" DFLT_NULLPTR)); + Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1")); + Cpp::TCppType_t K2 = Cpp::GetTypeFromScope(Cpp::GetNamed("K2")); operators.clear(); - Cpp::GetOperator(Cpp::GetScope("N2", Cpp::GetScope("N1" DFLT_NULLPTR)), - Cpp::Operator::OP_Plus, operators DFLT_OP_ARITY); + Cpp::GetOperator(Cpp::GetScope("N2", Cpp::GetScope("N1")), + Cpp::Operator::OP_Plus, operators); EXPECT_EQ(operators.size(), 1); Cpp::TCppFunction_t kop = Cpp::BestOverloadFunctionMatch(operators, empty_templ_args, {K1, K2}); @@ -2091,9 +2049,9 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { )"); std::vector unresolved_candidate_methods; - Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std" DFLT_NULLPTR), + Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std"), unresolved_candidate_methods); - Cpp::TCppType_t p = Cpp::GetTypeFromScope(Cpp::GetNamed("p" DFLT_NULLPTR)); + Cpp::TCppType_t p = Cpp::GetTypeFromScope(Cpp::GetNamed("p")); EXPECT_TRUE(p); Cpp::TCppScope_t fn = Cpp::BestOverloadFunctionMatch( @@ -2170,8 +2128,8 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { Cpp::TCppScope_t tuple_tuple = Cpp::BestOverloadFunctionMatch( unresolved_candidate_methods, {}, - {Cpp::GetVariableType(Cpp::GetNamed("tuple_one" DFLT_NULLPTR)), - Cpp::GetVariableType(Cpp::GetNamed("tuple_two" DFLT_NULLPTR))}); + {Cpp::GetVariableType(Cpp::GetNamed("tuple_one")), + Cpp::GetVariableType(Cpp::GetNamed("tuple_two"))}); EXPECT_TRUE(tuple_tuple); auto tuple_tuple_callable = Cpp::MakeFunctionCallable(tuple_tuple); @@ -2186,7 +2144,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { )"); Cpp::TCppScope_t bar = - Cpp::GetNamed("bar", Cpp::GetScope("EnumFunctionSameName" DFLT_NULLPTR)); + Cpp::GetNamed("bar", Cpp::GetScope("EnumFunctionSameName")); EXPECT_TRUE(bar); auto bar_callable = Cpp::MakeFunctionCallable(bar); @@ -2202,7 +2160,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { template void consume(T t) {} - )" DFLT_FALSE); + )"); unresolved_candidate_methods.clear(); Cpp::GetClassTemplatedMethods("consume", Cpp::GetGlobalScope(), @@ -2211,7 +2169,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { Cpp::TCppScope_t consume = Cpp::BestOverloadFunctionMatch( unresolved_candidate_methods, {}, - {Cpp::GetVariableType(Cpp::GetNamed("consumable" DFLT_NULLPTR))}); + {Cpp::GetVariableType(Cpp::GetNamed("consumable"))}); EXPECT_TRUE(consume); auto consume_callable = Cpp::MakeFunctionCallable(consume); @@ -2231,22 +2189,21 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { const Product operator*(const KlassProduct &other) const { return Product(); } }; - )" DFLT_FALSE); + )"); - Cpp::TCppScope_t KlassProduct = Cpp::GetNamed("KlassProduct" DFLT_NULLPTR); + Cpp::TCppScope_t KlassProduct = Cpp::GetNamed("KlassProduct"); EXPECT_TRUE(KlassProduct); Cpp::TCppScope_t KlassProduct_int = - Cpp::InstantiateTemplate(KlassProduct, &TAI, 1 DFLT_FALSE); + Cpp::InstantiateTemplate(KlassProduct, {TAI}); EXPECT_TRUE(KlassProduct_int); TAI = Cpp::TemplateArgInfo(Cpp::GetType("float")); Cpp::TCppScope_t KlassProduct_float = - Cpp::InstantiateTemplate(KlassProduct, &TAI, 1 DFLT_FALSE); + Cpp::InstantiateTemplate(KlassProduct, {TAI}); EXPECT_TRUE(KlassProduct_float); operators.clear(); - Cpp::GetOperator(KlassProduct_int, Cpp::Operator::OP_Star, - operators DFLT_OP_ARITY); + Cpp::GetOperator(KlassProduct_int, Cpp::Operator::OP_Star, operators); EXPECT_EQ(operators.size(), 2); op = Cpp::BestOverloadFunctionMatch( @@ -2266,47 +2223,43 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) { template class TemplatedEnum {}; } - )" DFLT_FALSE); + )"); - Cpp::TCppScope_t TemplatedEnum = Cpp::GetScope("TemplatedEnum" DFLT_NULLPTR); + Cpp::TCppScope_t TemplatedEnum = Cpp::GetScope("TemplatedEnum"); EXPECT_TRUE(TemplatedEnum); - auto TAI_enum = Cpp::TemplateArgInfo( - Cpp::GetTypeFromScope(Cpp::GetNamed("MyEnum" DFLT_NULLPTR)), "1"); + auto TAI_enum = + Cpp::TemplateArgInfo(Cpp::GetTypeFromScope(Cpp::GetNamed("MyEnum")), "1"); Cpp::TCppScope_t TemplatedEnum_instantiated = - Cpp::InstantiateTemplate(TemplatedEnum, &TAI_enum, 1 DFLT_FALSE); + Cpp::InstantiateTemplate(TemplatedEnum, {TAI_enum}); EXPECT_TRUE(TemplatedEnum_instantiated); - Cpp::TCppObject_t obj = - Cpp::Construct(TemplatedEnum_instantiated DFLT_NULLPTR DFLT_1); + Cpp::TCppObject_t obj = Cpp::Construct(TemplatedEnum_instantiated); EXPECT_TRUE(obj); - Cpp::Destruct(obj, TemplatedEnum_instantiated DFLT_TRUE DFLT_0); + Cpp::Destruct(obj, TemplatedEnum_instantiated); obj = nullptr; Cpp::TCppScope_t MyNameSpace_TemplatedEnum = - Cpp::GetScope("TemplatedEnum", Cpp::GetScope("MyNameSpace" DFLT_NULLPTR)); + Cpp::GetScope("TemplatedEnum", Cpp::GetScope("MyNameSpace")); EXPECT_TRUE(TemplatedEnum); - TAI_enum = Cpp::TemplateArgInfo( - Cpp::GetTypeFromScope( - Cpp::GetNamed("MyEnum", Cpp::GetScope("MyNameSpace" DFLT_NULLPTR))), - "1"); + TAI_enum = Cpp::TemplateArgInfo(Cpp::GetTypeFromScope(Cpp::GetNamed( + "MyEnum", Cpp::GetScope("MyNameSpace"))), + "1"); Cpp::TCppScope_t MyNameSpace_TemplatedEnum_instantiated = - Cpp::InstantiateTemplate(MyNameSpace_TemplatedEnum, &TAI_enum, - 1 DFLT_FALSE); + Cpp::InstantiateTemplate(MyNameSpace_TemplatedEnum, {TAI_enum}); EXPECT_TRUE(TemplatedEnum_instantiated); - obj = Cpp::Construct( - MyNameSpace_TemplatedEnum_instantiated DFLT_NULLPTR DFLT_1); + obj = Cpp::Construct(MyNameSpace_TemplatedEnum_instantiated); EXPECT_TRUE(obj); - Cpp::Destruct(obj, MyNameSpace_TemplatedEnum_instantiated DFLT_TRUE DFLT_0); + Cpp::Destruct(obj, MyNameSpace_TemplatedEnum_instantiated); 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" DFLT_NULLPTR); + Cpp::TCppScope_t get_fn = Cpp::GetNamed("get_fn"); EXPECT_TRUE(get_fn); auto get_fn_callable = Cpp::MakeFunctionCallable(get_fn); @@ -2424,9 +2377,9 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgDefault) { EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 3), "0."); ASTContext& C = Interp->getCI()->getASTContext(); - Cpp::TemplateArgInfo template_args[1] = {C.IntTy.getAsOpaquePtr()}; + std::vector template_args = {C.IntTy.getAsOpaquePtr()}; Cpp::TCppScope_t my_struct = - Cpp::InstantiateTemplate(Decls[6], template_args, 1 DFLT_FALSE); + Cpp::InstantiateTemplate(Decls[6], template_args); EXPECT_TRUE(my_struct); std::vector fns = @@ -2439,10 +2392,13 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionArgDefault) { } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Construct) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif +#ifdef EMSCRIPTEN +#if CLANG_VERSION_MAJOR > 21 + GTEST_SKIP() << "Test fails for Emscipten builds using LLVM 22"; +#endif #endif if (TypeParam::isOutOfProcess) GTEST_SKIP() << "Test fails for OOP JIT builds"; @@ -2466,8 +2422,8 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Construct) { GetAllTopLevelDecls(code, Decls, false, interpreter_args); GetAllSubDecls(Decls[1], SubDecls); testing::internal::CaptureStdout(); - Cpp::TCppScope_t scope = Cpp::GetNamed("C" DFLT_NULLPTR); - Cpp::TCppObject_t object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); + Cpp::TCppScope_t scope = Cpp::GetNamed("C"); + Cpp::TCppObject_t object = Cpp::Construct(scope); EXPECT_TRUE(object != nullptr); std::string output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Constructor Executed"); @@ -2477,60 +2433,47 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Construct) { // Placement. testing::internal::CaptureStdout(); - void* where = Cpp::Allocate(scope DFLT_1); - EXPECT_TRUE(where == Cpp::Construct(scope, where DFLT_1)); + void* where = Cpp::Allocate(scope); + EXPECT_TRUE(where == Cpp::Construct(scope, where)); // Check for the value of x which should be at the start of the object. EXPECT_TRUE(*(int*)where == 12345); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Constructor Executed"); output.clear(); Cpp::Destruct(where, scope, /*withFree=*/false, /*count=*/0); - Cpp::Deallocate(scope, where DFLT_1); + Cpp::Deallocate(scope, where); // Pass a constructor testing::internal::CaptureStdout(); - where = Cpp::Allocate(scope DFLT_1); - EXPECT_TRUE(where == Cpp::Construct(SubDecls[3], where DFLT_1)); + where = Cpp::Allocate(scope); + EXPECT_TRUE(where == Cpp::Construct(SubDecls[3], where)); EXPECT_TRUE(*(int*)where == 12345); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Constructor Executed"); output.clear(); Cpp::Destruct(where, scope, /*withFree=*/false, /*count=*/0); - Cpp::Deallocate(scope, where DFLT_1); + Cpp::Deallocate(scope, where); // Pass a non-class decl, this should fail. Capture the failing // Construct's nullptr in a separate variable so the arena pointer in // `where` stays alive for Deallocate. FIXME: Construct's failure path // could own the arena release itself rather than leaking this contract // to every caller — see Cpp::Construct in lib/CppInterOp/CppInterOp.cpp. - where = Cpp::Allocate(scope DFLT_1); - void* construct_fail = Cpp::Construct(Decls[2], where DFLT_1); + where = Cpp::Allocate(scope); + void* construct_fail = Cpp::Construct(Decls[2], where); EXPECT_TRUE(construct_fail == nullptr); - Cpp::Deallocate(scope, where DFLT_1); - // C API - testing::internal::CaptureStdout(); - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - auto scope_c = make_scope(static_cast(scope), I); - auto object_c = clang_construct(scope_c, nullptr, 1UL); - EXPECT_TRUE(object_c != nullptr); - output = testing::internal::GetCapturedStdout(); - EXPECT_EQ(output, "Constructor Executed"); - output.clear(); - clang_destruct(object_c, scope_c, /*withFree=*/true, /*nary=*/0); - auto* dummy = clang_allocate(8); - EXPECT_TRUE(dummy); - clang_deallocate(dummy); - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); + Cpp::Deallocate(scope, where); } // Test zero initialization of PODs and default initialization cases TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructPOD) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif +#ifdef EMSCRIPTEN +#if CLANG_VERSION_MAJOR > 21 + GTEST_SKIP() << "Test fails for Emscipten builds using LLVM 22"; +#endif #endif if (TypeParam::isOutOfProcess) GTEST_SKIP() << "Test fails for OOP JIT builds"; @@ -2548,10 +2491,10 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructPOD) { }; })"); - auto* ns = Cpp::GetNamed("PODS" DFLT_NULLPTR); + auto* ns = Cpp::GetNamed("PODS"); Cpp::TCppScope_t scope = Cpp::GetNamed("SomePOD_B", ns); EXPECT_TRUE(scope); - Cpp::TCppObject_t object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); + Cpp::TCppObject_t object = Cpp::Construct(scope); EXPECT_TRUE(object != nullptr); int* fInt = reinterpret_cast(reinterpret_cast(object)); EXPECT_TRUE(*fInt == 0); @@ -2559,7 +2502,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructPOD) { scope = Cpp::GetNamed("SomePOD_C", ns); EXPECT_TRUE(scope); - object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); + object = Cpp::Construct(scope); EXPECT_TRUE(object); auto* fDouble = reinterpret_cast(reinterpret_cast(object) + sizeof(int)); @@ -2569,10 +2512,13 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructPOD) { // Test nested constructor calls TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructNested) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif +#ifdef EMSCRIPTEN +#if CLANG_VERSION_MAJOR > 21 + GTEST_SKIP() << "Test fails for Emscipten builds using LLVM 22"; +#endif #endif if (TypeParam::isOutOfProcess) GTEST_SKIP() << "Test fails for OOP JIT builds"; @@ -2602,9 +2548,9 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructNested) { )"); testing::internal::CaptureStdout(); - 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); + Cpp::TCppScope_t scope_A = Cpp::GetNamed("A"); + Cpp::TCppScope_t scope_B = Cpp::GetNamed("B"); + Cpp::TCppObject_t object = Cpp::Construct(scope_B); EXPECT_TRUE(object != nullptr); std::string output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "A Constructor Called\nB Constructor Called\n"); @@ -2613,8 +2559,8 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructNested) { // In-memory construction testing::internal::CaptureStdout(); - void* arena = Cpp::Allocate(scope_B DFLT_1); - EXPECT_TRUE(arena == Cpp::Construct(scope_B, arena DFLT_1)); + void* arena = Cpp::Allocate(scope_B); + EXPECT_TRUE(arena == Cpp::Construct(scope_B, arena)); // Check if both integers a_val and b_val were set. EXPECT_EQ(*(int*)arena, 7); @@ -2622,7 +2568,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructNested) { int* b_val_ptr = reinterpret_cast(reinterpret_cast(arena) + a_size); EXPECT_EQ(*b_val_ptr, 99); - Cpp::Deallocate(scope_B, arena DFLT_1); + Cpp::Deallocate(scope_B, arena); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "A Constructor Called\nB Constructor Called\n"); output.clear(); @@ -2632,8 +2578,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructArray) { #if defined(EMSCRIPTEN) GTEST_SKIP() << "Test fails for Emscripten builds"; #endif - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif @@ -2654,7 +2598,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructArray) { }; )"); - Cpp::TCppScope_t scope = Cpp::GetNamed("C" DFLT_NULLPTR); + Cpp::TCppScope_t scope = Cpp::GetNamed("C"); std::string output; size_t a = 5; // Construct an array of 5 objects @@ -2686,8 +2630,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Destruct) { #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."; @@ -2710,35 +2652,25 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Destruct) { )"); testing::internal::CaptureStdout(); - 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)); + Cpp::TCppScope_t scope = Cpp::GetNamed("C"); + Cpp::TCppObject_t object = Cpp::Construct(scope); + EXPECT_TRUE(Cpp::Destruct(object, scope)); std::string output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Destructor Executed"); output.clear(); testing::internal::CaptureStdout(); - object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); + object = Cpp::Construct(scope); // 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 DFLT_0)); - Cpp::Deallocate(scope, object DFLT_1); + EXPECT_TRUE(Cpp::Destruct(object, scope, /*withFree=*/false)); + Cpp::Deallocate(scope, object); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Destructor Executed"); // C API testing::internal::CaptureStdout(); - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - auto scope_c = make_scope(static_cast(scope), I); - auto object_c = clang_construct(scope_c, nullptr, 1UL); - clang_destruct(object_c, scope_c, true); - output = testing::internal::GetCapturedStdout(); - EXPECT_EQ(output, "Destructor Executed"); - output.clear(); - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); // Failure Test, this wrapper should not compile since we explicitly delete // the destructor @@ -2750,17 +2682,16 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Destruct) { }; )"); - scope = Cpp::GetNamed("D" DFLT_NULLPTR); - object = Cpp::Construct(scope DFLT_NULLPTR DFLT_1); - EXPECT_FALSE(Cpp::Destruct(object, scope DFLT_TRUE DFLT_0)); + scope = Cpp::GetNamed("D"); + object = Cpp::Construct(scope); + EXPECT_FALSE(Cpp::Destruct(object, scope)); + testing::internal::GetCapturedStdout(); } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_DestructArray) { #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."; @@ -2786,7 +2717,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_DestructArray) { }; )"); - Cpp::TCppScope_t scope = Cpp::GetNamed("C" DFLT_NULLPTR); + Cpp::TCppScope_t scope = Cpp::GetNamed("C"); std::string output; size_t a = 5; // Construct an array of 5 objects @@ -2882,22 +2813,22 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_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" DFLT_NULLPTR)); - Cpp::TCppType_t o2 = Cpp::GetTypeFromScope(Cpp::GetNamed("o2" DFLT_NULLPTR)); + Cpp::TCppType_t o1 = Cpp::GetTypeFromScope(Cpp::GetNamed("o1")); + Cpp::TCppType_t o2 = Cpp::GetTypeFromScope(Cpp::GetNamed("o2")); 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 DFLT_FALSE); + std::vector args = {{o1}, {o2}}; + Cpp::TCppScope_t fn = Cpp::InstantiateTemplate(fns[0], args); 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;" DFLT_FALSE)); - EXPECT_FALSE(Cpp::Declare("int y = x;" DFLT_FALSE)); + EXPECT_FALSE(Cpp::Declare("int x = 1;")); + EXPECT_FALSE(Cpp::Declare("int y = x;")); } TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsExplicit) { diff --git a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp index 792d1491530cb..8b527793ad1bc 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp @@ -13,11 +13,9 @@ #include "clang/Basic/Version.h" -#include -#include "clang-c/CXCppInterOp.h" - #include "llvm/ADT/SmallString.h" #include "llvm/Config/llvm-config.h" +#include "llvm/Support/BuryPointer.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" @@ -26,6 +24,7 @@ #include #include +#include using ::testing::StartsWith; @@ -33,6 +32,17 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Version) { EXPECT_THAT(Cpp::GetVersion(), StartsWith("CppInterOp version")); } +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_BuildInfo) { + // Pin the headline labels so a future template edit cannot silently + // drop a field. + std::string Info = Cpp::GetBuildInfo(); + EXPECT_THAT(Info, ::testing::HasSubstr("build type:")); + EXPECT_THAT(Info, ::testing::HasSubstr("compiler:")); + EXPECT_THAT(Info, ::testing::HasSubstr("llvm:")); + EXPECT_THAT(Info, ::testing::HasSubstr("target:")); + EXPECT_THAT(Info, ::testing::HasSubstr("cmake-line:")); +} + #ifndef LLVM_ENABLE_ASSERTIONS TYPED_TEST(CPPINTEROP_TEST_MODE, DISABLED_Interpreter_DebugFlag) { #else @@ -67,8 +77,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Evaluate) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #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); @@ -99,6 +107,26 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Evaluate) { EXPECT_FALSE(HadError); } +// Regression (cppyy test12): no-Value-after-success used to abort in +// convertTo. A pure class decl is no-Value across all clang-repl +// versions; `int x = 5;` is bound on newer ones. +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Evaluate_NonValueStatement) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + TestFixture::CreateInterpreter(); + + bool HadError = false; + EXPECT_EQ(Cpp::Evaluate("class EvalRegression_NoValue {};", &HadError), + (intptr_t)~0UL); + EXPECT_TRUE(HadError); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DeleteInterpreter) { if (TypeParam::isOutOfProcess) GTEST_SKIP() << "Test fails for OOP JIT builds"; @@ -119,6 +147,74 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DeleteInterpreter) { EXPECT_EQ(I2, Cpp::GetInterpreter()) << "I2 is not active"; } +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_StaticDtorsRunOnDelete) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscripten builds"; +#endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; + + auto* I = TestFixture::CreateInterpreter(); + ASSERT_NE(I, nullptr); + + // Host-side flag the JIT writes to; survives the interpreter deletion + // that frees JIT memory. Reset explicitly for --gtest_repeat. + static int HostFlag; + HostFlag = 0; + std::string Inject = "extern \"C\" void* dtor_sink = (void*)" + + std::to_string(reinterpret_cast(&HostFlag)) + + ";"; + Cpp::Declare(Inject.c_str(), I); + Cpp::Declare(R"( + struct DtorNotify { ~DtorNotify() { *static_cast(dtor_sink) = 1; } }; + )", + I); + Cpp::Process("static DtorNotify notify;"); + + EXPECT_EQ(HostFlag, 0); + Cpp::DeleteInterpreter(I); + EXPECT_EQ(HostFlag, 1) << "JIT static dtor did not run on DeleteInterpreter"; +} + +// Mirrors clang/unittests/Interpreter/InterpreterTest.cpp's +// ShutdownDoesNotMaterializeAgainstDestroyedGlobals at the CppInterOp +// wrapper level: two interpreters each register a static with a +// non-trivial dtor, then std::exit drives the C++ static-dtor phase. +// On LLVM 23+ our InterpreterShutdown calls llvm_shutdown which fires +// ~InterpreterInfo -> ~Interpreter -> JIT deinit; the deinit lookup +// skips lazy materialization (Platform::lookupResolvedInitSymbols, +// llvm/llvm-project#196874) and the process exits cleanly. +// +// Skipped on older LLVM: InterpreterShutdown's llvm_shutdown call is +// gated out (we leak instead of risking the JIT cleanUp crash), and +// driving the chain manually from the test deadlocks rather than +// crashing cleanly through the compat layer. The crash contract is +// pinned upstream by clang's +// ShutdownDoesNotMaterializeAgainstDestroyedGlobals test. +TYPED_TEST(CPPINTEROP_TEST_MODE, + Interpreter_ShutdownDoesNotMaterializeAgainstDestroyedGlobals) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscripten builds"; +#endif + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; +#if LLVM_VERSION_MAJOR <= 22 + GTEST_SKIP() << "Requires LLVM 23+ (lookupResolvedInitSymbols)"; +#else + EXPECT_EXIT( + { + auto* I1 = TestFixture::CreateInterpreter(); + auto* I2 = TestFixture::CreateInterpreter(); + Cpp::ActivateInterpreter(I1); + Cpp::Process("struct S1 { S1() {} ~S1() {} }; static S1 s1;"); + Cpp::ActivateInterpreter(I2); + Cpp::Process("struct S2 { S2() {} ~S2() {} }; static S2 s2;"); + std::exit(0); + }, + ::testing::ExitedWithCode(0), ""); +#endif +} + TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_ActivateInterpreter) { #ifdef EMSCRIPTEN_STATIC_LIBRARY GTEST_SKIP() << "Test fails for Emscipten static library build"; @@ -131,20 +227,20 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_ActivateInterpreter) { auto* Cpp20 = TestFixture::CreateInterpreter({"-std=c++20"}); EXPECT_TRUE(Cpp14 && Cpp17 && Cpp20); - EXPECT_TRUE(Cpp::Evaluate("__cplusplus" DFLT_NULLPTR) == 202002L) + EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 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" DFLT_NULLPTR) == 201402L); + EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201402L); Cpp::DeleteInterpreter(Cpp14); EXPECT_EQ(Cpp::GetInterpreter(), Cpp20); EXPECT_TRUE(Cpp::ActivateInterpreter(Cpp17)); - EXPECT_TRUE(Cpp::Evaluate("__cplusplus" DFLT_NULLPTR) == 201703L); + EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201703L); } TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Process) { @@ -156,25 +252,13 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Process) { #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", "-Xclang", "-iwithsysroot/include/compat" }; - auto* I = TestFixture::CreateInterpreter(interpreter_args); + TestFixture::CreateInterpreter(interpreter_args); EXPECT_TRUE(Cpp::Process("") == 0); EXPECT_TRUE(Cpp::Process("int a = 12;") == 0); EXPECT_FALSE(Cpp::Process("error_here;") == 0); // Linker/JIT error. EXPECT_FALSE(Cpp::Process("int f(); int res = f();") == 0); - - // C API - auto* CXI = clang_createInterpreterFromRawPtr(I); - clang_Interpreter_declare(CXI, "#include ", false); - clang_Interpreter_process(CXI, "int c = 42;"); - auto* CXV = clang_createValue(); - auto Res = clang_Interpreter_evaluate(CXI, "c", CXV); - EXPECT_EQ(Res, CXError_Success); - clang_Value_dispose(CXV); - clang_Interpreter_dispose(CXI); } TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DeclareSilent) { @@ -193,6 +277,31 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DeclareSilent) { EXPECT_EQ(0, Cpp::Declare("int y = 123;", /*silent=*/false)); } +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DeclareReportsParseErrors) { +#ifdef _WIN32 + // The non-silent parse-error case emits an `error: expected expression` + // diagnostic to stderr, which MSBuild's check-cppinterop custom-build + // rule scans and treats as a build failure regardless of the gtest + // result. + GTEST_SKIP() + << "non-silent diagnostic trips MSBuild error scanning on Windows"; +#endif +#if CLANG_VERSION_MAJOR > 21 + GTEST_SKIP() << "Test crashes gtest for llvm 22 based build"; +#endif + TestFixture::CreateInterpreter(); + + // Valid input still returns 0. + EXPECT_EQ(0, Cpp::Declare("int z = 7;", /*silent=*/false)); + + // Parse error -> rc != 0 (Parse-recovered case the trap catches). + EXPECT_NE(0, Cpp::Declare("int err = ;", /*silent=*/false)); + + // Warning-only diagnostics must not trip the trap. + EXPECT_EQ(0, Cpp::Declare("int __attribute__((deprecated)) v = 0;", + /*silent=*/false)); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_EmscriptenExceptionHandling) { #ifndef EMSCRIPTEN GTEST_SKIP() << "This test is intended to check exception handling for Emscripten builds."; @@ -226,48 +335,21 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_CreateInterpreter) { "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)); + "#endif"); + EXPECT_TRUE(Cpp::GetNamed("cpp14")); + EXPECT_FALSE(Cpp::GetNamed("cppUnknown")); I = TestFixture::CreateInterpreter({"-std=c++17"}); Cpp::Declare("#if __cplusplus==201703L\n" "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 - auto* CXI = clang_createInterpreterFromRawPtr(I); - auto CLI = clang_Interpreter_getClangInterpreter(CXI); - EXPECT_TRUE(CLI); - - auto I2 = clang_Interpreter_takeInterpreterAsPtr(CXI); - EXPECT_EQ(I, I2); - clang_Interpreter_dispose(CXI); -#endif + "#endif"); + EXPECT_TRUE(Cpp::GetNamed("cpp17")); + EXPECT_FALSE(Cpp::GetNamed("cppUnknown")); } #ifndef CPPINTEROP_USE_CLING -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); - EXPECT_TRUE(CLI); - clang_Interpreter_dispose(CXI); -} - -TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_CreateInterpreterCAPIFailure) { -#ifdef _WIN32 - GTEST_SKIP() << "Disabled on Windows. Needs fixing."; -#endif - const char* argv[] = {"-fsyntax-only", "-Xclang", "-invalid-plugin"}; - auto *CXI = clang_createInterpreter(argv, 3); - EXPECT_EQ(CXI, nullptr); -} #endif #ifdef LLVM_BINARY_DIR @@ -342,7 +424,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_CodeCompletion) { #endif TestFixture::CreateInterpreter(); std::vector cc; - Cpp::Declare("int foo = 12;" DFLT_FALSE); + Cpp::Declare("int foo = 12;"); 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 @@ -380,16 +462,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_GetLanguageStandardCpp) { Cpp::InterpreterLanguageStandard::cxx23); } -TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_GetLanguageCAPI) { - auto* I = TestFixture::CreateInterpreter(); - auto* CXI = clang_createInterpreterFromRawPtr(I); - EXPECT_EQ(clang_Interpreter_getLanguage(CXI), - CXInterpreterLanguage_CPlusPlus); - EXPECT_EQ(clang_Interpreter_getLanguageStandard(CXI), - CXInterpreterLanguageStandard_cxx14); - clang_Interpreter_dispose(CXI); -} - TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_GetLanguageC) { TestFixture::CreateInterpreter({"-xc", "-std=c99"}); EXPECT_EQ(Cpp::GetLanguage(nullptr), Cpp::InterpreterLanguage::C); @@ -426,8 +498,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_GetLanguageStandardGNU) { TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_ExternalInterpreter) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; #ifndef CPPINTEROP_USE_CLING llvm::ExitOnError ExitOnErr; @@ -466,7 +536,10 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_ExternalInterpreter) { EXPECT_TRUE(Cpp::GetInterpreter()) << "External Interpreter not set"; #ifndef CPPINTEROP_USE_CLING - I.release(); + // Skip Interpreter destruction: the teardown-order bug tracked in + // CppInterOp#887 makes it unsafe. BuryPointer is used instead of + // release() so LSan sees the graph as reachable rather than leaked. + llvm::BuryPointer(std::move(I)); #endif #ifdef CPPINTEROP_USE_CLING @@ -534,11 +607,11 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_WrapperCacheIsPerInterpreter) { auto* I1 = TestFixture::CreateInterpreter(); ASSERT_NE(I1, nullptr); Cpp::ActivateInterpreter(I1); - Cpp::Declare("int add(int a, int b) { return a + b; }" DFLT_FALSE); - auto* AddDecl1 = Cpp::GetNamed("add" DFLT_NULLPTR); + Cpp::Declare("int add(int a, int b) { return a + b; }"); + auto* AddDecl1 = Cpp::GetNamed("add"); ASSERT_NE(AddDecl1, nullptr); - Cpp::Declare("#include " DFLT_FALSE); // Needed by JitCall + Cpp::Declare("#include "); // Needed by JitCall auto JC1 = Cpp::MakeFunctionCallable(AddDecl1); ASSERT_TRUE(JC1.isValid()); @@ -551,11 +624,11 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_WrapperCacheIsPerInterpreter) { auto* I2 = TestFixture::CreateInterpreter(); ASSERT_NE(I2, nullptr); Cpp::ActivateInterpreter(I2); - Cpp::Declare("int add(int a, int b) { return a * b; }" DFLT_FALSE); - auto* AddDecl2 = Cpp::GetNamed("add" DFLT_NULLPTR); + Cpp::Declare("int add(int a, int b) { return a * b; }"); + auto* AddDecl2 = Cpp::GetNamed("add"); ASSERT_NE(AddDecl2, nullptr); - Cpp::Declare("#include " DFLT_FALSE); // Needed by JitCall + Cpp::Declare("#include "); // Needed by JitCall auto JC2 = Cpp::MakeFunctionCallable(AddDecl2); ASSERT_TRUE(JC2.isValid()); @@ -570,4 +643,44 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_WrapperCacheIsPerInterpreter) { EXPECT_EQ(r2, 12) << "Wrapper should use I2's implementation (multiply), " "not a stale cache from deleted I1"; } + +TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DLMIsPerInterpreter) { + if (TypeParam::isOutOfProcess) + GTEST_SKIP() << "Test fails for OOP JIT builds"; +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows: DLM symbol-table walk crashes " + "(RVA 0x0 in export table)."; +#endif + +#ifdef __APPLE__ + static constexpr const char* kSym = "_ret_zero"; +#else + static constexpr const char* kSym = "ret_zero"; +#endif + + std::string BinaryPath = + llvm::sys::fs::getMainExecutable(/*Argv0=*/nullptr, /*MainAddr=*/nullptr); + ASSERT_FALSE(BinaryPath.empty()); + llvm::StringRef BinDir = llvm::sys::path::parent_path(BinaryPath); + + auto* I1 = TestFixture::CreateInterpreter(); + ASSERT_NE(I1, nullptr); + Cpp::ActivateInterpreter(I1); + Cpp::AddSearchPath(BinDir.str().c_str()); + + std::string R1 = Cpp::SearchLibrariesForSymbol(kSym, /*system_search=*/false); + if (R1.empty()) + GTEST_SKIP() << "TestSharedLib not found — cannot test DLM isolation"; + + // With a single function-local-static DLM, the path added on I1 + // would reach I2 and the lookup below would succeed; the per- + // interpreter DLM means I2 starts with a fresh path set. + auto* I2 = TestFixture::CreateInterpreter(); + ASSERT_NE(I2, nullptr); + Cpp::ActivateInterpreter(I2); + + std::string R2 = Cpp::SearchLibrariesForSymbol(kSym, /*system_search=*/false); + EXPECT_TRUE(R2.empty()) << "I2 must not see search paths added on I1; found: " + << R2; +} #endif // !EMSCRIPTEN diff --git a/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp index 34052a48866e7..ca6d7e732bdec 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp @@ -15,8 +15,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Jit_InsertOrReplaceJitSymbol) { #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 @@ -79,8 +77,6 @@ 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 diff --git a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp index 16008595cf081..36e4ea43240be 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -1,7 +1,6 @@ #include "Utils.h" #include "CppInterOp/CppInterOp.h" -#include "clang-c/CXCppInterOp.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDumper.h" @@ -13,10 +12,9 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Sema/Sema.h" -#include "llvm/Support/Valgrind.h" - #include "gtest/gtest.h" +#include #include #include @@ -511,10 +509,9 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetCompleteName) { EXPECT_EQ(Cpp::GetCompleteName(nullptr), ""); 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 DFLT_FALSE); + std::vector template_args = { + C.IntTy.getAsOpaquePtr(), C.DoubleTy.getAsOpaquePtr()}; + Cpp::TCppScope_t fn = Cpp::InstantiateTemplate(Decls[11], template_args); EXPECT_TRUE(fn); EXPECT_EQ(Cpp::GetCompleteName(fn), "fn"); } @@ -707,6 +704,97 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetNamed) { EXPECT_EQ(Cpp::GetQualifiedName(std_string_npos_var), "std::basic_string::npos"); } +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetNamedWithUsing) { + // Each subcase covers one form of [namespace.udecl] / [namespace.udir]. + // GetNamed must look through the alias and return the original decl, + // identified by canonical-decl identity. + std::string code = R"( + namespace N { + class C {}; + int x; + void f(); + } + namespace M { + using N::C; // using-declaration: type + using N::x; // using-declaration: variable + using N::f; // using-declaration: function + } + namespace P { + using namespace N; // using-directive + } + )"; + + TestFixture::CreateInterpreter(); + Interp->declare(code); + + Cpp::TCppScope_t ns_N = Cpp::GetNamed("N"); + Cpp::TCppScope_t ns_M = Cpp::GetNamed("M"); + Cpp::TCppScope_t ns_P = Cpp::GetNamed("P"); + ASSERT_TRUE(ns_N); + ASSERT_TRUE(ns_M); + ASSERT_TRUE(ns_P); + + Cpp::TCppScope_t N_C = Cpp::GetNamed("C", ns_N); + Cpp::TCppScope_t N_x = Cpp::GetNamed("x", ns_N); + Cpp::TCppScope_t N_f = Cpp::GetNamed("f", ns_N); + ASSERT_TRUE(N_C); + ASSERT_TRUE(N_x); + ASSERT_TRUE(N_f); + + // using-declaration: GetNamed inside M should resolve through the + // UsingShadowDecl to the original target in N. + EXPECT_EQ(Cpp::GetNamed("C", ns_M), N_C) + << "using N::C; GetNamed(\"C\", M) should return N::C"; + EXPECT_EQ(Cpp::GetNamed("x", ns_M), N_x) + << "using N::x; GetNamed(\"x\", M) should return N::x"; + EXPECT_EQ(Cpp::GetNamed("f", ns_M), N_f) + << "using N::f; GetNamed(\"f\", M) should return N::f"; + + // using-directive: names in N are visible inside P; GetNamed should + // surface them as the canonical decls in N. + EXPECT_EQ(Cpp::GetNamed("C", ns_P), N_C) + << "using namespace N; GetNamed(\"C\", P) should return N::C"; + EXPECT_EQ(Cpp::GetNamed("x", ns_P), N_x) + << "using namespace N; GetNamed(\"x\", P) should return N::x"; + EXPECT_EQ(Cpp::GetNamed("f", ns_P), N_f) + << "using namespace N; GetNamed(\"f\", P) should return N::f"; + + // Transitive using-directive ([namespace.udir]p3): names visible via + // the closure of using-directives, not just the first hop. Exercises + // UnqualUsingDirectiveSet::addUsingDirectives' transitive walk. + Interp->declare(R"( + namespace P2 { using namespace N; } + namespace Q { using namespace P2; } + )"); + Cpp::TCppScope_t ns_Q = Cpp::GetNamed("Q"); + ASSERT_TRUE(ns_Q); + EXPECT_EQ(Cpp::GetNamed("C", ns_Q), N_C) + << "using namespace P2 (which uses N); GetNamed(\"C\", Q) " + "should chase Q -> P2 -> N::C"; + + // Ambiguous using-directives: two namespaces define the same name, + // both made visible by sibling using-directives. GetNamed must + // collapse the ambiguity to nullptr (LookupResult2Decl returns -1 + // for non-single results, GetNamed turns that into nullptr). + Interp->declare(R"( + namespace N3 { class C {}; } + namespace AmbU { using namespace N; using namespace N3; } + )"); + Cpp::TCppScope_t ns_Amb = Cpp::GetNamed("AmbU"); + ASSERT_TRUE(ns_Amb); + EXPECT_EQ(Cpp::GetNamed("C", ns_Amb), nullptr) + << "ambiguous using-directives must return nullptr, not silently " + "pick one"; + + // Anonymous-namespace member visible at TU scope: Within=nullptr + // takes the TUScope short-circuit, which already runs unqualified + // lookup. Pin that the short-circuit still finds the name (the + // synthetic-scope path never runs here). + Interp->declare("namespace { int hidden_y = 42; }"); + EXPECT_TRUE(Cpp::GetNamed("hidden_y")) + << "anonymous-namespace member should be visible at TU scope"; +} + TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetParentScope) { std::string code = R"(namespace N1 { namespace N2 { @@ -722,7 +810,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetParentScope) { TestFixture::CreateInterpreter(); Interp->declare(code); - Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1" DFLT_NULLPTR); + Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1"); 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); @@ -850,13 +938,13 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_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" DFLT_NULLPTR); + auto* VD = Cpp::GetNamed("var"); 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" DFLT_NULLPTR); + auto* VD1 = Cpp::GetNamed("var1"); auto* VT1 = Cpp::GetVariableType(VD1); auto* TC3_A_Decl = Cpp::GetScopeFromType(VT1); auto* A_class = Cpp::GetBaseClass(TC3_A_Decl, 0); @@ -1010,19 +1098,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateNNTPClassTemplate) { ASTContext &C = Interp->getCI()->getASTContext(); Cpp::TCppType_t IntTy = C.IntTy.getAsOpaquePtr(); std::vector args1 = {{IntTy, "5"}}; - EXPECT_TRUE(Cpp::InstantiateTemplate(Decls[0], args1.data(), - /*type_size*/ args1.size() DFLT_FALSE)); - - // C API - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - CXTemplateArgInfo Args1[] = {{IntTy, "5"}}; - auto C_API_SHIM = [&](auto Decl) { - return clang_instantiateTemplate(make_scope(Decl, I), Args1, 1).data[0]; - }; - EXPECT_NE(C_API_SHIM(Decls[0]), nullptr); - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); + EXPECT_TRUE(Cpp::InstantiateTemplate(Decls[0], args1)); } TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateVarTemplate) { @@ -1035,11 +1111,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() DFLT_FALSE); - EXPECT_TRUE(isa((Decl*)Instance1)); - auto* VD = cast((Decl*)Instance1); + auto* Instance1 = Cpp::InstantiateTemplate(Decls[0], args1); + EXPECT_TRUE(isa(static_cast(Instance1))); + auto* VD = cast(static_cast(Instance1)); VarTemplateDecl* VDTD1 = VD->getSpecializedTemplate(); EXPECT_TRUE(VDTD1->isThisDeclarationADefinition()); TemplateArgument TA1 = (*VD->getTemplateArgsAsWritten())[0].getArgument(); @@ -1056,11 +1130,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() DFLT_FALSE); - EXPECT_TRUE(isa((Decl*)Instance1)); - FunctionDecl* FD = cast((Decl*)Instance1); + auto* Instance1 = Cpp::InstantiateTemplate(Decls[0], args1); + EXPECT_TRUE(isa(static_cast(Instance1))); + FunctionDecl* FD = cast(static_cast(Instance1)); FunctionDecl* FnTD1 = FD->getTemplateInstantiationPattern(); EXPECT_TRUE(FnTD1->isThisDeclarationADefinition()); TemplateArgument TA1 = FD->getTemplateSpecializationArgs()->get(0); @@ -1069,14 +1141,13 @@ template T TrivialFnTemplate() { return T(); } TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateTemplateFunctionFromString) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector interpreter_args = {"-include", "new"}; TestFixture::CreateInterpreter(interpreter_args); std::string code = R"(#include )"; Interp->process(code); const char* str = "std::make_unique"; - auto* Instance1 = (Decl*)Cpp::InstantiateTemplateFunctionFromString(str); + auto* Instance1 = + static_cast(Cpp::InstantiateTemplateFunctionFromString(str)); EXPECT_TRUE(Instance1); } @@ -1124,29 +1195,27 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateTemplate) { ASTContext &C = Interp->getCI()->getASTContext(); std::vector args1 = {C.IntTy.getAsOpaquePtr()}; - auto* Instance1 = - Cpp::InstantiateTemplate(Decls[0], args1.data(), - /*type_size*/ args1.size() DFLT_FALSE); - EXPECT_TRUE(isa((Decl*)Instance1)); + auto* Instance1 = Cpp::InstantiateTemplate(Decls[0], args1); + EXPECT_TRUE( + isa(static_cast(Instance1))); auto *CTSD1 = static_cast(Instance1); EXPECT_TRUE(CTSD1->hasDefinition()); TemplateArgument TA1 = CTSD1->getTemplateArgs().get(0); EXPECT_TRUE(TA1.getAsType()->isIntegerType()); EXPECT_TRUE(CTSD1->hasDefinition()); - auto Instance2 = Cpp::InstantiateTemplate(Decls[1], nullptr, - /*type_size*/ 0 DFLT_FALSE); - EXPECT_TRUE(isa((Decl*)Instance2)); + auto Instance2 = Cpp::InstantiateTemplate(Decls[1], {}); + EXPECT_TRUE( + isa(static_cast(Instance2))); auto *CTSD2 = static_cast(Instance2); EXPECT_TRUE(CTSD2->hasDefinition()); TemplateArgument TA2 = CTSD2->getTemplateArgs().get(0); EXPECT_TRUE(TA2.getAsType()->isIntegerType()); std::vector args3 = {C.IntTy.getAsOpaquePtr()}; - auto* Instance3 = - Cpp::InstantiateTemplate(Decls[2], args3.data(), - /*type_size*/ args3.size() DFLT_FALSE); - EXPECT_TRUE(isa((Decl*)Instance3)); + auto* Instance3 = Cpp::InstantiateTemplate(Decls[2], args3); + EXPECT_TRUE( + isa(static_cast(Instance3))); auto *CTSD3 = static_cast(Instance3); EXPECT_TRUE(CTSD3->hasDefinition()); TemplateArgument TA3_0 = CTSD3->getTemplateArgs().get(0); @@ -1161,11 +1230,10 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateTemplate) { std::vector args4 = {C.IntTy.getAsOpaquePtr(), {C.IntTy.getAsOpaquePtr(), "3"}}; - auto* Instance4 = - Cpp::InstantiateTemplate(Decls[3], args4.data(), - /*type_size*/ args4.size() DFLT_FALSE); + auto* Instance4 = Cpp::InstantiateTemplate(Decls[3], args4); - EXPECT_TRUE(isa((Decl*)Instance4)); + EXPECT_TRUE( + isa(static_cast(Instance4))); auto *CTSD4 = static_cast(Instance4); EXPECT_TRUE(CTSD4->hasDefinition()); TemplateArgument TA4_0 = CTSD4->getTemplateArgs().get(0); @@ -1174,6 +1242,38 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateTemplate) { EXPECT_TRUE(TA4_1.getAsIntegral() == 3); } +TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetClassTemplateArgs) { + std::vector Decls; + std::string code = R"( + template + class function; + + template + class function<_Res(_ArgTypes...)> {}; + + function f; + )"; + + GetAllTopLevelDecls(code, Decls); + + Cpp::TCppScope_t f = + Cpp::GetScopeFromType(Cpp::GetVariableType(Decls.back())); + EXPECT_TRUE(f); + + std::vector tmpl_args; + Cpp::GetClassTemplateArgs(f, tmpl_args); + EXPECT_EQ(tmpl_args.size(), 1); + + EXPECT_EQ(Cpp::GetTypeAsString(tmpl_args[0].m_Type), "int (int)"); + + tmpl_args.clear(); + Cpp::GetClassTemplateInstantiationArgs(f, tmpl_args); + EXPECT_EQ(tmpl_args.size(), 2); + + EXPECT_EQ(Cpp::GetTypeAsString(tmpl_args[0].m_Type), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(tmpl_args[1].m_Type), "int"); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetClassTemplateInstantiationArgs) { std::vector Decls; @@ -1186,9 +1286,9 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, GetAllTopLevelDecls(code, Decls); - auto* v1 = Cpp::GetNamed("v1" DFLT_NULLPTR); - auto* v2 = Cpp::GetNamed("v2" DFLT_NULLPTR); - auto* v3 = Cpp::GetNamed("v3" DFLT_NULLPTR); + auto* v1 = Cpp::GetNamed("v1"); + auto* v2 = Cpp::GetNamed("v2"); + auto* v3 = Cpp::GetNamed("v3"); EXPECT_TRUE(v1 && v2 && v3); auto *v1_class = Cpp::GetScopeFromType(Cpp::GetVariableType(v1)); @@ -1213,8 +1313,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, } TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IncludeVector) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; std::string code = R"( #include #include @@ -1225,8 +1323,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_IncludeVector) { } TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetOperator) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; TestFixture::CreateInterpreter(); @@ -1262,44 +1358,39 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetOperator) { } )"; - Cpp::Declare(code.c_str() DFLT_FALSE); + Cpp::Declare(code.c_str()); std::vector ops; - Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Plus, - ops DFLT_OP_ARITY); + Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Plus, ops); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Minus, - ops DFLT_OP_ARITY); + Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Minus, ops); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Star, - ops DFLT_OP_ARITY); + Cpp::GetOperator(Cpp::GetGlobalScope(), Cpp::Operator::OP_Star, ops); EXPECT_EQ(ops.size(), 0); ops.clear(); // operators defined within a namespace - Cpp::GetOperator(Cpp::GetScope("extra_ops" DFLT_0), Cpp::Operator::OP_Plus, - ops DFLT_OP_ARITY); + Cpp::GetOperator(Cpp::GetScope("extra_ops"), Cpp::Operator::OP_Plus, ops); EXPECT_EQ(ops.size(), 2); ops.clear(); // unary operator - Cpp::GetOperator(Cpp::GetScope("extra_ops" DFLT_0), Cpp::Operator::OP_Tilde, - ops DFLT_OP_ARITY); + Cpp::GetOperator(Cpp::GetScope("extra_ops"), Cpp::Operator::OP_Tilde, ops); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetScope("extra_ops" DFLT_0), Cpp::Operator::OP_Tilde, - ops, Cpp::OperatorArity::kUnary); + Cpp::GetOperator(Cpp::GetScope("extra_ops"), Cpp::Operator::OP_Tilde, ops, + Cpp::OperatorArity::kUnary); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetScope("extra_ops" DFLT_0), Cpp::Operator::OP_Tilde, - ops, Cpp::OperatorArity::kBinary); + Cpp::GetOperator(Cpp::GetScope("extra_ops"), Cpp::Operator::OP_Tilde, ops, + Cpp::OperatorArity::kBinary); EXPECT_EQ(ops.size(), 0); std::string inheritance_code = R"( @@ -1318,15 +1409,13 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetOperator) { } }; )"; - Cpp::Declare(inheritance_code.c_str() DFLT_FALSE); + Cpp::Declare(inheritance_code.c_str()); ops.clear(); - Cpp::GetOperator(Cpp::GetScope("Child" DFLT_0), Cpp::Operator::OP_Plus, - ops DFLT_OP_ARITY); + Cpp::GetOperator(Cpp::GetScope("Child"), Cpp::Operator::OP_Plus, ops); EXPECT_EQ(ops.size(), 1); ops.clear(); - Cpp::GetOperator(Cpp::GetScope("Child" DFLT_0), Cpp::Operator::OP_Minus, - ops DFLT_OP_ARITY); + Cpp::GetOperator(Cpp::GetScope("Child"), Cpp::Operator::OP_Minus, ops); EXPECT_EQ(ops.size(), 1); } diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/CMakeLists.txt b/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/CMakeLists.txt new file mode 100644 index 0000000000000..755f6696e039b --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/CMakeLists.txt @@ -0,0 +1,17 @@ +add_llvm_library(TestDownstreamLib + SHARED + DISABLE_LLVM_LINK_LLVM_DYLIB + BUILDTREE_ONLY + TestDownstreamLib.cpp) +add_dependencies(TestDownstreamLib CppInterOpTableGen) +# NDEBUG mirrors cppyy's Release build so the JitCall::Invoke asserts +# don't drag in extra UND refs. target_compile_options so the flag +# appends after add_llvm_library's `-UNDEBUG`. +target_compile_options(TestDownstreamLib PRIVATE -DNDEBUG) + +set_output_directory(TestDownstreamLib + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/../bin/$/ + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/../bin/$/ +) + +set_target_properties(TestDownstreamLib PROPERTIES FOLDER "Tests") diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/TestDownstreamLib.cpp b/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/TestDownstreamLib.cpp new file mode 100644 index 0000000000000..e27449e042a27 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/TestDownstreamLib.cpp @@ -0,0 +1,33 @@ +#include "TestDownstreamLib.h" + +#include "CppInterOp/Dispatch.h" + +// Per-DSO slot storage, mirroring cppyy-backend's cppinterop_dispatch.cxx. +namespace CppInternal { +namespace DispatchRaw { +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + Ret(*DN) RawTypes = nullptr; +#include "CppInterOp/CppInterOpAPI.inc" +} // namespace DispatchRaw +} // namespace CppInternal + +// ODR-uses the inline JitCall fast path. JC is opaque so the optimizer +// can't DCE the calls at any -O level. The body never runs at test +// time; only the .o's UND-symbol surface matters. +void downstream_link_probe(CppImpl::JitCall* JC) { + JC->Invoke(); + JC->InvokeConstructor(nullptr); + JC->InvokeDestructor(nullptr); +} + +int downstream_verify_trace_slots(const char* libpath) { + if (!CppInternal::Dispatch::LoadDispatchAPI(libpath)) + return 1; + if (!CppInternal::DispatchRaw::CppInterOpTraceJitCallInvokeImpl) + return 2; + if (!CppInternal::DispatchRaw::CppInterOpTraceJitCallInvokeDestructorImpl) + return 3; + if (!CppInternal::DispatchRaw::CppInterOpTraceJitCallInvokeReturnImpl) + return 4; + return 0; +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/TestDownstreamLib.h b/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/TestDownstreamLib.h new file mode 100644 index 0000000000000..e84d1dbdab42c --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLib/TestDownstreamLib.h @@ -0,0 +1,22 @@ +#ifndef UNITTESTS_CPPINTEROP_TESTDOWNSTREAMLIB_TESTDOWNSTREAMLIB_H +#define UNITTESTS_CPPINTEROP_TESTDOWNSTREAMLIB_TESTDOWNSTREAMLIB_H + +// Probe entry that ODR-uses the inline JitCall fast path. JC is opaque +// so the optimizer can't DCE the inlined references at any -O level. +namespace CppImpl { +class JitCall; +} +#ifdef _WIN32 +#define TESTDOWNSTREAM_EXPORT extern "C" __declspec(dllexport) +#else +#define TESTDOWNSTREAM_EXPORT extern "C" __attribute__((visibility("default"))) +#endif +TESTDOWNSTREAM_EXPORT void downstream_link_probe(CppImpl::JitCall* JC); + +/// After LoadDispatchAPI(libpath) succeeds, check every DispatchRaw +/// trace slot in this DSO is non-null. Returns 0 on success, a +/// positive non-zero code on the first failure (1=LoadDispatchAPI +/// failed, 2/3/4=corresponding slot still null). +TESTDOWNSTREAM_EXPORT int downstream_verify_trace_slots(const char* libpath); + +#endif // UNITTESTS_CPPINTEROP_TESTDOWNSTREAMLIB_TESTDOWNSTREAMLIB_H diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLoader.cpp b/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLoader.cpp new file mode 100644 index 0000000000000..c28cfd98aecb2 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/TestDownstreamLoader.cpp @@ -0,0 +1,56 @@ +// dlopens TestDownstreamLib without linking libclangCppInterOp; with +// argv[1] = libclangCppInterOp path, also drives the probe's +// LoadDispatchAPI check to pin the X-macro slot-population contract. + +#include +#include + +#ifdef _WIN32 +#include +using HandleTy = HMODULE; +static HandleTy openLib(const char* p) { return LoadLibraryA(p); } +static void* findSym(HandleTy h, const char* n) { + return reinterpret_cast(GetProcAddress(h, n)); +} +static void closeLib(HandleTy h) { FreeLibrary(h); } +static const char* lastErr() { return "LoadLibrary failed"; } +#else +#include +using HandleTy = void*; +static HandleTy openLib(const char* p) { + return dlopen(p, RTLD_NOW | RTLD_LOCAL); +} +static void* findSym(HandleTy h, const char* n) { return dlsym(h, n); } +static void closeLib(HandleTy h) { dlclose(h); } +static const char* lastErr() { return dlerror(); } +#endif + +#ifndef TEST_DOWNSTREAM_LIB_PATH +#error "TEST_DOWNSTREAM_LIB_PATH must be defined" +#endif + +int main(int argc, char** argv) { + HandleTy h = openLib(TEST_DOWNSTREAM_LIB_PATH); + if (!h) { + std::fprintf(stderr, "open(%s) failed: %s\n", TEST_DOWNSTREAM_LIB_PATH, + lastErr()); + return 1; + } + // argv[1] is the libclangCppInterOp path; verify slot population. + int rc = 0; + if (argc > 1) { + auto* verify = reinterpret_cast( + findSym(h, "downstream_verify_trace_slots")); + if (!verify) { + std::fprintf(stderr, "missing downstream_verify_trace_slots: %s\n", + lastErr()); + rc = 2; + } else { + rc = verify(argv[1]); + if (rc != 0) + std::fprintf(stderr, "downstream_verify_trace_slots -> %d\n", rc); + } + } + closeLib(h); + return rc; +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TracingTests.cpp b/interpreter/CppInterOp/unittests/CppInterOp/TracingTests.cpp index 140edcc7c6a19..e3f1122f43082 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/TracingTests.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/TracingTests.cpp @@ -1,10 +1,13 @@ #include "../../lib/CppInterOp/Tracing.h" +#include "clang/Basic/Version.h" + #include "CppInterOp/CppInterOp.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -18,7 +21,7 @@ using ::testing::Not; // Helper: join the full trace log into a single string for matching. static std::string getFullLog() { std::string result; - for (const auto& entry : TraceInfo::TheTraceInfo->getLog()) + for (const auto& entry : TheTraceInfo->getLog()) result += entry + "\n"; return result; } @@ -29,17 +32,29 @@ static std::string getFullLog() { class TracingTest : public ::testing::Test { protected: + static void SetUpTestSuite() { + saved_death_test_style_ = ::testing::GTEST_FLAG(death_test_style); + ::testing::GTEST_FLAG(death_test_style) = "threadsafe"; + } + static void TearDownTestSuite() { + ::testing::GTEST_FLAG(death_test_style) = saved_death_test_style_; + } void SetUp() override { - if (TraceInfo::TheTraceInfo) - TraceInfo::TheTraceInfo->clear(); - TraceInfo::TheTraceInfo = nullptr; + if (TheTraceInfo) + TheTraceInfo->clear(); + TheTraceInfo = nullptr; InitTracing(); } + +private: + static std::string saved_death_test_style_; }; +std::string TracingTest::saved_death_test_style_; + class NoTracingTest : public ::testing::Test { protected: - void SetUp() override { TraceInfo::TheTraceInfo = nullptr; } + void SetUp() override { TheTraceInfo = nullptr; } }; // --------------------------------------------------------------------------- @@ -133,13 +148,76 @@ TEST_F(TracingTest, CountParamsHandlesVoidParamList) { EXPECT_EQ(TR::countParams("void foo(int, std::vector&)"), 2U); } +TEST_F(TracingTest, OutMaskLookupMatchesTd) { + using TR = CppInterOp::Tracing::TraceRegion; + // Public APIs marked OutArg<...> in CppInterOp.td. + EXPECT_EQ(TR::lookupOutMask("GetAllCppNames"), 0b10ULL); // arg 1 OUT + EXPECT_EQ(TR::lookupOutMask("CodeComplete"), 0b1ULL); // arg 0 OUT + EXPECT_EQ(TR::lookupOutMask("GetOperator"), 0b100ULL); // arg 2 OUT + // Public APIs without OUT args. + EXPECT_EQ(TR::lookupOutMask("Process"), 0ULL); + // Test helpers / unknown names produce nullopt -- the runtime check + // skips them, so helpers like FillHandles can still use INTEROP_OUT. + EXPECT_FALSE(TR::lookupOutMask("FillHandles").has_value()); +} + +TEST_F(TracingTest, ComputeOutMaskFromArgTypes) { + using TR = CppInterOp::Tracing::TraceRegion; + using OP = CppInterOp::Tracing::OutParam; + EXPECT_EQ((TR::computeOutMask()), 0ULL); + EXPECT_EQ((TR::computeOutMask()), 0b10ULL); + EXPECT_EQ((TR::computeOutMask()), 0b101ULL); +} + +TEST_F(TracingTest, FormatOutMaskMismatchMessage) { + // Pure formatter; non-death test gives real coverage on the diagnostic + // path that EXPECT_DEATH hides inside a forked child. + using TR = CppInterOp::Tracing::TraceRegion; + std::string Msg = TR::formatOutMaskMismatchMessage("FooApi", 0b10ULL, 0b1ULL); + EXPECT_THAT(Msg, HasSubstr("INTEROP_OUT coverage mismatch in 'FooApi'")); + EXPECT_THAT(Msg, HasSubstr("mask 0x2")); + EXPECT_THAT(Msg, HasSubstr("args 0x1")); +} + +// Scalar-pointer OUT helper: a function with a `bool*` out-param can +// now wrap it with INTEROP_OUT and the reproducer renders `nullptr`. +intptr_t ScalarOutDummy(const char* code, bool* err) { + INTEROP_TRACE(code, INTEROP_OUT(err)); + if (err) + *err = false; + return INTEROP_RETURN((intptr_t)42); +} + +TEST_F(TracingTest, ScalarPointerOutRendersAsNullptr) { + bool err = true; + EXPECT_EQ(ScalarOutDummy("x", &err), 42); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::ScalarOutDummy(\"x\", nullptr)")); +} + +// Shadows the public API name Cpp::Process (which is not OUT in the .td) +// to drive lookupOutMask("Process") -> 0 while wrapping the arg with +// INTEROP_OUT yields actual=1; the mismatch must fire the OUT-coverage +// assert, not silently pass. +void Process(int* x) { + INTEROP_TRACE(INTEROP_OUT(x)); + (void)x; +} + +TEST_F(TracingTest, DetectOutCoverageMismatch) { +#ifndef NDEBUG + int dummy = 0; + EXPECT_DEATH({ Process(&dummy); }, "INTEROP_OUT coverage mismatch"); +#endif +} + TEST_F(TracingTest, ValidAnnotatedBranch) { EXPECT_EQ(AnnotatedFunction(5), 10); } TEST_F(TracingTest, VoidFunctionTracing) { VoidFunc(); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + auto output = TheTraceInfo->getLastLogEntry(); EXPECT_THAT(output, HasSubstr("Cpp::VoidFunc();")); } @@ -160,16 +238,17 @@ void WithOutParamTrace(std::vector& out) { TEST_F(TracingTest, TraceWithNoArgs) { NoArgTrace(); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + auto output = TheTraceInfo->getLastLogEntry(); EXPECT_THAT(output, HasSubstr("Cpp::NoArgTrace();")); } TEST_F(TracingTest, TraceWithOutParamArg) { + // OUT pointer-container: preamble decl + alias at the call site. std::vector v; WithOutParamTrace(v); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); - // OutParam should not appear in the arg list. - EXPECT_THAT(output, HasSubstr("Cpp::WithOutParamTrace();")); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("std::vector _out0;")); + EXPECT_THAT(output, HasSubstr("Cpp::WithOutParamTrace(_out0);")); EXPECT_EQ(v.size(), 1u); } @@ -217,15 +296,95 @@ TEST_F(TracingTest, ArgFormattingHandle) { EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingHandle(v1)")); } +TEST_F(TracingTest, ArgFormattingUnregisteredHandleAnnotated) { + // A non-null pointer the tracer never produced -- the call is the + // first reference to it. The reproducer cannot bind it to a name, + // so flag the gap with `nullptr /*unknown*/` rather than silently + // collapsing to a bare `nullptr` (the previously-dead branch in + // ReproBuffer::append; lookupHandle now returns "" for unregistered). + // 0xDEADBEEF is unsigned int (>= 2^31) -- cast through uintptr_t + // to keep MSVC's C4312 silent on 64-bit, where void* is wider. + FuncTakingHandle(reinterpret_cast(static_cast(0xDEADBEEF))); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingHandle(nullptr /*unknown*/)")); +} + +TEST_F(TracingTest, ArgFormattingNullHandlePlain) { + // Genuine null renders as plain `nullptr` -- never carries the + // /*unknown*/ annotation, which is reserved for unregistered + // non-null pointers. + FuncTakingHandle(nullptr); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingHandle(nullptr)")); + EXPECT_THAT(output, Not(HasSubstr("/*unknown*/"))); +} + TEST_F(TracingTest, ArgFormattingString) { + // Plain printable-ASCII content takes the `"..."` branch -- the + // raw-literal form is reserved for content that would mis-encode as + // a plain literal (see ArgFormattingString* tests below). FuncTakingString("hello"); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + auto output = TheTraceInfo->getLastLogEntry(); EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingString(\"hello\")")); } +TEST_F(TracingTest, ArgFormattingEmptyStringIsPlain) { + // Empty string takes the no-special-char branch -- emit `""` rather + // than `R"CPPI()CPPI"`. + FuncTakingString(""); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingString(\"\")")); + EXPECT_THAT(output, Not(HasSubstr("R\"CPPI("))); +} + +TEST_F(TracingTest, ArgFormattingStringWithEmbeddedDoubleQuote) { + // Double quote forces the raw-literal fallback -- a plain `"..."` + // would terminate the string at the embedded quote. + FuncTakingString("a\"b"); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("R\"CPPI(a\"b)CPPI\"")); +} + +TEST_F(TracingTest, ArgFormattingStringWithEmbeddedBackslash) { + // Backslash forces raw-literal -- in a plain literal it would start + // an escape sequence. + FuncTakingString("a\\b"); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("R\"CPPI(a\\b)CPPI\"")); +} + +TEST_F(TracingTest, ArgFormattingStringWithControlChar) { + // Newline (a control char, < 0x20) forces raw-literal so the line + // doesn't get split mid-string in the reproducer. + FuncTakingString("a\nb"); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("R\"CPPI(a\nb)CPPI\"")); +} + +TEST_F(TracingTest, ArgFormattingMultiLineStringUsesRawLiteral) { + // Multi-line source blocks (typical of Cpp::Declare / Cpp::Process) + // have several embedded newlines; raw-literal preserves the layout + // verbatim so the reproducer compiles unchanged. + FuncTakingString("void f() {\n return 42;\n}\n"); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, + HasSubstr("R\"CPPI(void f() {\n return 42;\n}\n)CPPI\"")); +} + +TEST_F(TracingTest, ArgFormattingStringWithHighBitStaysPlain) { + // High-bit (UTF-8) bytes are >= 0x20 and != 0x7f, so they keep the + // plain-literal path -- the source bytes pass through unchanged and + // the reproducer compiles with the same encoding. + FuncTakingString("r\xc3\xa9sum\xc3\xa9"); // "résumé" in UTF-8 + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, + HasSubstr("Cpp::FuncTakingString(\"r\xc3\xa9sum\xc3\xa9\")")); + EXPECT_THAT(output, Not(HasSubstr("R\"CPPI("))); +} + TEST_F(TracingTest, ArgFormattingInt) { FuncTakingInt(42); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + auto output = TheTraceInfo->getLastLogEntry(); EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingInt(42)")); } @@ -233,18 +392,82 @@ TEST_F(TracingTest, ArgFormattingMixed) { void* h = FuncReturningHandle(); FuncTakingMixed(h, "world", 99); auto output = getFullLog(); - EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingMixed(v1, \"world\", 99)")); + EXPECT_THAT(output, + HasSubstr("Cpp::FuncTakingMixed(v1, \"world\", 99)")); +} + +TEST_F(TracingTest, ArgFormattingOutParamAliased) { + // OUT alias preserves left-to-right argument order: (v1, _out0). + void* h = FuncReturningHandle(); + std::vector out; + FuncWithHandleAndOut(h, out); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("std::vector _out0;")); + EXPECT_THAT(output, HasSubstr("Cpp::FuncWithHandleAndOut(v1, _out0);")); +} + +void FuncWithTwoOuts(std::vector& a, std::vector& b) { + INTEROP_TRACE(INTEROP_OUT(a), INTEROP_OUT(b)); + a.push_back((void*)0x11); + b.push_back((void*)0x22); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, ArgFormattingTwoOutParamsGetDistinctIndices) { + // Two OUTs on one call get distinct indices in arg order. + std::vector a, b; + FuncWithTwoOuts(a, b); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("std::vector _out0;")); + EXPECT_THAT(output, HasSubstr("std::vector _out1;")); + EXPECT_THAT(output, HasSubstr("Cpp::FuncWithTwoOuts(_out0, _out1);")); } -TEST_F(TracingTest, ArgFormattingOutParamSkipped) { - // OutParam should not appear in the formatted args, - // but regular args before it should. +TEST_F(TracingTest, ArgFormattingOutParamDistinctSourcesGetDistinctIndices) { + // Two calls with DIFFERENT source vectors get distinct `_outN` + // slots, both declared in the preamble. + std::vector a, b; + WithOutParamTrace(a); + WithOutParamTrace(b); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("std::vector _out0;")); + EXPECT_THAT(output, HasSubstr("std::vector _out1;")); + EXPECT_THAT(output, HasSubstr("Cpp::WithOutParamTrace(_out0);")); + EXPECT_THAT(output, HasSubstr("Cpp::WithOutParamTrace(_out1);")); +} + +TEST_F(TracingTest, ArgFormattingOutParamSameSourceAliasesSlot) { + // Reusing the same source vector across calls reuses the same + // `_outN` -- one preamble decl, both call sites alias it -- so the + // reproducer faithfully replays the API's append-on-call contract. + std::vector v; + WithOutParamTrace(v); + WithOutParamTrace(v); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("std::vector _out0;")); + EXPECT_THAT(output, Not(HasSubstr("std::vector _out1;"))); + EXPECT_THAT(output, HasSubstr("Cpp::WithOutParamTrace(_out0);")); + EXPECT_THAT(output, Not(HasSubstr("Cpp::WithOutParamTrace(_out1);"))); +} + +TEST_F(TracingTest, OutParamElementUsableInLaterCall) { + // The OUT-param element decl makes the handle bind in the + // reproducer: a downstream call that takes one of the elements as + // input renders to a name that's actually declared earlier. void* h = FuncReturningHandle(); std::vector out; FuncWithHandleAndOut(h, out); + ASSERT_FALSE(out.empty()); + FuncTakingHandle(out[0]); auto output = getFullLog(); - // Should show only the handle arg, not the OutParam. - EXPECT_THAT(output, HasSubstr("Cpp::FuncWithHandleAndOut(v1)")); + // The element's decl reads `_out0[0]` and binds a handle name (vN); + // the downstream call references the same name. + TraceInfo& TI = *TheTraceInfo; + std::string elem = TI.lookupHandle(out[0]); + ASSERT_FALSE(elem.empty()); + EXPECT_THAT(output, + HasSubstr("void* " + elem + " = _out0.size() > 0 ? _out0[0]")); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingHandle(" + elem + ")")); } // Test: when two functions return the same pointer, the second should not @@ -288,7 +511,7 @@ void* ReturnNull() { TEST_F(TracingTest, NullptrReturnAnnotated) { ReturnNull(); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + auto output = TheTraceInfo->getLastLogEntry(); EXPECT_THAT(output, HasSubstr("/*nullptr*/")); EXPECT_THAT(output, HasSubstr("Cpp::ReturnNull()")); } @@ -302,24 +525,260 @@ int ReturnInt() { TEST_F(TracingTest, NonPointerReturnNoAnnotation) { ReturnInt(); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + auto output = TheTraceInfo->getLastLogEntry(); EXPECT_THAT(output, Not(HasSubstr("auto"))); EXPECT_THAT(output, Not(HasSubstr("nullptr"))); EXPECT_THAT(output, HasSubstr("Cpp::ReturnInt()")); } -// Test: non-streamable types (e.g. std::vector) are formatted as placeholders. -void FuncTakingVector(const std::vector& args) { +// Vector input args are formatted as braced init lists of +// recursively-formatted elements -- no longer the `{...}` placeholder +// that produced uncompilable reproducers. +void FuncTakingStrVector(const std::vector& args) { + INTEROP_TRACE(args); + return INTEROP_VOID_RETURN(); +} + +void FuncTakingIntVector(const std::vector& args) { INTEROP_TRACE(args); return INTEROP_VOID_RETURN(); } -TEST_F(TracingTest, ArgFormattingNonStreamable) { +void FuncTakingNestedVector(const std::vector>& args) { + INTEROP_TRACE(args); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, ArgFormattingVectorOfStrings) { + // Exercises the per-element loop with the `, ` separator (>1 element) + // and the const-char* overload of append (plain-quote form). std::vector v = {"a", "b"}; - FuncTakingVector(v); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); - // Non-streamable types should get a {...} placeholder. - EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingVector({...})")); + FuncTakingStrVector(v); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingStrVector({\"a\", \"b\"})")); +} + +TEST_F(TracingTest, ArgFormattingVectorOfInts) { + // Pins the integer-element path through append(int). + std::vector v = {1, 2, 3}; + FuncTakingIntVector(v); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingIntVector({1, 2, 3})")); +} + +TEST_F(TracingTest, ArgFormattingEmptyVector) { + // Empty vector skips the loop -- emit `{}` with no separator. Pins + // the First-flag branch where it never flips. + std::vector v; + FuncTakingIntVector(v); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingIntVector({})")); +} + +TEST_F(TracingTest, ArgFormattingNestedVector) { + // Recursive call: append(vector>) dispatches to + // append(vector) for each element. + std::vector> v = {{1, 2}, {3}}; + FuncTakingNestedVector(v); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingNestedVector({{1, 2}, {3}})")); +} + +// --------------------------------------------------------------------------- +// Tests: returned std::vector -- elements registered as handles +// --------------------------------------------------------------------------- + +std::vector ReturnPointerVector() { + INTEROP_TRACE(); + std::vector v; + v.push_back((void*)0x101); + v.push_back((void*)0x202); + return INTEROP_RETURN(v); +} + +std::vector ReturnEmptyPointerVector() { + INTEROP_TRACE(); + std::vector v; + return INTEROP_RETURN(v); +} + +std::vector ReturnVectorWithNullElement() { + INTEROP_TRACE(); + std::vector v; + v.push_back((void*)0x303); + v.push_back(nullptr); + v.push_back((void*)0x404); + return INTEROP_RETURN(v); +} + +TEST_F(TracingTest, ReturnedPointerVectorProducerAndConsumer) { + // Producer side: `auto _retN = ...` prelude + bounds-guarded + // `_retN[i]` per element. Consumer side: a subsequent call taking + // one of those pointers prints it by the same handle name (vN), so + // the reproducer's producing line and the consumer's argument refer + // to the same binding. + TraceInfo& TI = *TheTraceInfo; + auto v = ReturnPointerVector(); + std::string h0 = TI.lookupHandle(v[0]); + std::string h1 = TI.lookupHandle(v[1]); + ASSERT_NE(h0, "nullptr"); + ASSERT_NE(h1, "nullptr"); + + FuncTakingHandle(v[0]); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("auto _ret0 = Cpp::ReturnPointerVector()")); + EXPECT_THAT(output, + HasSubstr("void* " + h0 + " = _ret0.size() > 0 ? _ret0[0]")); + EXPECT_THAT(output, + HasSubstr("void* " + h1 + " = _ret0.size() > 1 ? _ret0[1]")); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingHandle(" + h0 + ")")); +} + +TEST_F(TracingTest, ReturnedPointerVectorEmptyEmitsNoDecls) { + // Empty vector returns the placeholder + zero element decls. The + // RetDecls loop never appends, so the only log line for this call is + // the call itself. + size_t before = TheTraceInfo->getLog().size(); + ReturnEmptyPointerVector(); + size_t after = TheTraceInfo->getLog().size(); + EXPECT_EQ(after - before, 1u) + << "Empty vector return should emit only the call line, no element decls"; + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, + HasSubstr("auto _ret0 = Cpp::ReturnEmptyPointerVector()")); +} + +TEST_F(TracingTest, ReturnedPointerVectorDuplicateElementSkipsDecl) { + // If two returned vectors share an element pointer, only the first + // call registers (and emits a decl for) it; the second skips + // because lookupHandle no longer says "nullptr". + ReturnPointerVector(); + ReturnPointerVector(); + auto output = getFullLog(); + // First call's decls are present; the second call's decls are + // suppressed (the elements already have names). + EXPECT_THAT(output, HasSubstr("auto _ret0 = Cpp::ReturnPointerVector()")); + EXPECT_THAT(output, HasSubstr("auto _ret1 = Cpp::ReturnPointerVector()")); + // _ret1 should have no `void* vN = _ret1[i]` lines for elements that + // were already registered. + EXPECT_THAT(output, Not(HasSubstr("_ret1.size() > 0 ? _ret1[0]"))); +} + +TEST_F(TracingTest, ReturnedPointerVectorNullElementSkipsDecl) { + // Null elements are skipped (`if (!p) continue;`) so the reproducer + // doesn't emit a decl that would shadow the global `nullptr`. The + // surrounding non-null elements still get their decls at their + // original indices. + ReturnVectorWithNullElement(); + auto output = getFullLog(); + EXPECT_THAT(output, + HasSubstr("auto _ret0 = Cpp::ReturnVectorWithNullElement()")); + EXPECT_THAT(output, HasSubstr("_ret0.size() > 0 ? _ret0[0] : nullptr")); + EXPECT_THAT(output, Not(HasSubstr("_ret0.size() > 1 ? _ret0[1]"))); + EXPECT_THAT(output, HasSubstr("_ret0.size() > 2 ? _ret0[2] : nullptr")); +} + +// --------------------------------------------------------------------------- +// Reproducer-compilability fixtures: enum cast, escaped strings, const void*. +// Each one used to emit "?" or invalid C++ in the captured trace; the tests +// pin the new behaviour so the auto-generated reproducer compiles. +// --------------------------------------------------------------------------- + +namespace tracing_test_enum { +enum Flavor : unsigned char { Red = 1, Green = 2, Blue = 3 }; +} // namespace tracing_test_enum + +void FuncTakingEnum(tracing_test_enum::Flavor f) { + INTEROP_TRACE(f); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, ArgFormattingEnumStaticCast) { + // Enums route through the std::is_enum_v overload and emit + // "static_cast(N)" so the reproducer compiles. Used to + // emit "?" via the catch-all template. + FuncTakingEnum(tracing_test_enum::Blue); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("static_cast<")); + EXPECT_THAT(output, HasSubstr("Flavor")); + EXPECT_THAT(output, HasSubstr(">(3)")); + EXPECT_THAT(output, Not(HasSubstr("?"))); +} + +void FuncTakingTrickyString(const char* s) { + INTEROP_TRACE(s); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, ArgFormattingStringEscapes) { + // Strings are emitted as a raw literal R"CPPI(...)CPPI" so newlines, + // quotes, and backslashes flow through verbatim and the reproducer + // compiles without a separate escape pass. + FuncTakingTrickyString("line1\nline2\twith \"quotes\" and a \\backslash"); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("R\"CPPI(")); + EXPECT_THAT(output, HasSubstr(")CPPI\"")); + // Verbatim content survives -- the raw literal preserves the + // newline / tab / quote / backslash bytes as-is. + EXPECT_THAT(output, + HasSubstr("line1\nline2\twith \"quotes\" and a \\backslash")); +} + +const void* FuncReturningConstHandle() { + INTEROP_TRACE(); + return INTEROP_RETURN((const void*)0xC0FFEE); +} + +void FuncTakingConstHandle(const void* h) { + INTEROP_TRACE(h); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, ArgFormattingConstVoidHandle) { + // const void* takes the second overload; before this fix the catch-all + // template absorbed "const T&" matches and emitted "?". + const void* h = FuncReturningConstHandle(); + FuncTakingConstHandle(h); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("auto v1 = Cpp::FuncReturningConstHandle()")); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingConstHandle(v1)")); + EXPECT_THAT(output, Not(HasSubstr("?"))); +} + +TEST_F(TracingTest, ArgFormattingConstVoidNullptr) { + // null const void* must emit literal nullptr, not the empty handle name. + FuncTakingConstHandle(nullptr); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingConstHandle(nullptr)")); +} + +void FuncTakingTemplateArg(Cpp::TemplateArgInfo tai) { + INTEROP_TRACE(tai); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, ArgFormattingTemplateArgInfoTypeAndIntegralValue) { + // m_Type renders as the registered handle name; m_IntegralValue + // takes the raw-string path. + TraceInfo& TI = *TheTraceInfo; + void* tp = reinterpret_cast(static_cast(0x12340000)); + std::string h = TI.getOrRegisterHandle(tp); + Cpp::TemplateArgInfo tai{tp, "5"}; + FuncTakingTemplateArg(tai); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingTemplateArg(Cpp::" + "TemplateArgInfo{" + + h + ", \"5\"})")); +} + +TEST_F(TracingTest, ArgFormattingTemplateArgInfoNullFields) { + // Null m_IntegralValue must round-trip to `nullptr` (the + // constructor's default), not the empty string `""`. + Cpp::TemplateArgInfo tai{nullptr, nullptr}; + FuncTakingTemplateArg(tai); + auto output = TheTraceInfo->getLastLogEntry(); + EXPECT_THAT(output, HasSubstr("Cpp::FuncTakingTemplateArg(Cpp::" + "TemplateArgInfo{nullptr, nullptr})")); } // --------------------------------------------------------------------------- @@ -327,7 +786,7 @@ TEST_F(TracingTest, ArgFormattingNonStreamable) { // --------------------------------------------------------------------------- TEST_F(TracingTest, HandleRegistry) { - TraceInfo& TI = *TraceInfo::TheTraceInfo; + TraceInfo& TI = *TheTraceInfo; void* ptr1 = (void*)0x1234; void* ptr2 = (void*)0x5678; @@ -344,6 +803,12 @@ TEST_F(TracingTest, HandleRegistry) { EXPECT_EQ(TI.lookupHandle(nullptr), "nullptr"); + // An unregistered non-null pointer returns "" so callers can tell + // "the tracer has no handle for this" from "this is the literal + // nullptr". The two cases used to collide on "nullptr" and the + // distinction is what lets ReproBuffer annotate unknown args. + EXPECT_EQ(TI.lookupHandle((const void*)0xBADADD), ""); + // getOrRegisterHandle(nullptr) should return empty, not register. EXPECT_EQ(TI.getOrRegisterHandle(nullptr), ""); } @@ -353,7 +818,7 @@ TEST_F(TracingTest, HandleRegistry) { // --------------------------------------------------------------------------- TEST_F(TracingTest, TimerStackPushPop) { - TraceInfo& TI = *TraceInfo::TheTraceInfo; + TraceInfo& TI = *TheTraceInfo; llvm::Timer& T1 = TI.getTimer("FuncA"); llvm::Timer& T2 = TI.getTimer("FuncB"); @@ -381,7 +846,7 @@ TEST_F(TracingTest, TimerStackPushPop) { } TEST_F(TracingTest, ClearWithRunningTimers) { - TraceInfo& TI = *TraceInfo::TheTraceInfo; + TraceInfo& TI = *TheTraceInfo; llvm::Timer& T1 = TI.getTimer("RunningTimer"); TI.pushTimer(&T1); @@ -398,19 +863,19 @@ TEST_F(TracingTest, ClearWithRunningTimers) { TEST(StartTracingActivation, ActivatesIfNotActive) { // Save and clear TheTraceInfo to simulate tracing not being active. - auto* saved = TraceInfo::TheTraceInfo; - TraceInfo::TheTraceInfo = nullptr; + auto* saved = TheTraceInfo; + TheTraceInfo = nullptr; // StartTracing should call InitTracing (covers line 151). std::string Path = CppInterOp::Tracing::StartTracing(/*WriteOnStdErr=*/false); - ASSERT_NE(TraceInfo::TheTraceInfo, nullptr); + ASSERT_NE(TheTraceInfo, nullptr); ASSERT_FALSE(Path.empty()); CppInterOp::Tracing::StopTracing(); llvm::sys::fs::remove(Path); // Restore so other tests aren't affected. - TraceInfo::TheTraceInfo = saved; + TheTraceInfo = saved; } // --------------------------------------------------------------------------- @@ -449,12 +914,88 @@ TEST_F(TracingTest, ChainedReproducerLogic) { EXPECT_THAT(output, HasSubstr("auto v1 =")); } +// --------------------------------------------------------------------------- +// Tests: nested-call suppression +// --------------------------------------------------------------------------- + +void* NestedInnerReturn() { + INTEROP_TRACE(); + return INTEROP_RETURN((void*)0x2000); +} + +void* NestedOuterReturn() { + INTEROP_TRACE(); + return INTEROP_RETURN(NestedInnerReturn()); +} + +TEST_F(TracingTest, NestedCallSuppressedButHandleRegistered) { + // The inner call doesn't appear in the log, yet its handle is + // registered so the outer call's `auto vN = ...` line binds the + // pointer the inner produced. + void* h = NestedOuterReturn(); + TraceInfo& TI = *TheTraceInfo; + EXPECT_FALSE(TI.lookupHandle(h).empty()); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("auto v1 = Cpp::NestedOuterReturn()")); + EXPECT_THAT(output, Not(HasSubstr("Cpp::NestedInnerReturn"))); +} + +void NestedInnerNoOp() { + INTEROP_TRACE(); + return INTEROP_VOID_RETURN(); +} + +void NestedOuterWithOut(std::vector& out) { + INTEROP_TRACE(INTEROP_OUT(out)); + NestedInnerNoOp(); + out.push_back((void*)0x3000); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, NestedSuppressionDoesNotLeakOutCounter) { + // captureArg must not bump m_OutCount for nested OUT-params -- + // otherwise the outer call would emit `_out1` while the inner + // claimed `_out0` silently. + std::vector v; + NestedOuterWithOut(v); + auto output = getFullLog(); + EXPECT_THAT(output, HasSubstr("std::vector _out0;")); + EXPECT_THAT(output, HasSubstr("Cpp::NestedOuterWithOut(_out0);")); + EXPECT_THAT(output, Not(HasSubstr("_out1"))); +} + +// --------------------------------------------------------------------------- +// Tests: ctor-side placeholder for aborted calls +// --------------------------------------------------------------------------- + +void PlaceholderProbe() { + INTEROP_TRACE(); + // Mid-call: the ctor-emitted placeholder is already in the log. + auto& log = TheTraceInfo->getLog(); + ASSERT_FALSE(log.empty()); + EXPECT_THAT(log.back(), HasSubstr("Cpp::PlaceholderProbe")); + EXPECT_THAT(log.back(), HasSubstr("[aborted before return]")); + return INTEROP_VOID_RETURN(); +} + +TEST_F(TracingTest, PlaceholderInCtorReplacedByDtor) { + // Mid-call assertions live inside PlaceholderProbe; here we only + // verify the replacement: after the call returns, the slot reads + // the completed `// [N ns]` line, not the placeholder. + PlaceholderProbe(); + auto& log = TheTraceInfo->getLog(); + ASSERT_FALSE(log.empty()); + EXPECT_THAT(log.back(), Not(HasSubstr("aborted before return"))); + EXPECT_THAT(log.back(), + ::testing::MatchesRegex(R"(.*Cpp::PlaceholderProbe.*ns\].*)")); +} + // --------------------------------------------------------------------------- // Tests: out-parameters // --------------------------------------------------------------------------- TEST_F(TracingTest, OutParamRegistersHandles) { - TraceInfo& TI = *TraceInfo::TheTraceInfo; + TraceInfo& TI = *TheTraceInfo; std::vector results; FillHandles(results); @@ -466,7 +1007,7 @@ TEST_F(TracingTest, OutParamRegistersHandles) { } TEST_F(TracingTest, OutParamHandlesUsableInLaterCalls) { - TraceInfo& TI = *TraceInfo::TheTraceInfo; + TraceInfo& TI = *TheTraceInfo; std::vector results; FillHandles(results); @@ -493,14 +1034,17 @@ void FillStrings(std::vector& out) { } TEST_F(TracingTest, OutParamNonPointerElementType) { + // Non-pointer OUTs stay on the legacy skip path -- no `_outN` + // preamble is emitted and the call has zero args. std::vector results; FillStrings(results); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + auto output = getFullLog(); EXPECT_EQ(results.size(), 2u); EXPECT_EQ(results[0], "hello"); EXPECT_EQ(results[1], "world"); EXPECT_THAT(output, HasSubstr("Cpp::FillStrings();")); + EXPECT_THAT(output, Not(HasSubstr("_out0"))); } TEST_F(NoTracingTest, OutParamNonPointerDisabled) { @@ -524,7 +1068,7 @@ TEST_F(TracingTest, WriteToFileProducesValidReproducer) { return INTEROP_VOID_RETURN(); } - TraceInfo& TI = *TraceInfo::TheTraceInfo; + TraceInfo& TI = *TheTraceInfo; std::string Path = TI.writeToFile(); ASSERT_FALSE(Path.empty()); @@ -534,9 +1078,17 @@ TEST_F(TracingTest, WriteToFileProducesValidReproducer) { std::string content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + // Both prologue branches must travel together. + EXPECT_THAT(content, HasSubstr("#ifdef CPPINTEROP_USE_DISPATCH")); + EXPECT_THAT(content, HasSubstr("#include ")); EXPECT_THAT(content, HasSubstr("#include ")); + EXPECT_THAT(content, HasSubstr("CPPINTEROP_API_FUNC")); + EXPECT_THAT(content, HasSubstr("Cpp::LoadDispatchAPI")); EXPECT_THAT(content, HasSubstr("void reproducer()")); EXPECT_THAT(content, HasSubstr("Cpp::")); + EXPECT_THAT(content, HasSubstr("Build context")); + EXPECT_THAT(content, HasSubstr("build type:")); + EXPECT_THAT(content, HasSubstr("cmake-cache:")); // Clean up. llvm::sys::fs::remove(Path); @@ -546,7 +1098,7 @@ TEST_F(TracingTest, WriteToFileContainsOutParamCalls) { std::vector results; FillHandles(results); - TraceInfo& TI = *TraceInfo::TheTraceInfo; + TraceInfo& TI = *TheTraceInfo; std::string Path = TI.writeToFile(); ASSERT_FALSE(Path.empty()); @@ -554,7 +1106,31 @@ TEST_F(TracingTest, WriteToFileContainsOutParamCalls) { std::string content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); - EXPECT_THAT(content, HasSubstr("Cpp::FillHandles();")); + // Persisted reproducer mirrors the in-memory log. + EXPECT_THAT(content, HasSubstr("std::vector _out0;")); + EXPECT_THAT(content, HasSubstr("Cpp::FillHandles(_out0);")); + + llvm::sys::fs::remove(Path); +} + +TEST_F(TracingTest, WriteToFileSuppressesSelfTrace) { + // Without the m_Dumping guard, GetVersion/GetBuildInfo (both + // INTEROP_TRACE'd) would leak into the reproducer body. + AnnotatedFunction(7); + AnnotatedFunction(8); + + TraceInfo& TI = *TheTraceInfo; + size_t before = TI.getLog().size(); + std::string Path = TI.writeToFile(); + ASSERT_FALSE(Path.empty()); + EXPECT_EQ(TI.getLog().size(), before); + + std::ifstream ifs(Path); + std::string content((std::istreambuf_iterator(ifs)), + std::istreambuf_iterator()); + EXPECT_THAT(content, Not(HasSubstr("Cpp::GetVersion()"))); + EXPECT_THAT(content, Not(HasSubstr("Cpp::GetBuildInfo()"))); + EXPECT_THAT(content, HasSubstr("// Replay scope:")); llvm::sys::fs::remove(Path); } @@ -569,11 +1145,11 @@ TEST_F(TracingTest, ReproducerCompilesViaInterpreter) { Cpp::CreateInterpreter({}); // Verify tracing is active after interpreter creation. - ASSERT_NE(TraceInfo::TheTraceInfo, nullptr) + ASSERT_NE(TheTraceInfo, nullptr) << "TheTraceInfo was cleared during CreateInterpreter"; // Clear any prior trace state so the reproducer only contains our calls. - TraceInfo::TheTraceInfo->clear(); + TheTraceInfo->clear(); // Exercise real API calls that will be recorded. auto GlobalScope = Cpp::GetGlobalScope(); @@ -597,7 +1173,7 @@ TEST_F(TracingTest, ReproducerCompilesViaInterpreter) { Cpp::SizeOf(Foo); // Write the reproducer. - TraceInfo& TI = *TraceInfo::TheTraceInfo; + TraceInfo& TI = *TheTraceInfo; std::string Path = TI.writeToFile(); ASSERT_FALSE(Path.empty()); @@ -609,20 +1185,23 @@ TEST_F(TracingTest, ReproducerCompilesViaInterpreter) { ifs.close(); // Verify the reproducer has proper structure and content. - EXPECT_THAT(content, HasSubstr("#include ")); + EXPECT_THAT(content, HasSubstr("#include ")); EXPECT_THAT(content, HasSubstr("void reproducer()")); EXPECT_THAT(content, HasSubstr("Cpp::")); // Create a fresh interpreter and #include the reproducer file as-is. Cpp::CreateInterpreter({}); Cpp::AddIncludePath(CPPINTEROP_DIR "/include"); + // Generated .inc files live under the build tree. + Cpp::AddIncludePath(CPPINTEROP_BINARY_DIR "/include"); std::string includeDirective = "#include \"" + Path + "\""; EXPECT_EQ(Cpp::Process(includeDirective.c_str()), 0) << "Reproducer failed to compile via #include:\n" << content; - // Execute the reproducer in clean interpreter state. + // Drives the static-link `#else` branch via the in-process interpreter; + // the dispatch path is for standalone builds, not exercised here. EXPECT_EQ(Cpp::Process("reproducer();"), 0) << "Reproducer failed to execute"; llvm::sys::fs::remove(Path); @@ -634,8 +1213,13 @@ TEST_F(TracingTest, ReproducerCompilesViaInterpreter) { // --------------------------------------------------------------------------- TEST_F(TracingTest, JitCallWrapperSourceLogged) { +#ifdef EMSCRIPTEN +#if CLANG_VERSION_MAJOR > 21 + GTEST_SKIP() << "Test fails for Emscipten builds using LLVM 22"; +#endif +#endif Cpp::CreateInterpreter({}); - ASSERT_NE(TraceInfo::TheTraceInfo, nullptr); + ASSERT_NE(TheTraceInfo, nullptr); // Placement new requires . Cpp::Declare("#include "); @@ -644,7 +1228,7 @@ TEST_F(TracingTest, JitCallWrapperSourceLogged) { static_cast(Cpp::GetNamed("add", Cpp::GetScope("WrapNS"))); ASSERT_NE(Func, nullptr); - TraceInfo::TheTraceInfo->clear(); + TheTraceInfo->clear(); // MakeFunctionCallable triggers make_wrapper which logs the wrapper source. auto JC = Cpp::MakeFunctionCallable(Func); @@ -660,7 +1244,7 @@ TEST_F(TracingTest, JitCallWrapperSourceLogged) { #ifndef NDEBUG TEST_F(TracingTest, JitCallInvokeLogged) { Cpp::CreateInterpreter({}); - ASSERT_NE(TraceInfo::TheTraceInfo, nullptr); + ASSERT_NE(TheTraceInfo, nullptr); Cpp::Declare("#include "); Cpp::Declare("namespace InvNS { int square(int x) { return x * x; } }"); @@ -672,7 +1256,7 @@ TEST_F(TracingTest, JitCallInvokeLogged) { ASSERT_TRUE(JC.isValid()); // Clear so only the Invoke shows up. - TraceInfo::TheTraceInfo->clear(); + TheTraceInfo->clear(); int arg = 7; int result = 0; @@ -688,12 +1272,111 @@ TEST_F(TracingTest, JitCallInvokeLogged) { // And the function should have actually executed. EXPECT_EQ(result, 49); } + +TEST_F(TracingTest, JitCallConstructorInvokeLogged) { + // Pins the InvokeConstructor inline-guard: ReportInvokeStart must fire + // for kConstructorCall just as it does for kGenericCall. + Cpp::CreateInterpreter({}); + ASSERT_NE(TheTraceInfo, nullptr); + + Cpp::Declare("#include "); + Cpp::Declare("namespace InvCtorNS { struct Bar { int x = 7; }; }"); + auto* ClassBar = Cpp::GetScope("Bar", Cpp::GetScope("InvCtorNS")); + ASSERT_NE(ClassBar, nullptr); + auto* CtorD = Cpp::GetDefaultConstructor(ClassBar); + ASSERT_NE(CtorD, nullptr); + // Resolve the dtor up-front so the cleanup at the end of the test + // can free the heap-allocated Bar (LeakSanitizer otherwise flags + // the 4-byte `int x` payload). + auto* DtorD = Cpp::GetDestructor(ClassBar); + ASSERT_NE(DtorD, nullptr); + + auto CtorJC = Cpp::MakeFunctionCallable(CtorD); + auto DtorJC = Cpp::MakeFunctionCallable(DtorD); + ASSERT_TRUE(CtorJC.isValid()); + ASSERT_TRUE(DtorJC.isValid()); + + TheTraceInfo->clear(); + void* obj = nullptr; + CtorJC.Invoke(&obj); + ASSERT_NE(obj, nullptr); + + auto log = getFullLog(); + EXPECT_THAT(log, HasSubstr("JitCall::Invoke")); + EXPECT_THAT(log, HasSubstr("Bar")); + + DtorJC.Invoke(obj); +} + +TEST_F(TracingTest, JitCallDestructorInvokeLogged) { + // Pins the InvokeDestructor inline-guard: ReportInvokeStart must emit + // a JitCall::InvokeDestructor line for the dtor path. + Cpp::CreateInterpreter({}); + ASSERT_NE(TheTraceInfo, nullptr); + + Cpp::Declare("#include "); + Cpp::Declare("namespace InvDtorNS { struct Baz { ~Baz() {} }; }"); + auto* ClassBaz = Cpp::GetScope("Baz", Cpp::GetScope("InvDtorNS")); + ASSERT_NE(ClassBaz, nullptr); + auto* CtorD = Cpp::GetDefaultConstructor(ClassBaz); + ASSERT_NE(CtorD, nullptr); + auto* DtorD = Cpp::GetDestructor(ClassBaz); + ASSERT_NE(DtorD, nullptr); + + auto CtorJC = Cpp::MakeFunctionCallable(CtorD); + auto DtorJC = Cpp::MakeFunctionCallable(DtorD); + ASSERT_TRUE(CtorJC.isValid()); + ASSERT_TRUE(DtorJC.isValid()); + + void* obj = nullptr; + CtorJC.Invoke(&obj); + ASSERT_NE(obj, nullptr); + + TheTraceInfo->clear(); + DtorJC.Invoke(obj); + + auto log = getFullLog(); + EXPECT_THAT(log, HasSubstr("JitCall::InvokeDestructor")); + EXPECT_THAT(log, HasSubstr("Baz")); +} + +TEST_F(TracingTest, JitCallReturnedPointerRegistered) { + // Factory returning T*: the wrapper writes the new pointer at + // *result; the return-hook registers it as a vN handle so later + // trace lines that consume it render the name instead of + // `nullptr /*unknown*/`. + Cpp::CreateInterpreter({}); + ASSERT_NE(TheTraceInfo, nullptr); + + Cpp::Declare("#include "); + Cpp::Declare("namespace InvRetNS { struct Foo {}; " + "Foo* makeFoo() { return new Foo(); } }"); + auto* MakeFD = Cpp::GetNamed("makeFoo", Cpp::GetScope("InvRetNS")); + ASSERT_NE(MakeFD, nullptr); + auto MakeJC = Cpp::MakeFunctionCallable(MakeFD); + ASSERT_TRUE(MakeJC.isValid()); + + // Sanity: an unrelated pointer remains unregistered. + EXPECT_TRUE(TheTraceInfo->lookupHandle(reinterpret_cast(0x1)).empty()); + + void* foo = nullptr; + MakeJC.Invoke(&foo); + ASSERT_NE(foo, nullptr); + + // After Invoke the return-hook has registered foo; lookupHandle + // yields its assigned name instead of "". + EXPECT_FALSE(TheTraceInfo->lookupHandle(foo).empty()); + + // makeFoo heap-allocated a trivially-destructible Foo; free it so + // LeakSanitizer doesn't flag the allocation. + ::operator delete(foo); +} #endif // Verify that log entries include timing annotations. TEST_F(TracingTest, LogEntriesIncludeTimingAnnotation) { VoidFunc(); - auto output = TraceInfo::TheTraceInfo->getLastLogEntry(); + auto output = TheTraceInfo->getLastLogEntry(); EXPECT_THAT(output, HasSubstr("// [")); EXPECT_THAT(output, HasSubstr("ns]")); } @@ -725,7 +1408,7 @@ TEST_F(TracingTest, StartStopTracingWritesToFile) { ASSERT_FALSE(content.empty()); EXPECT_THAT(content, HasSubstr("Cpp::VoidFunc()")); EXPECT_THAT(content, HasSubstr("Cpp::AnnotatedFunction(")); - EXPECT_THAT(content, HasSubstr("#include ")); + EXPECT_THAT(content, HasSubstr("#include ")); llvm::sys::fs::remove(Path); } @@ -762,7 +1445,7 @@ TEST_F(TracingTest, OnlyRegionCallsAreRecorded) { TEST_F(TracingTest, StartTracingWithEnvVarNarrowsToRegion) { // When CPPINTEROP_LOG=1 is set (tracing already active from SetUp), // StartTracing/StopTracing should still narrow to just the region. - ASSERT_NE(TraceInfo::TheTraceInfo, nullptr); + ASSERT_NE(TheTraceInfo, nullptr); // This call is before the region. VoidFunc(); @@ -848,7 +1531,7 @@ TEST_F(TracingTest, WriteToFileWhenStdErrDisabled) { // The file should contain the full reproducer. std::string FileContent = ReadFileToString(Path); - EXPECT_THAT(FileContent, HasSubstr("#include ")); + EXPECT_THAT(FileContent, HasSubstr("#include ")); EXPECT_THAT(FileContent, HasSubstr("Cpp::AnnotatedFunction(77)")); llvm::sys::fs::remove(Path); @@ -859,10 +1542,18 @@ TEST_F(TracingTest, WriteToFileWhenStdErrDisabled) { // --------------------------------------------------------------------------- TEST(TracingCoverageTest, AllPublicAPIsAreTraced) { - // 1. Parse the header to extract all CPPINTEROP_API function names. + // 1. Parse the header and generated declarations to extract all + // CPPINTEROP_API function names. std::string Header = ReadFileToString(CPPINTEROP_DIR "/include/CppInterOp/CppInterOp.h"); ASSERT_FALSE(Header.empty()) << "Could not read CppInterOp.h"; + // Function declarations are generated into CppInterOpDecl.inc. + std::string DeclInc = ReadFileToString( + CPPINTEROP_BINARY_DIR "/include/CppInterOp/CppInterOpDecl.inc"); + if (DeclInc.empty()) + DeclInc = ReadFileToString(CPPINTEROP_DIR + "/include/CppInterOp/CppInterOpDecl.inc"); + Header += DeclInc; std::regex ApiRe(R"(CPPINTEROP_API\s+[\w:<>\s\*&]+?\s+(\w+)\s*\()"); std::set ApiNames; @@ -876,6 +1567,12 @@ TEST(TracingCoverageTest, AllPublicAPIsAreTraced) { ApiNames.erase("AreArgumentsValid"); ApiNames.erase("ReportInvokeStart"); ApiNames.erase("ReportInvokeEnd"); + // Exclude the JitCall trace-hook impls: they are reached only from + // INTEROP_TRACE-instrumented inline code; tracing them recursively + // would just nest entries for no benefit. + ApiNames.erase("CppInterOpTraceJitCallInvokeImpl"); + ApiNames.erase("CppInterOpTraceJitCallInvokeDestructorImpl"); + ApiNames.erase("CppInterOpTraceJitCallInvokeReturnImpl"); ASSERT_GT(ApiNames.size(), 100u) << "Suspiciously few API functions found — regex may be broken"; @@ -920,4 +1617,5 @@ TEST(TracingCoverageTest, AllPublicAPIsAreTraced) { FAIL() << Msg; } } + #endif // !EMSCRIPTEN diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp index 4cb93d22af646..6df0a6c6f595b 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp @@ -7,8 +7,6 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Sema/Sema.h" -#include "clang-c/CXCppInterOp.h" - #include "gtest/gtest.h" #include @@ -133,6 +131,52 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_GetType) { EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("struct")),"NULL TYPE"); } +TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_GetTypeWithParent) { + TestFixture::CreateInterpreter(); + + Interp->declare(R"( + namespace NS { + struct Test {}; + using testptr = Test*; // alias whose target is Test*, not Test + using testref = Test&; // alias whose target is a reference + typedef Test testtd; // classic typedef to a class + class Member {}; + } + )"); + + Cpp::TCppScope_t ns = Cpp::GetNamed("NS"); + ASSERT_TRUE(ns); + + // Builtin fast-path is independent of parent: `int` resolves + // identically with and without a scope. + EXPECT_EQ(Cpp::GetType("int"), Cpp::GetType("int", ns)); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("int", ns)), "int"); + + // Direct-member lookup via parent. + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("Test", ns)), "NS::Test"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("Member", ns)), "NS::Member"); + + // The motivating case: a type alias whose underlying QualType carries + // a pointer must round-trip with the pointer intact. GetTypeFromScope + // routes TypeAliasDecl through TND->getUnderlyingType(), preserving + // pointer / reference / cv qualifiers. + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("testptr", ns)), "NS::Test *"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("testref", ns)), "NS::Test &"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("testtd", ns)), "NS::Test"); + + // TU-only lookup (no parent) does NOT see members of NS. + EXPECT_EQ(Cpp::GetType("testptr"), nullptr) + << "GetType(name) without a parent must not reach NS members"; + + // Negative: name not declared in the parent scope returns nullptr. + EXPECT_EQ(Cpp::GetType("nope", ns), nullptr); + + // parent==nullptr is the historical behavior: TU-scope lookup, + // builtins still resolved. + EXPECT_EQ(Cpp::GetType("int", nullptr), Cpp::GetType("int")); + EXPECT_EQ(Cpp::GetType("testptr", nullptr), nullptr); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_IsRecordType) { std::vector Decls; @@ -357,26 +401,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_GetComplexType) { EXPECT_EQ(get_complex_type_as_string("int"), "_Complex int"); EXPECT_EQ(get_complex_type_as_string("float"), "_Complex float"); EXPECT_EQ(get_complex_type_as_string("double"), "_Complex double"); - - // C API - auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); - auto C_API_SHIM = [&](const std::string& element_type) { - auto ElementQT = Cpp::GetType(element_type); - CXQualType EQT = {CXType_Unexposed, {ElementQT, I}}; - CXQualType ComplexQT = clang_getComplexType(EQT); - auto Str = clang_getTypeAsString(ComplexQT); - auto Res = std::string(get_c_string(Str)); - dispose_string(Str); - return Res; - }; - - EXPECT_EQ(C_API_SHIM("int"), "_Complex int"); - EXPECT_EQ(C_API_SHIM("float"), "_Complex float"); - EXPECT_EQ(C_API_SHIM("double"), "_Complex double"); - - // Clean up resources - clang_Interpreter_takeInterpreterAsPtr(I); - clang_Interpreter_dispose(I); } TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_GetTypeFromScope) { @@ -558,8 +582,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_IsSmartPtrType) { #if CLANG_VERSION_MAJOR == 20 && defined(CPPINTEROP_USE_CLING) && defined(_WIN32) GTEST_SKIP() << "Test fails with Cling on Windows"; #endif - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector interpreter_args = {"-include", "new"}; TestFixture::CreateInterpreter(interpreter_args); @@ -586,7 +608,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_IsSmartPtrType) { )"); auto get_type_from_varname = [&](const std::string& varname) { - return Cpp::GetVariableType(Cpp::GetNamed(varname DFLT_NULLPTR)); + return Cpp::GetVariableType(Cpp::GetNamed(varname)); }; //EXPECT_TRUE(Cpp::IsSmartPtrType(get_type_from_varname("smart_ptr1"))); @@ -610,10 +632,10 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_IsFunctionPointerType) { int i = 2; )"); - EXPECT_TRUE(Cpp::IsFunctionPointerType( - Cpp::GetVariableType(Cpp::GetNamed("f" DFLT_NULLPTR)))); - EXPECT_FALSE(Cpp::IsFunctionPointerType( - Cpp::GetVariableType(Cpp::GetNamed("i" DFLT_NULLPTR)))); + EXPECT_TRUE( + Cpp::IsFunctionPointerType(Cpp::GetVariableType(Cpp::GetNamed("f")))); + EXPECT_FALSE( + Cpp::IsFunctionPointerType(Cpp::GetVariableType(Cpp::GetNamed("i")))); } TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_OperatorSpelling) { @@ -635,16 +657,16 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_TypeQualifiers) { int *__restrict__ const f = nullptr; int *__restrict__ volatile g; int *__restrict__ const volatile h = nullptr; - )" 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)); + 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")); EXPECT_FALSE(Cpp::HasTypeQualifier(nullptr, Cpp::QualKind::Const)); EXPECT_FALSE(Cpp::RemoveTypeQualifier(nullptr, Cpp::QualKind::Const)); @@ -714,12 +736,12 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, TypeReflection_IsIntegerType) { GetAllTopLevelDecls(code, Decls); Cpp::Signedness sign; - EXPECT_TRUE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[0]) DFLT_NULLPTR)); - EXPECT_FALSE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[1]) DFLT_NULLPTR)); - EXPECT_FALSE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[2]) DFLT_NULLPTR)); - EXPECT_TRUE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[4]) DFLT_NULLPTR)); - EXPECT_TRUE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[5]) DFLT_NULLPTR)); - EXPECT_TRUE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[6]) DFLT_NULLPTR)); + EXPECT_TRUE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[0]))); + EXPECT_FALSE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[1]))); + EXPECT_FALSE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[2]))); + EXPECT_TRUE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[4]))); + EXPECT_TRUE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[5]))); + EXPECT_TRUE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[6]))); // Check signedness via out parameter EXPECT_TRUE(Cpp::IsIntegerType(Cpp::GetVariableType(Decls[0]), &sign)); diff --git a/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp b/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp index 57489ae0ad03a..a8c7d247f9a90 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp @@ -18,32 +18,6 @@ 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() = default; - DispatchInitializer(const DispatchInitializer&) = delete; - DispatchInitializer& operator=(const DispatchInitializer&) = delete; - DispatchInitializer(DispatchInitializer&&) noexcept = default; - DispatchInitializer& operator=(DispatchInitializer&&) noexcept = default; -}; -// Thread-safe initialization using function-local static -DispatchInitializer& GetDispatchInitializer() { - static DispatchInitializer instance; - return instance; -} -const DispatchInitializer& g_dispatch_init = GetDispatchInitializer(); -} // namespace -#endif - namespace TestUtils { TestConfig current_config; std::vector GetInterpreterArgs( @@ -105,16 +79,3 @@ bool IsTargetX86() { #endif return triple.isX86(); } - -const char* get_c_string(CXString string) { - return static_cast(string.data); -} - -void dispose_string(CXString string) { - if (string.private_flags == 1 && string.data) - free(const_cast(string.data)); -} - -CXScope make_scope(const clang::Decl* D, const CXInterpreter I) { - return {CXCursor_UnexposedDecl, 0, {D, nullptr, I}}; -} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/Utils.h b/interpreter/CppInterOp/unittests/CppInterOp/Utils.h index be7c62cddcdc1..1101b6f46a303 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/Utils.h +++ b/interpreter/CppInterOp/unittests/CppInterOp/Utils.h @@ -3,32 +3,8 @@ #include "../../lib/CppInterOp/Compatibility.h" -#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 @@ -70,21 +46,42 @@ void GetAllSubDecls(clang::Decl* D, std::vector& SubDecls, bool filter_implicitGenerated = false); } // end namespace TestUtils -const char* get_c_string(CXString string); - -void dispose_string(CXString string); - -CXScope make_scope(const clang::Decl* D, const CXInterpreter I); - bool IsTargetX86(); +// OOP-JIT is incompatible with two configurations and is excluded +// from the typed-test matrix wholesale (rather than per-test) when +// either applies: +// * Any sanitizer (ASan/MSan/TSan): upstream LLVM ORC trips +// `Resolving symbol with incorrect flags` +// (`llvm/lib/ExecutionEngine/Orc/Core.cpp`, the JITSymbolFlags +// compare under `OL_notifyResolved`) because +// sanitizer-instrumented common symbols carry flags the host +// process didn't declare; the EPC boundary surfaces the +// mismatch. In-process JIT is unaffected. +// * Emscripten: the OOP path requires fork/exec + a separate +// executor binary, which the wasm runtime doesn't provide. +#if defined(__has_feature) +# if __has_feature(address_sanitizer) || \ + __has_feature(memory_sanitizer) || \ + __has_feature(thread_sanitizer) +# define CPPINTEROP_OOP_DISABLED 1 +# endif +#endif +#if defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) +# define CPPINTEROP_OOP_DISABLED 1 +#endif +#if defined(__EMSCRIPTEN__) +# define CPPINTEROP_OOP_DISABLED 1 +#endif + // 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 +#if LLVM_VERSION_MAJOR > 21 && !defined(_WIN32) && \ + !defined(CPPINTEROP_OOP_DISABLED) struct OutOfProcessJITConfig { static constexpr bool isOutOfProcess = true; static constexpr const char* name = "OutOfProcessJIT"; @@ -100,8 +97,9 @@ template class CPPINTEROP_TEST_MODE : public ::testing::Test { } public: - static TInterp_t CreateInterpreter(const std::vector& Args = {}, - const std::vector& GpuArgs = {}) { + static Cpp::TInterp_t + CreateInterpreter(const std::vector& Args = {}, + const std::vector& GpuArgs = {}) { auto mergedArgs = TestUtils::GetInterpreterArgs(Args); return Cpp::CreateInterpreter(mergedArgs, GpuArgs); } @@ -118,7 +116,8 @@ struct JITConfigNameGenerator { } }; -#ifdef LLVM_BUILT_WITH_OOP_JIT +#if LLVM_VERSION_MAJOR > 21 && !defined(_WIN32) && \ + !defined(CPPINTEROP_OOP_DISABLED) using CppInterOpTestTypes = ::testing::Types; #else using CppInterOpTestTypes = ::testing::Types; diff --git a/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp index d370eec35d9da..a9dc9be898f13 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp @@ -117,8 +117,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_DatamembersWithAnonymousStru #if CLANG_VERSION_MAJOR == 20 && defined(CPPINTEROP_USE_CLING) && defined(_WIN32) GTEST_SKIP() << "Test fails with Cling on Windows"; #endif - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector Decls; #define Stringify(s) Stringifyx(s) @@ -139,27 +137,27 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_DatamembersWithAnonymousStru EXPECT_EQ(datamembers_klass1.size(), 3); EXPECT_EQ(datamembers_klass2.size(), 3); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[0] DFLT_0), 0); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[0]), 0); // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[1] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[1]), ((intptr_t) & (k1.a)) - ((intptr_t) & (k1.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[2] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[2]), ((intptr_t) & (k1.b)) - ((intptr_t) & (k1.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[0] DFLT_0), 0); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[1] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[0]), 0); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[1]), ((intptr_t) & (k2.a)) - ((intptr_t) & (k2.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[2] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass2[2]), ((intptr_t) & (k2.b)) - ((intptr_t) & (k2.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[0] DFLT_0), 0); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[1] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[0]), 0); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[1]), ((intptr_t) & (k3.a)) - ((intptr_t) & (k3.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[2] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[2]), ((intptr_t) & (k3.b)) - ((intptr_t) & (k3.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[3] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[3]), ((intptr_t) & (k3.c)) - ((intptr_t) & (k3.num))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[4] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[4]), ((intptr_t) & (k3.num2)) - ((intptr_t) & (k3.num))); // NOLINTEND(cppcoreguidelines-pro-type-union-access) #ifdef _WIN32 @@ -168,8 +166,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_DatamembersWithAnonymousStru } TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_GetTypeAsString) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; std::string code = R"( namespace my_namespace { @@ -186,7 +182,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_GetTypeAsString) { )"; TestFixture::CreateInterpreter(); - EXPECT_EQ(Cpp::Declare(code.c_str() DFLT_FALSE), 0); + EXPECT_EQ(Cpp::Declare(code.c_str()), 0); Cpp::TCppScope_t wrapper = Cpp::GetScopeFromCompleteName("my_namespace::Wrapper"); @@ -287,62 +283,62 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_GetVariableOffset) { std::vector datamembers; Cpp::GetDatamembers(Decls[4], datamembers); - 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_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_EQ(Cpp::GetVariableOffset(datamembers[0] DFLT_0), 0); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[0]), 0); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[1] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[1]), ((intptr_t) & (c.b)) - ((intptr_t) & (c.a))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[2] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[2]), ((intptr_t) & (c.c)) - ((intptr_t) & (c.a))); - EXPECT_EQ(Cpp::GetVariableOffset(datamembers[3] DFLT_0), + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[3]), ((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 DFLT_0)); + EXPECT_TRUE((bool)Cpp::GetVariableOffset(VD_C_s_a)); struct K { int x; int y; int z; }; - Cpp::Declare("struct K;" DFLT_FALSE); - Cpp::TCppScope_t k = Cpp::GetNamed("K" DFLT_NULLPTR); + Cpp::Declare("struct K;"); + Cpp::TCppScope_t k = Cpp::GetNamed("K"); EXPECT_TRUE(k); - Cpp::Declare("struct K { int x; int y; int z; };" DFLT_FALSE); + Cpp::Declare("struct K { int x; int y; int z; };"); datamembers.clear(); Cpp::GetDatamembers(k, datamembers); EXPECT_EQ(datamembers.size(), 3); - 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)); + 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)); 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" DFLT_NULLPTR); + Cpp::TCppScope_t klass = Cpp::GetNamed("ClassWithStatic"); 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() DFLT_FALSE); + Cpp::TCppScope_t klass_instantiated = + Cpp::InstantiateTemplate(klass, template_args); EXPECT_TRUE(klass_instantiated); Cpp::TCppScope_t var = Cpp::GetNamed("ref_value", klass_instantiated); EXPECT_TRUE(var); - EXPECT_TRUE(Cpp::GetVariableOffset(var DFLT_0)); + EXPECT_TRUE(Cpp::GetVariableOffset(var)); } #define CODE \ @@ -385,22 +381,20 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_VariableOffsetsWithInheritan #if CLANG_VERSION_MAJOR == 20 && defined(CPPINTEROP_USE_CLING) && defined(_WIN32) GTEST_SKIP() << "Test fails with Cling on Windows"; #endif - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; std::vector interpreter_args = {"-include", "new"}; TestFixture::CreateInterpreter(interpreter_args); - Cpp::Declare("#include" DFLT_FALSE); + Cpp::Declare("#include"); #define Stringify(s) Stringifyx(s) #define Stringifyx(...) #__VA_ARGS__ - Cpp::Declare(Stringify(CODE) DFLT_FALSE); + Cpp::Declare(Stringify(CODE)); #undef Stringifyx #undef Stringify #undef CODE - Cpp::TCppScope_t myklass = Cpp::GetNamed("MyKlass" DFLT_NULLPTR); + Cpp::TCppScope_t myklass = Cpp::GetNamed("MyKlass"); EXPECT_TRUE(myklass); size_t num_bases = Cpp::GetNumBases(myklass); @@ -560,8 +554,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, } TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_StaticConstExprDatamember) { - if (llvm::sys::RunningOnValgrind()) - GTEST_SKIP() << "XFAIL due to Valgrind report"; #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; @@ -590,16 +582,16 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_StaticConstExprDatamember) { template struct Elements : public integral_constant {}; - )" DFLT_FALSE); + )"); - Cpp::TCppScope_t MyClass = Cpp::GetNamed("MyClass" DFLT_NULLPTR); + Cpp::TCppScope_t MyClass = Cpp::GetNamed("MyClass"); EXPECT_TRUE(MyClass); std::vector datamembers; Cpp::GetStaticDatamembers(MyClass, datamembers); EXPECT_EQ(datamembers.size(), 1); - intptr_t offset = Cpp::GetVariableOffset(datamembers[0] DFLT_0); + intptr_t offset = Cpp::GetVariableOffset(datamembers[0]); EXPECT_EQ(3, *(size_t*)offset); ASTContext& C = Interp->getCI()->getASTContext(); @@ -607,23 +599,21 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_StaticConstExprDatamember) { {C.IntTy.getAsOpaquePtr(), "5"}}; Cpp::TCppFunction_t MyTemplatedClass = Cpp::InstantiateTemplate( - Cpp::GetNamed("MyTemplatedClass" DFLT_NULLPTR), template_args.data(), - template_args.size() DFLT_FALSE); + Cpp::GetNamed("MyTemplatedClass"), template_args); EXPECT_TRUE(MyTemplatedClass); datamembers.clear(); Cpp::GetStaticDatamembers(MyTemplatedClass, datamembers); EXPECT_EQ(datamembers.size(), 1); - offset = Cpp::GetVariableOffset(datamembers[0] DFLT_0); + offset = Cpp::GetVariableOffset(datamembers[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" DFLT_NULLPTR), ele_template_args.data(), - ele_template_args.size() DFLT_FALSE); + Cpp::TCppFunction_t Elements = + Cpp::InstantiateTemplate(Cpp::GetNamed("Elements"), ele_template_args); EXPECT_TRUE(Elements); EXPECT_EQ(1, Cpp::GetNumBases(Elements)); @@ -634,7 +624,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_StaticConstExprDatamember) { Cpp::GetStaticDatamembers(IC, datamembers); EXPECT_EQ(datamembers.size(), 1); - offset = Cpp::GetVariableOffset(datamembers[0] DFLT_0); + offset = Cpp::GetVariableOffset(datamembers[0]); EXPECT_EQ(2, *(size_t*)offset); } @@ -648,13 +638,13 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, enum A { ONE, TWO, THREE }; enum class B { SEVEN, EIGHT, NINE }; }; - )" DFLT_FALSE); + )"); - Cpp::TCppScope_t MyEnumClass = Cpp::GetNamed("MyEnumClass" DFLT_NULLPTR); + Cpp::TCppScope_t MyEnumClass = Cpp::GetNamed("MyEnumClass"); EXPECT_TRUE(MyEnumClass); std::vector datamembers; - Cpp::GetEnumConstantDatamembers(MyEnumClass, datamembers DFLT_TRUE); + Cpp::GetEnumConstantDatamembers(MyEnumClass, datamembers); EXPECT_EQ(datamembers.size(), 9); EXPECT_TRUE(Cpp::IsEnumType(Cpp::GetVariableType(datamembers[0]))); @@ -728,7 +718,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, VariableReflection_Is_Get_Reference) { EXPECT_EQ(Cpp::GetValueKind(Cpp::GetVariableType(Decls[2])), Cpp::ValueKind::LValue); - EXPECT_EQ(Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1]) DFLT_FALSE), + EXPECT_EQ(Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1])), Cpp::GetVariableType(Decls[2])); EXPECT_EQ(Cpp::GetValueKind( Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1]), true)), diff --git a/interpreter/CppInterOp/utils/TableGen/CMakeLists.txt b/interpreter/CppInterOp/utils/TableGen/CMakeLists.txt new file mode 100644 index 0000000000000..4eea719595546 --- /dev/null +++ b/interpreter/CppInterOp/utils/TableGen/CMakeLists.txt @@ -0,0 +1,6 @@ +set(LLVM_LINK_COMPONENTS Support TableGen) + +add_tablegen(cppinterop-tblgen CPPINTEROP + CppInterOpEmitter.cpp + TableGen.cpp +) diff --git a/interpreter/CppInterOp/utils/TableGen/CppInterOpEmitter.cpp b/interpreter/CppInterOp/utils/TableGen/CppInterOpEmitter.cpp new file mode 100644 index 0000000000000..338a28fbd0aef --- /dev/null +++ b/interpreter/CppInterOp/utils/TableGen/CppInterOpEmitter.cpp @@ -0,0 +1,535 @@ +//===- CppInterOpEmitter.cpp - Emit X-macro .inc from .td records ---------===// +// +// Part of the compiler-research project, under the Apache License v2.0 with +// LLVM Exceptions. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Emits a single CppInterOpAPI.inc file. Each API function becomes one +// CPPINTEROP_API_FUNC invocation that consumers expand with their own macro +// definition. +// +// CPPINTEROP_API_FUNC(DispatchName, CppName, ReturnType, +// DeclArgs, CallArgs, RawArgTypes, Defaults) +// +// where: +// DispatchName — name in the dispatch table (unique, even for overloads) +// CppName — name in CppImpl:: namespace (may be shared by overloads) +// ReturnType — C++ return type string +// DeclArgs — "(Type1 name1, Type2 name2, ...)" with defaults +// CallArgs — "(name1, name2, ...)" for forwarding +// RawArgTypes — "(Type1, Type2, ...)" for function pointer declarations +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/TableGen/Record.h" + +#include +#include +#include + +using namespace llvm; + +namespace { + +struct ArgInfo { + StringRef Type; + StringRef Name; + StringRef Default; + bool IsOut = false; +}; + +struct APIRecord { + StringRef Name; + StringRef CppName; + StringRef DispatchName; + StringRef ReturnType; + StringRef Doc; + StringRef Deprecated; + bool NoCWrapper = false; + std::vector Args; +}; + +/// C type mapping entry, read from CTypeMap records in the .td file. +struct CTypeMapEntry { + std::string CType; + std::string ArgConversion; // "$name" placeholder for the arg name + std::string RetConversion; // "$result" placeholder for the return expr + std::string CollectionKind; // "return", "outparam", "inparam", or "" + std::string ElementCType; // C element type for collections +}; + +std::vector collectAPIs(const RecordKeeper& Records) { + std::vector APIs; + for (const auto* Rec : Records.getAllDerivedDefinitions("CppInterOpAPI")) { + APIRecord R; + R.Name = Rec->getName(); + R.CppName = Rec->getValueAsString("CppName"); + R.DispatchName = Rec->getValueAsString("DispatchName"); + R.ReturnType = Rec->getValueAsString("ReturnType"); + R.Doc = Rec->getValueAsString("Doc"); + R.Deprecated = Rec->getValueAsString("Deprecated"); + R.NoCWrapper = Rec->getValueAsBit("NoCWrapper"); + for (const auto* ArgRec : Rec->getValueAsListOfDefs("Args")) { + ArgInfo A; + A.Type = ArgRec->getValueAsString("Type"); + A.Name = ArgRec->getValueAsString("Name"); + A.Default = ArgRec->getValueAsString("Default"); + A.IsOut = ArgRec->getValueAsBit("IsOut"); + R.Args.push_back(A); + } + APIs.push_back(std::move(R)); + } + return APIs; +} + +/// Build the C++ → C type map from CTypeMap records in the .td file. +StringMap collectCTypeMaps(const RecordKeeper& Records) { + StringMap Map; + for (const auto* Rec : Records.getAllDerivedDefinitions("CTypeMap")) { + CTypeMapEntry E; + StringRef CppType = Rec->getValueAsString("CppType"); + E.CType = Rec->getValueAsString("CType").str(); + E.ArgConversion = Rec->getValueAsString("ArgConversion").str(); + E.RetConversion = Rec->getValueAsString("RetConversion").str(); + E.CollectionKind = Rec->getValueAsString("CollectionKind").str(); + E.ElementCType = Rec->getValueAsString("ElementCType").str(); + Map[CppType] = std::move(E); + } + return Map; +} + +} // end anonymous namespace + +void EmitCppInterOpAPI(const RecordKeeper& Records, raw_ostream& OS) { + OS << "// Auto-generated by cppinterop-tblgen. Do not edit.\n"; + OS << "//\n"; + OS << "// CPPINTEROP_API_FUNC(DispatchName, CppName, ReturnType, DeclArgs,\n"; + OS << "// CallArgs, RawArgTypes)\n"; + OS << "// CPPINTEROP_API_OUT(CppName, OutMask)\n"; + OS << "// OutMask: bit i set iff arg i is an OUT parameter (declared as\n"; + OS << "// OutArg<...> in the .td). CPPINTEROP_API_OUT defaults to a\n"; + OS << "// no-op so consumers that only care about the function shape can\n"; + OS << "// ignore it.\n"; + OS << "\n"; + OS << "#ifndef CPPINTEROP_API_FUNC\n"; + OS << "#error \"Define CPPINTEROP_API_FUNC before including " + "CppInterOpAPI.inc\"\n"; + OS << "#endif\n"; + OS << "#ifndef CPPINTEROP_API_OUT\n"; + OS << "#define CPPINTEROP_API_OUT(CppName, OutMask)\n"; + OS << "#define CPPINTEROP_API_OUT_DEFINED_LOCALLY\n"; + OS << "#endif\n\n"; + + for (const auto& R : collectAPIs(Records)) { + OS << "CPPINTEROP_API_FUNC(" << R.DispatchName << ", " << R.CppName << ", " + << R.ReturnType << ", "; + + // DeclArgs: (Type name = default, ...) + OS << "("; + for (size_t I = 0; I < R.Args.size(); ++I) { + if (I) + OS << ", "; + OS << R.Args[I].Type << " " << R.Args[I].Name; + if (!R.Args[I].Default.empty()) + OS << " = " << R.Args[I].Default; + } + OS << "), "; + + // CallArgs: (name, ...) + OS << "("; + for (size_t I = 0; I < R.Args.size(); ++I) { + if (I) + OS << ", "; + OS << R.Args[I].Name; + } + OS << "), "; + + // RawArgTypes: (Type, ...) + OS << "("; + for (size_t I = 0; I < R.Args.size(); ++I) { + if (I) + OS << ", "; + OS << R.Args[I].Type; + } + OS << "))\n"; + + // OUT bitmask -- separate macro so dispatcher consumers can ignore it. + uint64_t Mask = 0; + for (size_t I = 0; I < R.Args.size() && I < 64; ++I) + if (R.Args[I].IsOut) + Mask |= (uint64_t{1} << I); + OS << "CPPINTEROP_API_OUT(" << R.CppName << ", 0x" + << format_hex_no_prefix(Mask, 1) << "ULL)\n"; + } + + OS << "\n#undef CPPINTEROP_API_FUNC\n"; + OS << "#ifdef CPPINTEROP_API_OUT_DEFINED_LOCALLY\n"; + OS << "#undef CPPINTEROP_API_OUT\n"; + OS << "#undef CPPINTEROP_API_OUT_DEFINED_LOCALLY\n"; + OS << "#endif\n"; +} + +void EmitCppInterOpDecl(const RecordKeeper& Records, raw_ostream& OS) { + OS << "// Auto-generated by cppinterop-tblgen. Do not edit.\n"; + OS << "// Function declarations for the CppInterOp public API.\n\n"; + + for (const auto& R : collectAPIs(Records)) { + // Emit doxygen comment if present. + if (!R.Doc.empty()) { + // Split multi-line doc strings and prefix each with ///. + SmallVector Lines; + R.Doc.split(Lines, '\n'); + for (const auto& Line : Lines) { + StringRef Trimmed = Line.ltrim(); + if (Trimmed.empty()) + OS << "///\n"; + else + OS << "/// " << Trimmed << "\n"; + } + } + + // Emit the declaration. CPPINTEROP_DEPRECATED expands to the + // vendor-specific deprecation attribute -- older Clang (Cling) + // mis-parses C++11 [[deprecated]] near the return type. + OS << "CPPINTEROP_API "; + if (!R.Deprecated.empty()) + OS << "CPPINTEROP_DEPRECATED(\"" << R.Deprecated << "\") "; + OS << R.ReturnType << " " << R.CppName << "("; + for (size_t I = 0; I < R.Args.size(); ++I) { + if (I) + OS << ", "; + OS << R.Args[I].Type << " " << R.Args[I].Name; + if (!R.Args[I].Default.empty()) + OS << " = " << R.Args[I].Default; + } + OS << ");\n\n"; + } +} + +// The prefix for all generated C API functions. Change here to rename the +// entire C API surface (e.g. "cppinterop_" -> "CppInterOp_"). +static constexpr const char* CAPIPrefix = "cppinterop_"; + +/// Replace all occurrences of \p Placeholder with \p Value in \p Pattern. +static std::string substPlaceholder(StringRef Pattern, StringRef Placeholder, + StringRef Value) { + std::string Result; + size_t Pos = 0; + while (true) { + size_t Found = Pattern.find(Placeholder, Pos); + if (Found == StringRef::npos) { + Result += Pattern.substr(Pos); + break; + } + Result += Pattern.substr(Pos, Found - Pos); + Result += Value; + Pos = Found + Placeholder.size(); + } + return Result; +} + +/// Returns true if all types in the function are in the CTypeMap. +/// If not, and the function is not marked NoCWrapper, emits a warning. +static bool canGenerateCWrapper(const APIRecord& R, + const StringMap& TypeMap, + raw_ostream& Warnings) { + if (R.NoCWrapper) + return false; + + bool Mappable = true; + if (!TypeMap.count(R.ReturnType)) { + Warnings << "// WARNING: " << R.CppName + << " has no C wrapper — unmappable return type: " << R.ReturnType + << ". Add a CTypeMap record or set NoCWrapper = true.\n"; + Mappable = false; + } + for (const auto& A : R.Args) { + if (!TypeMap.count(A.Type)) { + Warnings << "// WARNING: " << R.CppName + << " has no C wrapper — unmappable arg type: " << A.Type << " " + << A.Name + << ". Add a CTypeMap record or set NoCWrapper = true.\n"; + Mappable = false; + break; + } + } + return Mappable; +} + +/// Emit the doxygen comment block for a function. +static void emitDoxygen(const APIRecord& R, raw_ostream& OS) { + if (R.Doc.empty()) + return; + SmallVector Lines; + R.Doc.split(Lines, '\n'); + OS << "/**\n"; + for (const auto& Line : Lines) { + StringRef Trimmed = Line.ltrim(); + if (Trimmed.empty()) + OS << " *\n"; + else + OS << " * " << Trimmed << "\n"; + } + OS << " */\n"; +} + +/// Determine the C++ container type for a tmp variable from the CTypeMap +/// CppType string. Strips trailing '&' from out-param types. +/// E.g. "std::vector&" → "std::vector". +static std::string getCppTmpType(StringRef CppType, StringRef ElemCType) { + // Strip trailing '&' for out-params. + StringRef Base = CppType.rtrim('&').rtrim(); + // Extract the container template: "std::vector" or "std::set". + size_t Open = Base.find('<'); + if (Open == StringRef::npos) + return Base.str(); + StringRef Container = Base.substr(0, Open); + + // Map C element type back to C++ element type for the container. + std::string CppElem; + if (ElemCType == "char*") + CppElem = "std::string"; + else + CppElem = ElemCType.str(); + + return (Container + "<" + CppElem + ">").str(); +} + +/// Emit code to convert a C++ collection (vector or set) into a +/// CppInterOpArray or CppInterOpStringArray. \p VarName is the C++ variable +/// holding the collection, \p Entry is the CTypeMap entry. +static void emitCollectionToArray(StringRef VarName, const CTypeMapEntry& Entry, + raw_ostream& OS) { + StringRef ElemC = Entry.ElementCType; + OS << " " << Entry.CType << " _arr = {nullptr, " << VarName << ".size()};\n"; + OS << " if (_arr.size) {\n"; + if (ElemC == "char*") { + // String elements: strdup each. + OS << " _arr.data = static_cast(malloc(_arr.size * " + "sizeof(char*)));\n"; + OS << " size_t _i = 0;\n"; + OS << " for (const auto& _s : " << VarName << ")\n"; + OS << " _arr.data[_i++] = strdup(_s.c_str());\n"; + } else { + // POD elements: memcpy from contiguous storage (vectors only). + OS << " _arr.data = static_cast(malloc(_arr.size * sizeof(" + << ElemC << ")));\n"; + OS << " memcpy(_arr.data, " << VarName << ".data(), _arr.size * sizeof(" + << ElemC << "));\n"; + } + OS << " }\n"; +} + +/// Shared analysis: determine C return type, C decl args, and C++ call args +/// for a single API function. Returns false if the function should be skipped. +static bool +analyzeCWrapper(const APIRecord& R, const StringMap& TypeMap, + std::string& CRetStr, std::string& CFuncName, + std::string& CDeclArgs, std::string& CCallArgs, + const CTypeMapEntry*& OutParamEntry, int& OutParamIdx, + const CTypeMapEntry*& RetCollEntry, raw_ostream& Warnings) { + OutParamEntry = nullptr; + RetCollEntry = nullptr; + OutParamIdx = -1; + + // Check return type for collection. + auto RetIt = TypeMap.find(R.ReturnType); + if (RetIt != TypeMap.end() && RetIt->second.CollectionKind == "return") + RetCollEntry = &RetIt->second; + + // Check args for collection types. + for (size_t I = 0; I < R.Args.size(); ++I) { + auto It = TypeMap.find(R.Args[I].Type); + if (It == TypeMap.end() || It->second.CollectionKind.empty()) + continue; + if (It->second.CollectionKind == "outparam") { + OutParamEntry = &It->second; + OutParamIdx = static_cast(I); + } + } + + // Skip functions with both a non-void return AND an outparam collection. + if (OutParamEntry && R.ReturnType != "void") { + Warnings << "// WARNING: " << R.CppName + << " has no C wrapper — has both return value and collection " + "outparam. Needs manual wrapping.\n"; + return false; + } + + // Determine C return type. + if (OutParamEntry) + CRetStr = OutParamEntry->CType; + else if (RetCollEntry) + CRetStr = RetCollEntry->CType; + else + CRetStr = TypeMap.find(R.ReturnType)->second.CType; + + CFuncName = (llvm::Twine(CAPIPrefix) + R.DispatchName).str(); + + // Build C declaration args and C++ call args. + llvm::raw_string_ostream DA(CDeclArgs); + llvm::raw_string_ostream CA(CCallArgs); + bool FirstDA = true; + bool FirstCA = true; + + for (size_t I = 0; I < R.Args.size(); ++I) { + const auto& ArgEntry = TypeMap.find(R.Args[I].Type)->second; + + if (ArgEntry.CollectionKind == "outparam") { + if (!FirstCA) + CA << ", "; + FirstCA = false; + CA << "_out"; + continue; + } + + if (ArgEntry.CollectionKind == "inparam") { + if (!FirstDA) + DA << ", "; + FirstDA = false; + DA << ArgEntry.CType << " " << R.Args[I].Name << "_data, size_t " + << R.Args[I].Name << "_size"; + if (!FirstCA) + CA << ", "; + FirstCA = false; + CA << "{" << R.Args[I].Name << "_data, " << R.Args[I].Name << "_data + " + << R.Args[I].Name << "_size}"; + continue; + } + + if (!FirstDA) + DA << ", "; + FirstDA = false; + DA << ArgEntry.CType << " " << R.Args[I].Name; + if (!FirstCA) + CA << ", "; + FirstCA = false; + if (!ArgEntry.ArgConversion.empty()) + CA << substPlaceholder(ArgEntry.ArgConversion, "$name", R.Args[I].Name); + else + CA << R.Args[I].Name; + } + + return true; +} + +void EmitCXCppInterOpDecl(const RecordKeeper& Records, raw_ostream& OS) { + OS << "// Auto-generated by cppinterop-tblgen. Do not edit.\n"; + OS << "// C API declarations that forward to the Cpp:: C++ API.\n"; + OS << "// Prefix: " << CAPIPrefix << "\n\n"; + // In C++ mode, the C-compatible structs live inside namespace CppImpl. + // Pull them into the enclosing scope so extern "C" signatures compile. + OS << "#ifdef __cplusplus\n"; + OS << "using Cpp::CppInterOpArray;\n"; + OS << "using Cpp::CppInterOpStringArray;\n"; + OS << "using Cpp::TemplateArgInfo;\n"; + OS << "#endif\n\n"; + + auto TypeMap = collectCTypeMaps(Records); + std::string WarningStr; + llvm::raw_string_ostream Warnings(WarningStr); + + for (const auto& R : collectAPIs(Records)) { + if (!canGenerateCWrapper(R, TypeMap, Warnings)) + continue; + + std::string CRetStr, CFuncName, CDeclArgs, CCallArgs; + const CTypeMapEntry *OutParamEntry, *RetCollEntry; + int OutParamIdx; + if (!analyzeCWrapper(R, TypeMap, CRetStr, CFuncName, CDeclArgs, CCallArgs, + OutParamEntry, OutParamIdx, RetCollEntry, Warnings)) + continue; + + emitDoxygen(R, OS); + OS << "CPPINTEROP_API " << CRetStr << " " << CFuncName << "(" << CDeclArgs + << ");\n\n"; + } + + // Dispose functions. + OS << "/// Free a CppInterOpArray returned by a C API wrapper.\n"; + OS << "CPPINTEROP_API void " << CAPIPrefix + << "DisposeArray(CppInterOpArray arr);\n"; + OS << "/// Free a CppInterOpStringArray and all its strings.\n"; + OS << "CPPINTEROP_API void " << CAPIPrefix + << "DisposeStringArray(CppInterOpStringArray arr);\n"; + + if (!WarningStr.empty()) { + OS << "\n// === Functions without C wrappers (unmappable types) ===\n"; + OS << WarningStr; + } +} + +void EmitCXCppInterOpImpl(const RecordKeeper& Records, raw_ostream& OS) { + OS << "// Auto-generated by cppinterop-tblgen. Do not edit.\n"; + OS << "// C API implementations that forward to the Cpp:: C++ API.\n"; + OS << "// Prefix: " << CAPIPrefix << "\n\n"; + + auto TypeMap = collectCTypeMaps(Records); + std::string WarningStr; + llvm::raw_string_ostream Warnings(WarningStr); + + for (const auto& R : collectAPIs(Records)) { + if (!canGenerateCWrapper(R, TypeMap, Warnings)) + continue; + + std::string CRetStr, CFuncName, CDeclArgs, CCallArgs; + const CTypeMapEntry *OutParamEntry, *RetCollEntry; + int OutParamIdx; + if (!analyzeCWrapper(R, TypeMap, CRetStr, CFuncName, CDeclArgs, CCallArgs, + OutParamEntry, OutParamIdx, RetCollEntry, Warnings)) + continue; + + OS << "CPPINTEROP_API " << CRetStr << " " << CFuncName << "(" << CDeclArgs + << ") {\n"; + + if (OutParamEntry) { + std::string TmpType = + getCppTmpType(R.Args[OutParamIdx].Type, OutParamEntry->ElementCType); + OS << " " << TmpType << " _out;\n"; + OS << " Cpp::" << R.CppName << "(" << CCallArgs << ");\n"; + emitCollectionToArray("_out", *OutParamEntry, OS); + OS << " return _arr;\n"; + } else if (RetCollEntry) { + OS << " auto _out = Cpp::" << R.CppName << "(" << CCallArgs << ");\n"; + emitCollectionToArray("_out", *RetCollEntry, OS); + OS << " return _arr;\n"; + } else { + const auto& RetEntry = TypeMap.find(R.ReturnType)->second; + bool HasRetConv = !RetEntry.RetConversion.empty(); + + if (R.ReturnType == "void") { + OS << " Cpp::" << R.CppName << "(" << CCallArgs << ");\n"; + } else if (HasRetConv) { + std::string CallExpr = + ("Cpp::" + R.CppName + "(" + CCallArgs + ")").str(); + OS << " return " + << substPlaceholder(RetEntry.RetConversion, "$result", CallExpr) + << ";\n"; + } else { + OS << " return Cpp::" << R.CppName << "(" << CCallArgs << ");\n"; + } + } + + OS << "}\n\n"; + } + + // Dispose functions. + OS << "CPPINTEROP_API void " << CAPIPrefix + << "DisposeArray(CppInterOpArray arr) {\n"; + OS << " free(arr.data);\n"; + OS << "}\n\n"; + OS << "CPPINTEROP_API void " << CAPIPrefix + << "DisposeStringArray(CppInterOpStringArray arr) {\n"; + OS << " for (size_t _i = 0; _i < arr.size; ++_i)\n"; + OS << " free(arr.data[_i]);\n"; + OS << " free(arr.data);\n"; + OS << "}\n"; +} diff --git a/interpreter/CppInterOp/utils/TableGen/TableGen.cpp b/interpreter/CppInterOp/utils/TableGen/TableGen.cpp new file mode 100644 index 0000000000000..cac08200a75d5 --- /dev/null +++ b/interpreter/CppInterOp/utils/TableGen/TableGen.cpp @@ -0,0 +1,70 @@ +//===- TableGen.cpp - Top-level TableGen for CppInterOp -------------------===// +// +// Part of the compiler-research project, under the Apache License v2.0 with +// LLVM Exceptions. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/Signals.h" +#include "llvm/TableGen/Main.h" +#include "llvm/TableGen/Record.h" + +using namespace llvm; + +void EmitCppInterOpAPI(const RecordKeeper& Records, raw_ostream& OS); +void EmitCppInterOpDecl(const RecordKeeper& Records, raw_ostream& OS); +void EmitCXCppInterOpDecl(const RecordKeeper& Records, raw_ostream& OS); +void EmitCXCppInterOpImpl(const RecordKeeper& Records, raw_ostream& OS); + +enum ActionType { + GenCppInterOpAPI, + GenCppInterOpDecl, + GenCXCppInterOpDecl, + GenCXCppInterOpImpl, +}; + +namespace { +cl::opt Action( + cl::desc("Action to perform:"), + cl::values( + clEnumValN(GenCppInterOpAPI, "gen-cppinterop-api", + "Generate CppInterOpAPI.inc X-macro file"), + clEnumValN(GenCppInterOpDecl, "gen-cppinterop-decl", + "Generate CppInterOpDecl.inc function declarations"), + clEnumValN(GenCXCppInterOpDecl, "gen-cx-cppinterop-decl", + "Generate CXCppInterOpDecl.inc C API declarations"), + clEnumValN(GenCXCppInterOpImpl, "gen-cx-cppinterop-impl", + "Generate CXCppInterOpImpl.inc C API implementations"))); + +bool CppInterOpTableGenMain(raw_ostream& OS, const RecordKeeper& Records) { + switch (Action) { + case GenCppInterOpAPI: + EmitCppInterOpAPI(Records, OS); + break; + case GenCppInterOpDecl: + EmitCppInterOpDecl(Records, OS); + break; + case GenCXCppInterOpDecl: + EmitCXCppInterOpDecl(Records, OS); + break; + case GenCXCppInterOpImpl: + EmitCXCppInterOpImpl(Records, OS); + break; + } + return false; +} +} // end anonymous namespace + +int main(int argc, char** argv) { + sys::PrintStackTraceOnErrorSignal(argv[0]); + PrettyStackTraceProgram X(argc, argv); + cl::ParseCommandLineOptions(argc, argv); + + llvm_shutdown_obj Y; + + return TableGenMain(argv[0], &CppInterOpTableGenMain); +} From 19bf4cbb9c2756173e4537061654dd716d18be0e Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 16 May 2026 15:59:11 +0200 Subject: [PATCH 2/4] [ci] No longer exclude CMakeLists.txt in CppInterOp diff workflow --- .github/workflows/cppinterop-diff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cppinterop-diff.yml b/.github/workflows/cppinterop-diff.yml index d8de787d1cb3f..e64eadf01a7ad 100644 --- a/.github/workflows/cppinterop-diff.yml +++ b/.github/workflows/cppinterop-diff.yml @@ -29,4 +29,4 @@ jobs: run: | rm -rf .git .github discord.svg - name: Compare - run: diff --exclude=CMakeLists.txt -ur CppInterOp/ root/interpreter/CppInterOp/ + run: diff -ur CppInterOp/ root/interpreter/CppInterOp/ From 6a83c108dda8ff3583f5d4842efab494f75faaf2 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 16 May 2026 17:58:57 +0200 Subject: [PATCH 3/4] [cmake] Stage CppInterOp headers and tblgen-generated .inc files in /etc --- interpreter/CMakeLists.txt | 48 ++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/interpreter/CMakeLists.txt b/interpreter/CMakeLists.txt index 65d2048b3aa88..56c4734ece377 100644 --- a/interpreter/CMakeLists.txt +++ b/interpreter/CMakeLists.txt @@ -553,20 +553,58 @@ mark_as_advanced(FORCE BUG_REPORT_URL BUILD_CLANG_FORMAT_VS_PLUGIN BUILD_SHARED_ GCC_INSTALL_PREFIX LIBCLANG_BUILD_STATIC TOOL_INFO_PLIST) mark_as_advanced(CLEAR LLVM_ENABLE_ASSERTIONS LLVM_BUILD_TYPE) -##################### LIBINTEROP ########################### +##################### CPPINTEROP ########################### -#---Set InterOp to build on Cling, this can be toggled to use Clang-REPL------------------------------------------------------- +# Set InterOp to build with Cling, can be toggled to use Clang-REPL set(CPPINTEROP_USE_CLING ON CACHE BOOL "" FORCE) set(CPPINTEROP_USE_REPL OFF CACHE BOOL "" FORCE) -#---Disable testing on InterOp if ROOT's testing is OFF (InterOp tests are enabled by default) ------------------------------------------------------- +# Suppress CppInterOps install rules: ROOT ships CppInterOps public headers +# under $ROOTSYS/etc/cppinterop and does not install any build artifacts. +# This option adds clangCppInterOp with BUILDTREE_ONLY (add_llvm_library) +set(CPPINTEROP_INSTALL OFF CACHE BOOL "" FORCE) + +# Disable testing on InterOp if ROOT's testing is OFF (enabled by default) if(testing) - set(CPPINTEROP_ENABLE_TESTING ON CACHE BOOL "Enable gtest in CppInterOp") + set(CPPINTEROP_ENABLE_TESTING ON CACHE BOOL "Enable gtest in CppInterOp" FORCE) else() - set(CPPINTEROP_ENABLE_TESTING OFF CACHE BOOL "Enable gtest in CppInterOp") + set(CPPINTEROP_ENABLE_TESTING OFF CACHE BOOL "Enable gtest in CppInterOp" FORCE) endif() + add_subdirectory(CppInterOp) if(TARGET CppInterOpUnitTests) set_target_properties(CppInterOpUnitTests PROPERTIES EXCLUDE_FROM_ALL OFF) endif() + +# Stage CppInterOp's public headers + tblgen-generated .inc files into +# ${CMAKE_BINARY_DIR}/etc/cppinterop/CppInterOp/. Based on how +# core/clingutils ships Cling runtime headers into etc/cling/ +set(cppinterop_etcdir ${CMAKE_BINARY_DIR}/etc/cppinterop/CppInterOp) +set(cppinterop_gen_dir ${CMAKE_BINARY_DIR}/interpreter/CppInterOp/include/CppInterOp) +set(cppinterop_src_files + ${CMAKE_CURRENT_SOURCE_DIR}/CppInterOp/include/CppInterOp/CppInterOp.h + ${CMAKE_CURRENT_SOURCE_DIR}/CppInterOp/include/CppInterOp/CppInterOpTypes.h + ${CMAKE_CURRENT_SOURCE_DIR}/CppInterOp/include/CppInterOp/Dispatch.h + ${cppinterop_gen_dir}/BuildInfo.inc + ${cppinterop_gen_dir}/CppInterOpAPI.inc + ${cppinterop_gen_dir}/CppInterOpDecl.inc +) +set(cppinterop_staged_files) +foreach(src ${cppinterop_src_files}) + get_filename_component(name ${src} NAME) + list(APPEND cppinterop_staged_files ${cppinterop_etcdir}/${name}) +endforeach() + +add_custom_command( + OUTPUT ${cppinterop_staged_files} + COMMAND ${CMAKE_COMMAND} -E make_directory ${cppinterop_etcdir} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${cppinterop_src_files} ${cppinterop_etcdir}/ + DEPENDS ${cppinterop_src_files} CppInterOpTableGen + COMMENT "Staging CppInterOp headers into etc/cppinterop/CppInterOp/") +add_custom_target(CppInterOpEtc DEPENDS ${cppinterop_staged_files}) +add_dependencies(clangCppInterOp CppInterOpEtc) + +install(FILES ${cppinterop_staged_files} + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/cppinterop/CppInterOp) From 2342f76eefd49c65744222df8778110ea759af14 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 16 May 2026 17:59:51 +0200 Subject: [PATCH 4/4] [metacling] Adapt to change in CppInterOp header location --- core/metacling/src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/metacling/src/CMakeLists.txt b/core/metacling/src/CMakeLists.txt index 784827df869c8..ce830802a9b21 100644 --- a/core/metacling/src/CMakeLists.txt +++ b/core/metacling/src/CMakeLists.txt @@ -50,7 +50,7 @@ target_include_directories(MetaCling SYSTEM PRIVATE ${CLANG_INCLUDE_DIRS} ${LLVM_INCLUDE_DIRS} ${CLAD_INCLUDE_DIRS} - ${CMAKE_SOURCE_DIR}/interpreter/CppInterOp/include + ${CMAKE_BINARY_DIR}/etc/cppinterop ) target_include_directories(MetaCling PRIVATE