diff --git a/.github/workflows/cppinterop-diff.yml b/.github/workflows/cppinterop-diff.yml new file mode 100644 index 0000000000000..11190b47d6484 --- /dev/null +++ b/.github/workflows/cppinterop-diff.yml @@ -0,0 +1,26 @@ +name: Diff CppInterOp against upstream +on: + pull_request: + paths: + - 'interpreter/CppInterOp/**' + +jobs: + cppinterop-diff: + runs-on: ubuntu-latest + steps: + - name: Check out ROOT + uses: actions/checkout@v4 + with: + path: root + - name: Check out CppInterOp + uses: actions/checkout@v4 + with: + repository: compiler-research/CppInterOp + ref: 6c6f94a22bd971520a249e2c02e4259cdd3a5be6 + path: CppInterOp + - name: Drop directories that are not added to ROOT + working-directory: CppInterOp + run: | + rm -rf .git .github discord.svg + - name: Compare + run: diff -ur CppInterOp/ root/interpreter/CppInterOp/ diff --git a/core/metacling/src/CMakeLists.txt b/core/metacling/src/CMakeLists.txt index e608e3a461e16..1a3b0515dc873 100644 --- a/core/metacling/src/CMakeLists.txt +++ b/core/metacling/src/CMakeLists.txt @@ -44,6 +44,7 @@ target_include_directories(MetaCling SYSTEM PRIVATE ${CLANG_INCLUDE_DIRS} ${LLVM_INCLUDE_DIRS} ${CLAD_INCLUDE_DIRS} + ${CPPINTEROP_INCLUDE_DIRS} ) target_include_directories(MetaCling PRIVATE @@ -74,7 +75,7 @@ if(MSVC) set_source_files_properties(TCling.cxx COMPILE_FLAGS /bigobj) endif() -add_dependencies(MetaCling CLING) +add_dependencies(MetaCling CLING clangCppInterOp) ##### libCling ############################################################# @@ -113,11 +114,15 @@ ROOT_LINKER_LIBRARY(Cling $ $ $ - LIBRARIES ${CLING_LIBRARIES} ${LINK_LIBS} ${CLING_PLUGIN_LINK_LIBS}) + LIBRARIES ${CLING_LIBRARIES} clangCppInterOp ${LINK_LIBS} ${CLING_PLUGIN_LINK_LIBS}) # When these two link at the same time, they can exhaust the RAM on many machines, since they both link against llvm. add_dependencies(Cling rootcling_stage1) +if(testing) + add_dependencies(Cling CppInterOpUnitTests) +endif() + if(MSVC) set_target_properties(Cling PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) set(cling_exports ${cling_exports} diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index d57a238880d53..df57fe0ca5ac5 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -115,6 +115,8 @@ clang/LLVM technology. #include "cling/Utils/SourceNormalization.h" #include "cling/Interpreter/Exception.h" +#include "clang/Interpreter/CppInterOp.h" + #include "llvm/IR/GlobalValue.h" #include "llvm/IR/Module.h" @@ -1539,6 +1541,11 @@ TCling::TCling(const char *name, const char *title, const char* const argv[], vo if (!fInterpreter->getCI()) { // Compiler instance could not be created. See https://its.cern.ch/jira/browse/ROOT-10239 return; } + + // Tell CppInterOp that the cling::Interpreter instance is managed externally by ROOT + // Sets the interpreter by passing the fInterpreter handle as soon as TCling is initialized + Cpp::UseExternalInterpreter((Cpp::TInterp_t*)fInterpreter.get()); + // Don't check whether modules' files exist. fInterpreter->getCI()->getPreprocessorOpts().DisablePCHOrModuleValidation = DisableValidationForModuleKind::All; diff --git a/interpreter/CMakeLists.txt b/interpreter/CMakeLists.txt index d44f3042498e6..5156f6c9fb538 100644 --- a/interpreter/CMakeLists.txt +++ b/interpreter/CMakeLists.txt @@ -514,3 +514,15 @@ mark_as_advanced(FORCE BUG_REPORT_URL BUILD_CLANG_FORMAT_VS_PLUGIN BUILD_SHARED_ C_INCLUDE_DIRS DEFAULT_SYSROOT FFI_INCLUDE_DIR FFI_LIBRARY_DIR GCC_INSTALL_PREFIX LIBCLANG_BUILD_STATIC TOOL_INFO_PLIST) mark_as_advanced(CLEAR LLVM_ENABLE_ASSERTIONS LLVM_BUILD_TYPE) + +##################### LIBINTEROP ########################### + +#---Set InterOp include directories, for any ROOT component that requires the headers-------------------------------------------------------- +set(CPPINTEROP_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/CppInterOp/include + CACHE STRING "CppInterOp include directories.") + +#---Set InterOp to build on Cling, this can be toggled to use Clang-REPL------------------------------------------------------- +set(CPPINTEROP_USE_CLING ON BOOL "Use Cling as backend") + +add_subdirectory(CppInterOp) diff --git a/interpreter/CppInterOp/.clang-format b/interpreter/CppInterOp/.clang-format new file mode 100644 index 0000000000000..23d6f8ef56464 --- /dev/null +++ b/interpreter/CppInterOp/.clang-format @@ -0,0 +1,17 @@ +BasedOnStyle: LLVM + +Language: Cpp +Standard: Cpp11 +PointerAlignment: Left + +IncludeCategories: + - Regex: '^"[^/]+\"' + Priority: 10 + - Regex: '^"cling/' + Priority: 20 + - Regex: '^"clang/' + Priority: 30 + - Regex: '^"llvm/' + Priority: 40 + - Regex: '^<' + Priority: 50 diff --git a/interpreter/CppInterOp/.clang-tidy b/interpreter/CppInterOp/.clang-tidy new file mode 100644 index 0000000000000..0491d89f23ec8 --- /dev/null +++ b/interpreter/CppInterOp/.clang-tidy @@ -0,0 +1,60 @@ +Checks: > + -*, + bugprone-*, + clang-diagnostic-*, + clang-analyzer-*, + cppcoreguidelines-*, + llvm-*, + misc-*, + modernize-*, + performance-*, + portability-*, + readability-*, + -bugprone-narrowing-conversions, + -bugprone-easily-swappable-parameters, + -bugprone-implicit-widening-of-multiplication-result, + -bugprone-unchecked-optional-access, + -misc-const-correctness, + -misc-unused-parameters, + -misc-non-private-member-variables-in-classes, + -misc-no-recursion, + -misc-use-anonymous-namespace, + -modernize-return-braced-init-list, + -modernize-use-trailing-return-type, + -readability-braces-around-statements, + -readability-identifier-length, + -readability-implicit-bool-conversion, + -readability-magic-numbers, + -readability-named-parameter, + -readability-function-cognitive-complexity, + -readability-redundant-access-specifiers, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-init-variables, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-member-init, + -cppcoreguidelines-macro-usage, + -llvm-namespace-comment + +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: aNy_CasE + - key: readability-identifier-naming.FunctionCase + value: aNy_CasE + - key: readability-identifier-naming.MemberCase + value: aNy_CasE + - key: readability-identifier-naming.ParameterCase + value: aNy_CasE + - key: readability-identifier-naming.UnionCase + value: CamelCase + - key: readability-identifier-naming.VariableCase + value: aNy_CasE + - key: readability-identifier-naming.IgnoreMainLikeFunctions + value: 1 + - key: cppcoreguidelines-avoid-magic-numbers.IgnoreMacros + value: 1 + - key: cppcoreguidelines-pro-bounds-pointer-arithmetic.Pessimistic + value: 1 + - key: cppcoreguidelines-pro-type-member-init.InitWithEquals + value: 1 + - key: llvm-namespace-comment.Spaces + value: 2 diff --git a/interpreter/CppInterOp/.codecov.yml b/interpreter/CppInterOp/.codecov.yml new file mode 100644 index 0000000000000..3216e14e28bc4 --- /dev/null +++ b/interpreter/CppInterOp/.codecov.yml @@ -0,0 +1,25 @@ +codecov: + require_ci_to_pass: no + +coverage: + precision: 2 + round: down + range: "70...100" + + status: + project: yes + patch: yes + changes: no + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "reach, diff, flags, tree, files" + behavior: default + require_changes: no \ No newline at end of file diff --git a/interpreter/CppInterOp/.gitignore b/interpreter/CppInterOp/.gitignore new file mode 100644 index 0000000000000..a079a11bce9c2 --- /dev/null +++ b/interpreter/CppInterOp/.gitignore @@ -0,0 +1,41 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Directories +build +install + +# CLion files +.idea + +# VSCode files +.vscode + +# Default Virtual Environments +.venv diff --git a/interpreter/CppInterOp/.readthedocs.yaml b/interpreter/CppInterOp/.readthedocs.yaml new file mode 100644 index 0000000000000..f513721407adf --- /dev/null +++ b/interpreter/CppInterOp/.readthedocs.yaml @@ -0,0 +1,16 @@ +version: 2 + +sphinx: + configuration: docs/conf.py + builder: html + +build: + os: "ubuntu-22.04" + tools: + python: "3.11" + apt_packages: + - clang-13 + - cmake + - libclang-13-dev + - llvm-13-dev + - llvm-13-tools \ No newline at end of file diff --git a/interpreter/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/CMakeLists.txt new file mode 100644 index 0000000000000..d8380b46116f3 --- /dev/null +++ b/interpreter/CppInterOp/CMakeLists.txt @@ -0,0 +1,538 @@ +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake" + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" + ) + +# If we are not building as a part of LLVM, build CppInterOp as a standalone +# project, using LLVM as an external library: +if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) + project(CppInterOp) + + # LLVM/Clang/Cling default paths + if (DEFINED LLVM_DIR) + if (NOT DEFINED Clang_DIR) + set(Clang_DIR ${LLVM_DIR}) + endif() + if (NOT DEFINED Cling_DIR) + set(Cling_DIR ${LLVM_DIR}) + endif() + if (NOT DEFINED LLD_DIR) + set(LLD_DIR ${LLVM_DIR}) + endif() + endif() + if (DEFINED LLD_DIR) + if (NOT DEFINED LLVM_DIR) + set(LLVM_DIR ${LLD_DIR}) + endif() + if (NOT DEFINED Clang_DIR) + set(Clang_DIR ${LLD_DIR}) + endif() + if (NOT DEFINED Cling_DIR) + set(Cling_DIR ${LLD_DIR}) + endif() + endif() + if (DEFINED Clang_DIR) + if (NOT DEFINED LLVM_DIR) + set(LLVM_DIR ${Clang_DIR}) + endif() + if (NOT DEFINED LLD_DIR) + set(LLD_DIR ${Clang_DIR}) + endif() + if (NOT DEFINED Cling_DIR) + set(Cling_DIR ${Clang_DIR}) + endif() + endif() + if (DEFINED Cling_DIR) + if (NOT DEFINED LLVM_DIR) + set(LLVM_DIR ${Cling_DIR}) + endif() + if (NOT DEFINED Clang_DIR) + set(Clang_DIR ${Cling_DIR}) + endif() + endif() + +enable_language(CXX) +set(CMAKE_CXX_EXTENSIONS NO) +include(GNUInstallDirs) + option(CPPINTEROP_USE_CLING "Use Cling as backend" OFF) + option(CPPINTEROP_USE_REPL "Use clang-repl as backend" ON) + + if (CPPINTEROP_USE_CLING AND CPPINTEROP_USE_REPL) + message(FATAL_ERROR "We can only use Cling (CPPINTEROP_USE_CLING=On) or Repl (CPPINTEROP_USE_REPL=On), but not both of them.") + endif() + + ## Define supported version of clang and llvm + + set(CLANG_MIN_SUPPORTED 13.0) + set(CLANG_MAX_SUPPORTED "19.1.x") + set(CLANG_VERSION_UPPER_BOUND 20.0.0) + set(LLD_MIN_SUPPORTED 13.0) + set(LLD_MAX_SUPPORTED "19.1.x") + set(LLD_VERSION_UPPER_BOUND 20.0.0) + set(LLVM_MIN_SUPPORTED 13.0) + set(LLVM_MAX_SUPPORTED "19.1.x") + set(LLVM_VERSION_UPPER_BOUND 20.0.0) + + ## Set Cmake packages search order + + set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL) + set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC) + + ## Search packages HINTS and PATHS + + if (DEFINED LLVM_DIR) + set(llvm_search_hints PATHS ${LLVM_DIR} HINTS "${LLVM_DIR}/lib/cmake/llvm" "${LLVM_DIR}/cmake" "${LLVM_CONFIG_EXTRA_PATH_HINTS}") + set(clang_search_hints PATHS ${LLVM_DIR} HINTS "${LLVM_DIR}/lib/cmake/clang" "${LLVM_DIR}/cmake") + set(lld_search_hints PATHS ${LLVM_DIR} HINTS "${LLVM_DIR}/lib/cmake/lld" "${LLVM_DIR}/cmake") + endif() + if (DEFINED LLD_DIR) + set(llvm_search_hints PATHS ${LLD_DIR} HINTS "${LLD_DIR}/lib/cmake/llvm" "${LLD_DIR}/cmake") + set(lld_search_hints PATHS ${LLD_DIR} HINTS "${lld_search_hints}" "${LLD_DIR}/lib/cmake/lld" "${LLD_DIR}/cmake") + endif() + if (DEFINED Clang_DIR) + set(llvm_search_hints PATHS ${Clang_DIR} HINTS "${Clang_DIR}/lib/cmake/llvm" "${Clang_DIR}/cmake") + set(clang_search_hints PATHS ${Clang_DIR} HINTS "${clang_search_hints}" "${Clang_DIR}/lib/cmake/clang" "${Clang_DIR}/cmake" "${CLANG_CONFIG_EXTRA_PATH_HINTS}") + endif() + if (DEFINED Cling_DIR) + set(cling_search_hints PATHS ${Cling_DIR} HINTS "${Cling_DIR}/lib/cmake/cling" "${Cling_DIR}/cmake" "${CLING_CONFIG_EXTRA_PATH_HINTS}") + endif() + + ## Find supported LLVM + + if (CPPINTEROP_USE_CLING) + message(STATUS "Mode CPPINTEROP_USE_CLING = ON") + find_package(LLVM REQUIRED CONFIG ${llvm_search_hints} NO_DEFAULT_PATH) + find_package(Clang REQUIRED CONFIG ${clang_search_hints} NO_DEFAULT_PATH) + find_package(Cling REQUIRED CONFIG ${cling_search_hints} NO_DEFAULT_PATH) + endif(CPPINTEROP_USE_CLING) + + if (LLVM_FOUND) + if (LLVM_PACKAGE_VERSION VERSION_LESS LLVM_MIN_SUPPORTED OR LLVM_PACKAGE_VERSION VERSION_GREATER_EQUAL LLVM_VERSION_UPPER_BOUND) + unset(LLVM_FOUND) + unset(LLVM_VERSION_MAJOR) + unset(LLVM_VERSION_MINOR) + unset(LLVM_VERSION_PATCH) + unset(LLVM_PACKAGE_VERSION) + else() + if (NOT DEFINED LLVM_VERSION AND NOT DEFINED LLVM_DIR) + set(LLVM_VERSION ${LLVM_PACKAGE_VERSION}) + endif() + endif() + endif() + + if (NOT LLVM_FOUND AND DEFINED LLVM_VERSION) + if (LLVM_VERSION VERSION_GREATER_EQUAL LLVM_VERSION_UPPER_BOUND) + set(LLVM_VERSION ${LLVM_VERSION_UPPER_BOUND}) + endif() + if (LLVM_VERSION VERSION_LESS LLVM_MIN_SUPPORTED) + set(LLVM_VERSION ${LLVM_MIN_SUPPORTED}) + endif() + + find_package(LLVM ${LLVM_VERSION} REQUIRED CONFIG ${llvm_search_hints} NO_DEFAULT_PATHS) + endif() + + if (NOT LLVM_FOUND AND DEFINED LLVM_DIR) + find_package(LLVM REQUIRED CONFIG ${llvm_search_hints} NO_DEFAULT_PATH) + endif() + + if (NOT LLVM_FOUND) + find_package(LLVM REQUIRED CONFIG) + endif() + + if (NOT LLVM_FOUND) + message(FATAL_ERROR "Please set LLVM_DIR pointing to the LLVM build or installation folder") + endif() + + if (LLVM_PACKAGE_VERSION VERSION_LESS LLVM_MIN_SUPPORTED OR LLVM_PACKAGE_VERSION VERSION_GREATER_EQUAL LLVM_VERSION_UPPER_BOUND) + message(FATAL_ERROR "Found unsupported version: LLVM ${LLVM_PACKAGE_VERSION};\nPlease set LLVM_DIR pointing to the llvm version ${LLVM_MIN_SUPPORTED} to ${LLVM_MAX_SUPPORTED} build or installation folder") + endif() + + message(STATUS "Found supported version: LLVM ${LLVM_PACKAGE_VERSION}") + message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") + + ## Find supported LLD only while building for webassembly against emscripten + +if(EMSCRIPTEN) + if (DEFINED LLD_VERSION) + if (LLD_VERSION VERSION_GREATER_EQUAL LLD_VERSION_UPPER_BOUND) + set(LLD_VERSION ${LLD_VERSION_UPPER_BOUND}) + endif() + if (LLD_VERSION VERSION_LESS LLD_MIN_SUPPORTED) + set(LLD_VERSION ${LLD_MIN_SUPPORTED}) + endif() + + find_package(LLD ${LLD_VERSION} REQUIRED CONFIG ${lld_search_hints} NO_DEFAULT_PATH) + endif() + + if (NOT LLD_FOUND AND DEFINED LLD_DIR) + find_package(LLD REQUIRED CONFIG ${lld_search_hints} NO_DEFAULT_PATH) + endif() + + if (NOT LLD_FOUND) + find_package(LLD REQUIRED CONFIG) + endif() + + if (NOT LLD_FOUND) + message(FATAL_ERROR "Please set LLD_DIR pointing to the LLD build or installation folder") + endif() + + set(LLD_VERSION_MAJOR ${LLVM_VERSION_MAJOR}) + set(LLD_VERSION_MINOR ${LLVM_VERSION_MINOR}) + set(LLD_VERSION_PATCH ${LLVM_VERSION_PATCH}) + set(LLD_PACKAGE_VERSION ${LLVM_PACKAGE_VERSION}) + + if (LLD_PACKAGE_VERSION VERSION_LESS LLD_MIN_SUPPORTED OR LLD_PACKAGE_VERSION VERSION_GREATER_EQUAL LLD_VERSION_UPPER_BOUND) + message(FATAL_ERROR "Found unsupported version: LLD ${LLD_PACKAGE_VERSION};\nPlease set LLD_DIR pointing to the LLD version ${LLD_MIN_SUPPORTED} to ${LLD_MAX_SUPPORTED} build or installation folder") + endif() + + message(STATUS "Found supported version: LLD ${LLD_PACKAGE_VERSION}") + message(STATUS "Using LLDConfig.cmake in: ${LLD_DIR}") +endif() + + ## Find supported Clang + + if (DEFINED CLANG_VERSION) + if (CLANG_VERSION VERSION_GREATER_EQUAL CLANG_VERSION_UPPER_BOUND) + set(CLANG_VERSION ${CLANG_VERSION_UPPER_BOUND}) + endif() + if (CLANG_VERSION VERSION_LESS CLANG_MIN_SUPPORTED) + set(CLANG_VERSION ${CLANG_MIN_SUPPORTED}) + endif() + + find_package(Clang ${CLANG_VERSION} REQUIRED CONFIG ${clang_search_hints} NO_DEFAULT_PATH) + endif() + + if (NOT Clang_FOUND AND DEFINED Clang_DIR) + find_package(Clang REQUIRED CONFIG ${clang_search_hints} NO_DEFAULT_PATH) + endif() + + if (NOT Clang_FOUND) + find_package(Clang REQUIRED CONFIG) + endif() + + if (NOT Clang_FOUND) + message(FATAL_ERROR "Please set Clang_DIR pointing to the clang build or installation folder") + endif() + + set(CLANG_VERSION_MAJOR ${LLVM_VERSION_MAJOR}) + set(CLANG_VERSION_MINOR ${LLVM_VERSION_MINOR}) + set(CLANG_VERSION_PATCH ${LLVM_VERSION_PATCH}) + set(CLANG_PACKAGE_VERSION ${LLVM_PACKAGE_VERSION}) + + if (CLANG_PACKAGE_VERSION VERSION_LESS CLANG_MIN_SUPPORTED OR CLANG_PACKAGE_VERSION VERSION_GREATER_EQUAL CLANG_VERSION_UPPER_BOUND) + message(FATAL_ERROR "Found unsupported version: Clang ${CLANG_PACKAGE_VERSION};\nPlease set Clang_DIR pointing to the clang version ${CLANG_MIN_SUPPORTED} to ${CLANG_MAX_SUPPORTED} build or installation folder") + endif() + + message(STATUS "Found supported version: Clang ${CLANG_PACKAGE_VERSION}") + message(STATUS "Using ClangConfig.cmake in: ${Clang_DIR}") + + ## Clang 13 require c++14 or later, Clang 16 require c++17 or later. + if (CLANG_VERSION_MAJOR GREATER_EQUAL 16) + if (NOT CMAKE_CXX_STANDARD) + set (CMAKE_CXX_STANDARD 17) + endif() + if (CMAKE_CXX_STANDARD LESS 17) + message(fatal "LLVM/CppInterOp requires c++17 or later") + endif() + elseif (CLANG_VERSION_MAJOR GREATER_EQUAL 13) + if (NOT CMAKE_CXX_STANDARD) + set (CMAKE_CXX_STANDARD 14) + endif() + if (CMAKE_CXX_STANDARD LESS 14) + message(fatal "LLVM/CppInterOp requires c++14 or later") + endif() + endif() + + ## Find supported Cling + + if (CPPINTEROP_USE_CLING) + if (NOT Cling_FOUND AND DEFINED Cling_DIR) + find_package(Cling REQUIRED CONFIG ${cling_extra_hints} NO_DEFAULT_PATH) + endif() + + if (NOT Cling_FOUND) + find_package(Cling REQUIRED CONFIG) + endif() + + if (NOT Cling_FOUND) + message(FATAL_ERROR "Please set Cling_DIR pointing to the cling build or installation folder") + endif() + + message(STATUS "Found supported version: Cling ${CLING_PACKAGE_VERSION}") + message(STATUS "Using ClingConfig.cmake in: ${Cling_DIR}") + + endif() + + #Replace \ with / in LLVM_DIR (attempt to fix path parsing issue Windows) + string(REPLACE "\\" "/" LLVM_DIR "${LLVM_DIR}") + + # When in debug mode the llvm package thinks it is built with -frtti. + # For consistency we should set it to the correct value. + set(LLVM_CONFIG_HAS_RTTI NO CACHE BOOL "" FORCE) + + ## Init + + # In case this was a path to a build folder of llvm still try to find AddLLVM + list(APPEND CMAKE_MODULE_PATH "${LLVM_DIR}") + + # Fix bug in some AddLLVM.cmake implementation (-rpath "" problem) + set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib${LLVM_LIBDIR_SUFFIX}) + + set( CPPINTEROP_BUILT_STANDALONE 1 ) +endif() + +include(AddLLVM) +include(HandleLLVMOptions) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# 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. +if (CPPINTEROP_USE_CLING) + add_definitions(-DCPPINTEROP_USE_CLING) + include_directories(SYSTEM ${CLING_INCLUDE_DIRS}) + else() + if (NOT CPPINTEROP_USE_REPL) + message(FATAL_ERROR "We need either CPPINTEROP_USE_CLING or CPPINTEROP_USE_REPL") + endif() + add_definitions(-DCPPINTEROP_USE_REPL) + +endif(CPPINTEROP_USE_CLING) +include_directories(SYSTEM ${CLANG_INCLUDE_DIRS}) +include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) +separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) +add_definitions(${LLVM_DEFINITIONS_LIST}) + +if (CPPINTEROP_USE_CLING) + message(STATUS "CLING_INCLUDE_DIRS: ${CLING_INCLUDE_DIRS}") +endif(CPPINTEROP_USE_CLING) +message(STATUS "CLANG_INCLUDE_DIRS: ${CLANG_INCLUDE_DIRS}") +message(STATUS "LLVM_INCLUDE_DIRS: ${LLVM_INCLUDE_DIRS}") +message(STATUS "LLVM_DEFINITIONS_LIST: ${LLVM_DEFINITIONS_LIST}") + +# If the llvm sources are present add them with higher priority. +if (LLVM_BUILD_MAIN_SRC_DIR) + # LLVM_INCLUDE_DIRS contains the include paths to both LLVM's source and + # build directories. Since we cannot just include ClangConfig.cmake (see + # fixme above) we have to do a little more work to get the right include + # paths for clang. + # + # FIXME: We only support in-tree builds of clang, that is clang being built + # in llvm_src/tools/clang. + include_directories(SYSTEM ${LLVM_BUILD_MAIN_SRC_DIR}/tools/clang/include/) + + if (NOT LLVM_BUILD_BINARY_DIR) + message(FATAL "LLVM_BUILD_* values should be available for the build tree") + endif() + + include_directories(SYSTEM ${LLVM_BUILD_BINARY_DIR}/tools/clang/include/) +endif() + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/) + +## Code Coverage Configuration +add_library(coverage_config INTERFACE) +option(CODE_COVERAGE "Enable coverage reporting" OFF) +if(CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + if(NOT uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG") + message(FATAL_ERROR "CodeCov enabled on non-debug build!") + endif() + set(GCC_COVERAGE_COMPILE_FLAGS "-fprofile-arcs -ftest-coverage") + set(GCC_COVERAGE_LINK_FLAGS "--coverage") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHAREDLINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") + set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS "${CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}") +endif() + +if( CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND NOT MSVC_IDE ) + message(FATAL_ERROR "In-source builds are not allowed. CMake would overwrite " +"the makefiles distributed with LLVM. Please create a directory and run cmake " +"from there, passing the path to this source directory as the last argument. " +"This process created the file `CMakeCache.txt' and the directory " +"`CMakeFiles'. Please delete them.") +endif() + +# Add appropriate flags for GCC +if (LLVM_COMPILER_IS_GCC_COMPATIBLE) + if (APPLE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-common -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-common -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing -pedantic -Wno-long-long -Wall -W -Wno-unused-parameter -Wwrite-strings") + endif () +endif () + +# Fixes "C++ exception handler used, but unwind semantics are not enabled" warning Windows +if (MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") +endif () + +if (APPLE) + set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-flat_namespace -Wl,-undefined -Wl,suppress") +endif () + +# FIXME: Use merge this with the content from the LLVMConfig and ClangConfig. +if (NOT CPPINTEROP_BUILT_STANDALONE) +include_directories(BEFORE SYSTEM + ${CMAKE_CURRENT_BINARY_DIR}/../clang/include + ${CMAKE_CURRENT_SOURCE_DIR}/../clang/include + ) +endif() + +include_directories(BEFORE SYSTEM + ${CMAKE_CURRENT_BINARY_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/include + ) + +#Removes flag due to issue with Google test download when LLVM_ENABLE_WERROR=On +string(REPLACE "-Wcovered-switch-default" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + +file(STRINGS "VERSION" CPPINTEROP_VERSION) +string(REPLACE "." ";" VERSION_LIST "${CPPINTEROP_VERSION}") +list(GET VERSION_LIST 0 CPPINTEROP_VERSION_MAJOR) +list(GET VERSION_LIST 1 CPPINTEROP_VERSION_MINOR) +list(GET VERSION_LIST 2 CPPINTEROP_VERSION_PATCH) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CppInterOp/CppInterOpConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/CppInterOp/CppInterOpConfig.cmake + @ONLY) +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CppInterOp/CppInterOpConfigVersion.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/CppInterOp/CppInterOpConfigVersion.cmake + @ONLY) +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 ".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" + ) + +add_definitions( -D_GNU_SOURCE ) + +# Add deps if we build together with clang. +if (TARGET intrinsics_gen) + list(APPEND LLVM_COMMON_DEPENDS intrinsics_gen) +endif() +if (TARGET clang-headers) + list(APPEND LLVM_COMMON_DEPENDS clang-headers) +endif() + +# Generate docs for CppInterOp +option(CPPINTEROP_INCLUDE_DOCS "Generate build targets for the CppInterOp docs.") +option(CPPINTEROP_ENABLE_DOXYGEN "Use doxygen to generate CppInterOp interal API documentation.") +option(CPPINTEROP_ENABLE_SPHINX "Use sphinx to generage CppInterOp user documentation") + + +if(EMSCRIPTEN) + message("Build with emscripten") + option(CPPINTEROP_ENABLE_TESTING "Enables the testing infrastructure." OFF) +else() + message("Build with cmake") + option(CPPINTEROP_ENABLE_TESTING "Enables the testing infrastructure." ON) +endif() + +if(MSVC) + + set(MSVC_EXPORTLIST + _Init_thread_header + _Init_thread_footer + ?nothrow@std@@3Unothrow_t@1@B + ??_7type_info@@6B@ + ) + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + # new/delete variants needed when linking to static msvc runtime (esp. Debug) + set(MSVC_EXPORTLIST ${MSVC_EXPORTLIST} + ??2@YAPEAX_K@Z + ??3@YAXPEAX@Z + ??_U@YAPEAX_K@Z + ??_V@YAXPEAX@Z + ??3@YAXPEAX_K@Z + ??_V@YAXPEAX_K@Z + ??2@YAPEAX_KAEBUnothrow_t@std@@@Z + ??_U@YAPEAX_KAEBUnothrow_t@std@@@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@H@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@M@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@N@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@PEBX@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@P6AAEAV01@AEAV01@@Z@Z + ??$?6U?$char_traits@D@std@@@std@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@D@Z + ??$?6U?$char_traits@D@std@@@std@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@PEBD@Z + ?_Facet_Register@std@@YAXPEAV_Facet_base@1@@Z + ) + else() + set(MSVC_EXPORTLIST ${MSVC_EXPORTLIST} + ??2@YAPAXI@Z + ??3@YAXPAX@Z + ??3@YAXPAXI@Z + ??_U@YAPAXI@Z + ??_V@YAXPAX@Z + ??_V@YAXPAXI@Z + ??2@YAPAXIABUnothrow_t@std@@@Z + ??_U@YAPAXIABUnothrow_t@std@@@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@M@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@N@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@PBX@Z + ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z + ??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@D@Z + ??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z + ?_Facet_Register@std@@YAXPAV_Facet_base@1@@Z + ) + endif() + + if(MSVC_VERSION LESS 1914) + set(MSVC_EXPORTLIST ${MSVC_EXPORTLIST} ??3@YAXPAX0@Z ??_V@YAXPAX0@Z) + endif() + + if(MSVC_VERSION GREATER_EQUAL 1936) + set(MSVC_EXPORTLIST ${MSVC_EXPORTLIST} + __std_find_trivial_1 + __std_find_trivial_2 + __std_find_trivial_4 + __std_find_trivial_8 + ) + endif() + +foreach(sym ${MSVC_EXPORTLIST}) + set(MSVC_EXPORTS "${MSVC_EXPORTS} /EXPORT:${sym}") +endforeach(sym ${MSVC_EXPORTLIST}) + +endif() + +if (CPPINTEROP_INCLUDE_DOCS) + add_subdirectory(docs) +endif() + +add_subdirectory(lib) +if (CPPINTEROP_ENABLE_TESTING) + add_subdirectory(unittests) +endif(CPPINTEROP_ENABLE_TESTING) diff --git a/interpreter/CppInterOp/CONTRIBUTING.md b/interpreter/CppInterOp/CONTRIBUTING.md new file mode 100644 index 0000000000000..b3816270cc357 --- /dev/null +++ b/interpreter/CppInterOp/CONTRIBUTING.md @@ -0,0 +1,74 @@ +# Overview + +Thank you for investing your time in contributing to our project! There are +numbers of ways to contribute to the project and we appreciate all of them. If +you like the project please give CppInterOp a star. + +Any contribution to open source makes a difference! + +## Are you new to open source, git or GitHub? + +To get an overview of the project, read the [README](README.md). Here are some +resources to help you get started with open source contributions: + +- [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github) +- [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git) +- [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) +- [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests) + +## Are you a contributor looking for a challenging summer project? + +Various opportunities such as information about google summer of code is +generally published on the [Compiler Research Open Projects page](https://compiler-research.org/open_projects). +If you have used CppInterOp and you have particular project proposal please reach out. + +## Ways to contribute + +### Submit a bug report + +If something does not seem right [search if an issue already exists](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments) +in [CppInterOps issue tracker](https://github.com/compiler-research/CppInterOp/issues). If a related issue doesn't exist, you can open a +new issue using a relevant [issue form](https://github.com/compiler-research/CppInterOp/issues/new). + +### Good first issues + +Some issues have been marked as ["good first issues"](https://github.com/compiler-research/CppInterOp/labels/good%20first%20issue). +These are intended to be a good place to start contributing. + +### Write documentation + +Documentation is critical for any open source project, especially for complex +projects such as CppInterOp. We have our documentation in the repository which is then +rendered in the [CppInterOp.readthedocs](https://cppinterop.readthedocs.io/en/latest/) website. +Documentation modifications happen by proposing a pull request. + +## Creating a successfull pull request + +To propose a code modification we use the pull requests. Pull requests which +review quickly and successfully share several common traits: + +- Sharp -- intends to fix a concrete problem. Usually the pull request addresses + an already opened issue; +- Atomic -- has one or more commits that can be reverted without any unwanted + side effects or regressions, aside from what you’d expect based on its + message. [More on atomic commits in git](https://www.aleksandrhovhannisyan.com/blog/atomic-git-commits/). +- Descriptive -- has a good description in what is being solved. This + information is usually published as part of the pull request description and + as part of the commit message. Writing good commit messages are critical. More + [here](https://github.blog/2022-06-30-write-better-commits-build-better-projects/) + and [here](https://cbea.ms/git-commit/). If your pull request fixes an existing + issue from the bug tracker make sure that the commit log and the pull request + description mentions `Fixes: #`. That will link both and will + close the issue automatically upon merging. +- Tested -- has a set of tests making sure that the issue will not resurface + without a notice. Usually the codecov bots annotate the code paths that are + not tested in the pull request after being run. +- Documented -- has good amount of code comment. The test cases are also a good + source of documentation. [Here](https://stackoverflow.blog/2021/12/23/best-practices-for-writing-code-comments/) + is a guideline about how write good code comments. [Here](https://stackoverflow.com/questions/184618/what-is-the-best-comment-in-source-code-you-have-ever-encountered) + are examples of what *not* to write as a code comment. + +### Developer Documentation + +We have documented several useful hints that usually help when addressing issues +as they come during developement time in our [developer documentation](https://cppinterop.readthedocs.io/en/latest/InstallationAndUsage.html). diff --git a/interpreter/CppInterOp/Emscripten-build-instructions.md b/interpreter/CppInterOp/Emscripten-build-instructions.md new file mode 100644 index 0000000000000..662643db33337 --- /dev/null +++ b/interpreter/CppInterOp/Emscripten-build-instructions.md @@ -0,0 +1,160 @@ +# Wasm Build Instructions + +It should be noted that the wasm build of CppInterOp is still experimental and subject to change. + +## 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 for osx and Linux. +We will first make folder to build our wasm build of CppInterOp. This can be done by executing the following command + +```bash +mkdir CppInterOp-wasm +``` + +Now move into this directory using the following command + +```bash +cd ./CppInterOp-wasm +``` + +To create a wasm build of CppInterOp we make use of the emsdk toolchain. This can be installed by executing (we only currently +support version 3.1.73) +```bash +git clone https://github.com/emscripten-core/emsdk.git +./emsdk/emsdk install 3.1.73 +``` + +and activate the emsdk environment + +```bash +./emsdk/emsdk activate 3.1.73 +source ./emsdk/emsdk_env.sh +``` + +Now clone the 19.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 appropriately) + + +```bash +git clone --depth=1 --branch release/19.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 + +```bash +cd ./llvm-project/ +git apply -v ../CppInterOp/patches/llvm/emscripten-clang19-*.patch +``` + +We are now in a position to build an emscripten build of llvm by executing the following +```bash +mkdir build +cd build +emcmake cmake -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_HOST_TRIPLE=wasm32-unknown-emscripten \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DLLVM_TARGETS_TO_BUILD="WebAssembly" \ + -DLLVM_ENABLE_LIBEDIT=OFF \ + -DLLVM_ENABLE_PROJECTS="clang;lld" \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + -DCMAKE_CXX_FLAGS="-Dwait4=__syscall_wait4" \ + -DLLVM_INCLUDE_BENCHMARKS=OFF \ + -DLLVM_INCLUDE_EXAMPLES=OFF \ + -DLLVM_INCLUDE_TESTS=OFF \ + -DLLVM_ENABLE_THREADS=OFF \ + ../llvm +emmake make clang -j $(nproc --all) +emmake make clang-repl -j $(nproc --all) +emmake make lld -j $(nproc --all) +``` + +Once this finishes building we need to take note of where we built our llvm build. This can be done by executing the following + +```bash +export LLVM_BUILD_DIR=$PWD +``` + +We can move onto building the wasm version of CppInterOp. We will do this within a Conda environment. We can achieve this +by executing (assumes you have micromamba installed and that your shell is initialised for the micromamba install) + +```bash +cd ../../CppInterOp/ +micromamba create -f environment-wasm.yml --platform=emscripten-wasm32 +micromamba activate CppInterOp-wasm +``` + +You will also want to set a few environment variables + +```bash +export PREFIX=$CONDA_PREFIX +export CMAKE_PREFIX_PATH=$PREFIX +export CMAKE_SYSTEM_PREFIX_PATH=$PREFIX +``` + +Now to build CppInterOp execute the following + +```bash +mkdir build +cd ./build/ +emcmake cmake -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_DIR=$LLVM_BUILD_DIR/lib/cmake/llvm \ + -DLLD_DIR=$LLVM_BUILD_DIR/lib/cmake/lld \ + -DClang_DIR=$LLVM_BUILD_DIR/lib/cmake/clang \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + ../ +emmake make -j $(nproc --all) install +``` + +Once this finishes building we need to take note of where we built CppInterOp. This can be done by executing the following + +```bash +export CPPINTEROP_BUILD_DIR=$PWD +``` + +## Xeus-cpp-lite Wasm Build Instructions + +A project which makes use of the wasm build of CppInterOp is xeus-cpp. xeus-cpp is a C++ Jupyter kernel. Assuming you are in +the CppInterOp build folder, you can build the wasm version of xeus-cpp by executing + +```bash +cd ../.. +export SYSROOT_PATH=$PWD/emsdk/upstream/emscripten/cache/sysroot +git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git +cd ./xeus-cpp +mkdir build +cd build +emcmake cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=$PREFIX \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DXEUS_CPP_EMSCRIPTEN_WASM_BUILD=ON \ + -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ + -DCppInterOp_DIR="$CPPINTEROP_BUILD_DIR/lib/cmake/CppInterOp" \ + -DSYSROOT_PATH=$SYSROOT_PATH \ + .. + emmake make -j $(nproc --all) install +``` + +To build Jupyter Lite website with this kernel locally that you can use for testing execute the following + +```bash +cd ../.. +micromamba create -n xeus-lite-host jupyterlite-core -c conda-forge +micromamba activate xeus-lite-host +python -m pip install jupyterlite-xeus jupyter_server +jupyter lite build --XeusAddon.prefix=$PREFIX --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb +``` + +Once the Jupyter Lite site has built you can test the website locally by executing + +```bash +jupyter lite serve --XeusAddon.prefix=$PREFIX +``` diff --git a/interpreter/CppInterOp/LICENSE.txt b/interpreter/CppInterOp/LICENSE.txt new file mode 100644 index 0000000000000..3959e211f863c --- /dev/null +++ b/interpreter/CppInterOp/LICENSE.txt @@ -0,0 +1,234 @@ +============================================================================== +The CppInterOp Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the CppInterOp Project: +============================================================================== +The CppInterOp Project contains third party software which is under different +license terms. All such code will be identified clearly using at least one of +two mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. diff --git a/interpreter/CppInterOp/README.md b/interpreter/CppInterOp/README.md new file mode 100644 index 0000000000000..1e4779c96abd4 --- /dev/null +++ b/interpreter/CppInterOp/README.md @@ -0,0 +1,482 @@ +# CppInterOp + +[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/Ubuntu.yml) + +[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/Ubuntu-arm.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/Ubuntu-arm.yml) + +[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/MacOS.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/MacOS.yml) + +[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/MacOS-arm.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/MacOS-arm.yml) + +[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/Windows.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/Windows.yml) + +[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/emscripten.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/emscripten.yml) + +[![codecov](https://codecov.io/gh/compiler-research/CppInterOp/branch/main/graph/badge.svg)](https://codecov.io/gh/compiler-research/CppInterOp) + +[![Conda-Forge](https://img.shields.io/conda/vn/conda-forge/cppinterop)](https://github.com/conda-forge/cppinterop-feedstock) + +[![Anaconda-Server Badge](https://anaconda.org/conda-forge/cppinterop/badges/license.svg)](https://github.com/conda-forge/cppinterop-feedstock) + +[![Conda Platforms](https://img.shields.io/conda/pn/conda-forge/cppinterop.svg)](https://anaconda.org/conda-forge/cppinterop) + +[![Anaconda-Server Badge](https://anaconda.org/conda-forge/cppinterop/badges/downloads.svg)](https://github.com/conda-forge/cppinterop-feedstock) + +CppInterOp exposes API from [Clang](http://clang.llvm.org/) and [LLVM](https://llvm.org) in a backward compatible way. +The API support downstream tools that utilize interactive C++ by using the compiler as a service. +That is, embed Clang and LLVM as a libraries in their codebases. +The API are designed to be minimalistic and aid non-trivial tasks such as language interoperability on the fly. +In such scenarios CppInterOp can be used to provide the necessary introspection information to the other side helping the language cross talk. + +[Installation](#build-instructions) + +[Documentation](https://cppinterop.readthedocs.io/en/latest/index.html) + +[CppInterOp API Documentation](https://cppinterop.readthedocs.io/en/latest/build/html/index.html) + +Try Jupyter Lite CppInterOp demo by clicking below + +[![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://compiler-research.github.io/CppInterOp/lab/index.html) + +## CppInterOp Introduction + +The CppInterOp library provides a minimalist approach for other languages to +bridge C++ entities (variables, classes, etc.). This +enables interoperability with C++ code, bringing the speed and +efficiency of C++ to simpler, more interactive languages like Python. + +Join our discord for discussions and collaboration. + +Discord + +### Incremental Adoption + +CppInterOp can be adopted incrementally. While the rest of the framework is +the same, a small part of CppInterOp can be utilized. More components may be +adopted over time. + +### Minimalist by design + +While the library includes some tricky code, it is designed to be simple and +robust (simple function calls, no inheritance, etc.). The goal is to make it +as close to the compiler API as possible, and each routine to do just one +thing that it was designed for. + +### Further Enhancing the Dynamic/Automatic bindings in CPPYY + +The main use case for CppInterOp is with the CPPYY service. CPPYY is an +automatic run-time bindings generator for Python & C++, and supports a wide +range of C++ features (e.g., template instantiation). It operates on demand +and generates only what is necessary. It requires a compiler (Cling[^1] +/Clang-REPL[^2]) that can be available during program runtime. + +Once CppInterOp is integrated with LLVM's[^3] Clang-REPL component (that can +then be used as a runtime compiler for CPPYY), it will further enhance +CPPYY's performance in the following ways: + +- **Simpler codebase**: Removal of string parsing logic will lead to a + simpler code base. +- **LLVM Integration**: The CppInterOp interfaces will be a part of the LLVM + toolchain (as part of Clang-REPL). +- **Better C++ Support**: C++ features such as Partial Template + Specialization will be available through CppInterOp. +- **Fewer Lines of Code**: A lot of dependencies and workarounds will be + removed, reducing the lines of code required to execute CPPYY. +- **Well tested interoperability Layer**: The CppInterOp interfaces have full + unit test coverage. + +### 'Roots' in High Energy Physics research + +Besides being developed as a general-purpose library, one of the long-term +goals of CppInterOp is to stay backward compatible and be adopted in the High +Energy Physics (HEP) field, as it will become an essential part of the Root +framework. Over time, parts of the Root framework can be swapped by this API, +adding speed and resilience with it. + +### Build Instructions + +Build instructions for CppInterOp and its dependencies are as follows. CppInterOP can be built with either Cling and Clang-REPL, so instructions will differ slightly depending on which option you would like to build, but should be clear from the section title which instructions to follow. + +#### Clone CppInterOp and cppyy-backend + +First clone the CppInterOp repository, as this contains patches that need to be applied to the subsequently cloned llvm-project repo (these patches are only applied if building CppInterOp with Clang-REPL) + +```bash +git clone --depth=1 https://github.com/compiler-research/CppInterOp.git +``` + +and clone cppyy-backend repository where we will be installing the CppInterOp library + +```bash +git clone --depth=1 https://github.com/compiler-research/cppyy-backend.git +``` + +#### Setup Clang-REPL + +Clone the 19.x release of the LLVM project repository. + +```bash +git clone --depth=1 --branch release/19.x https://github.com/llvm/llvm-project.git +cd llvm-project +``` + +For Clang 16 & 17, the following patches required for development work. To apply these patches on Linux and MacOS execute the following command(substitute `{version}` with your clang version): + +```bash +git apply -v ../CppInterOp/patches/llvm/clang{version}-*.patch +``` + +and + +```powershell +cp -r ..\CppInterOp\patches\llvm\clang{version}* . +git apply -v clang{version}-*.patch +``` + +on Windows. + +##### Build Clang-REPL + +Clang-REPL is an interpreter that CppInterOp works alongside. Build Clang (and +Clang-REPL along with it). On Linux and MacOS you do this by executing the following +command + +```bash +mkdir build +cd build +cmake -DLLVM_ENABLE_PROJECTS=clang \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_TERMINFO=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + ../llvm +cmake --build . --target clang clang-repl --parallel $(nproc --all) +``` + +On Windows you would do this by executing the following + +```powershell +$env:ncpus = $([Environment]::ProcessorCount) +mkdir build +cd build +cmake -DLLVM_ENABLE_PROJECTS=clang ` + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" ` + -DCMAKE_BUILD_TYPE=Release ` + -DLLVM_ENABLE_ASSERTIONS=ON ` + -DCLANG_ENABLE_STATIC_ANALYZER=OFF ` + -DCLANG_ENABLE_ARCMT=OFF ` + -DCLANG_ENABLE_FORMAT=OFF ` + -DCLANG_ENABLE_BOOTSTRAP=OFF ` + ..\llvm + cmake --build . --target clang clang-repl --parallel $env:ncpus +``` + +Note the 'llvm-project' directory location. On linux and MacOS you execute the following + +```bash +cd ../ +export LLVM_DIR=$PWD +cd ../ +``` + +On Windows you execute the following + +```powershell +cd ..\ +$env:LLVM_DIR= $PWD.Path +cd ..\ +``` + +#### Build Cling and related dependencies + +Besides the Clang-REPL interpreter, CppInterOp also works alongside the Cling +interpreter. Cling depends on its own customised version of `llvm-project`, +hosted under the `root-project` (see the git path below). +Use the following build instructions to build on Linux and MacOS + +```bash +git clone https://github.com/root-project/cling.git +cd ./cling/ +git checkout tags/v1.0 +cd .. +git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git +mkdir llvm-project/build +cd llvm-project/build +cmake -DLLVM_ENABLE_PROJECTS=clang \ + -DLLVM_EXTERNAL_PROJECTS=cling \ + -DLLVM_EXTERNAL_CLING_SOURCE_DIR=../../cling \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_TERMINFO=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + ../llvm +cmake --build . --target clang --parallel $(nproc --all) +cmake --build . --target cling --parallel $(nproc --all) +cmake --build . --target gtest_main --parallel $(nproc --all) +``` + +Use the following build instructions to build on Windows + +```powershell +git clone https://github.com/root-project/cling.git +cd .\cling\ +git checkout tags/v1.0 +cd .. +git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git +$env:ncpus = %NUMBER_OF_PROCESSORS% +$env:PWD_DIR= $PWD.Path +$env:CLING_DIR="$env:PWD_DIR\cling" +mkdir llvm-project\build +cd llvm-project\build +cmake -DLLVM_ENABLE_PROJECTS=clang ` + -DLLVM_EXTERNAL_PROJECTS=cling ` + -DLLVM_EXTERNAL_CLING_SOURCE_DIR="$env:CLING_DIR" ` + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" ` + -DCMAKE_BUILD_TYPE=Release ` + -DLLVM_ENABLE_ASSERTIONS=ON ` + -DCLANG_ENABLE_STATIC_ANALYZER=OFF ` + -DCLANG_ENABLE_ARCMT=OFF ` + -DCLANG_ENABLE_FORMAT=OFF ` + -DCLANG_ENABLE_BOOTSTRAP=OFF ` + ../llvm +cmake --build . --target clang --parallel $env:ncpus +cmake --build . --target cling --parallel $env:ncpus +cmake --build . --target gtest_main --parallel $env:ncpus +``` + +Note the 'llvm-project' directory location. On linux and MacOS you execute the following + +```bash +cd ../ +export LLVM_DIR=$PWD +cd ../ +``` + +On Windows you execute the following + +```powershell +cd ..\ +$env:LLVM_DIR= $PWD.Path +cd ..\ +``` + +#### Environment variables + +Regardless of whether you are building CppInterOP with Cling or Clang-REPL you will need to define the following environment variables (as they clear for a new session, it is recommended that you also add these to your .bashrc in linux, .bash_profile if on MacOS, or profile.ps1 on Windows). On Linux and MacOS you define as follows + +```bash +export CB_PYTHON_DIR="$PWD/cppyy-backend/python" +export CPPINTEROP_DIR="$CB_PYTHON_DIR/cppyy_backend" +export CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH}:${LLVM_DIR}/llvm/include:${LLVM_DIR}/clang/include:${LLVM_DIR}/build/include:${LLVM_DIR}/build/tools/clang/include" +``` + +If on MacOS you will also need the following environment variable defined + +```bash +export SDKROOT=`xcrun --show-sdk-path` +``` + +On Windows you define as follows (assumes you have defined $env:PWD_DIR= $PWD.Path ) + +```powershell +$env:CB_PYTHON_DIR="$env:PWD_DIR\cppyy-backend\python" +$env:CPPINTEROP_DIR="$env:CB_PYTHON_DIR\cppyy_backend" +$env:CPLUS_INCLUDE_PATH="$env:CPLUS_INCLUDE_PATH;$env:LLVM_DIR\llvm\include;$env:LLVM_DIR\clang\include;$env:LLVM_DIR\build\include;$env:LLVM_DIR\build\tools\clang\include" +``` + +#### Build CppInterOp + +Now CppInterOp can be installed. On Linux and MacOS execute + +```bash +mkdir CppInterOp/build/ +cd CppInterOp/build/ +``` + +On Windows execute + +```powershell +mkdir CppInterOp\build\ +cd CppInterOp\build\ +``` + +Now if you want to build CppInterOp with Clang-REPL then execute the following commands on Linux and MacOS + +```bash +cmake -DBUILD_SHARED_LIBS=ON -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. +cmake --build . --target install --parallel $(nproc --all) +``` + +and + +```powershell +cmake -DLLVM_DIR=$env:LLVM_DIR\build\lib\cmake\llvm -DClang_DIR=$env:LLVM_DIR\build\lib\cmake\clang -DCMAKE_INSTALL_PREFIX=$env:CPPINTEROP_DIR .. +cmake --build . --target install --parallel $env:ncpus +``` + +on Windows. If alternatively you would like to install CppInterOp with Cling then execute the following commands on Linux and MacOS + +```bash +cmake -DBUILD_SHARED_LIBS=ON -DCPPINTEROP_USE_CLING=ON -DCPPINTEROP_USE_REPL=Off -DCling_DIR=$LLVM_DIR/build/tools/cling -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. +cmake --build . --target install --parallel $(nproc --all) +``` + +and + +```powershell +cmake -DCPPINTEROP_USE_CLING=ON -DCPPINTEROP_USE_REPL=Off -DCling_DIR=$env:LLVM_DIR\build\tools\cling -DLLVM_DIR=$env:LLVM_DIR\build\lib\cmake\llvm -DClang_DIR=$env:LLVM_DIR\build\lib\cmake\clang -DCMAKE_INSTALL_PREFIX=$env:CPPINTEROP_DIR .. +cmake --build . --target install --parallel $env:ncpus +``` + +on Windows. + +#### Testing CppInterOp + +To test the built CppInterOp execute the following command in the CppInterOP build folder on Linux and MacOS + +```bash +cmake --build . --target check-cppinterop --parallel $(nproc --all) +``` + +and + +```powershell +cmake --build . --target check-cppinterop --parallel $env:ncpus +``` + +on Windows. Now go back to the top level directory in which your building CppInterOP. On Linux and MacOS you do this by executing + +```bash +cd ../.. +``` + +and + +```powershell +cd ..\.. +``` + +on Windows. Now you are in a position to install cppyy following the instructions below. + +#### Building and Install cppyy-backend + +Cd into the cppyy-backend directory, build it and copy library files into `python/cppyy-backend` directory: + +```bash +cd cppyy-backend +mkdir -p python/cppyy_backend/lib build +cd build +cmake -DCppInterOp_DIR=$CPPINTEROP_DIR .. +cmake --build . +``` + +If on a linux system now execute the following command + +```bash +cp libcppyy-backend.so ../python/cppyy_backend/lib/ +``` + +and if on MacOS execute the following command + +```bash +cp libcppyy-backend.dylib ../python/cppyy_backend/lib/ +``` + +Note go back to the top level build directory + +```bash +cd ../.. +``` + +#### Install CPyCppyy + +Create virtual environment and activate it: + +```bash +python3 -m venv .venv +source .venv/bin/activate +``` + +```bash +git clone --depth=1 https://github.com/compiler-research/CPyCppyy.git +mkdir CPyCppyy/build +cd CPyCppyy/build +cmake .. +cmake --build . +``` + +Note down the path to the `build` directory as `CPYCPPYY_DIR`: + +```bash +export CPYCPPYY_DIR=$PWD +cd ../.. +``` + +Export the `libcppyy` path to python: + +```bash +export PYTHONPATH=$PYTHONPATH:$CPYCPPYY_DIR:$CB_PYTHON_DIR +``` + +and on Windows: + +```powershell +$env:PYTHONPATH="$env:PYTHONPATH;$env:CPYCPPYY_DIR;$env:CB_PYTHON_DIR" +``` + +#### Install cppyy + +```bash +git clone --depth=1 https://github.com/compiler-research/cppyy.git +cd cppyy +python -m pip install --upgrade . --no-deps --no-build-isolation +cd .. +``` + +#### Run cppyy + +Each time you want to run cppyy you need to: +Activate the virtual environment + +```bash +source .venv/bin/activate +``` + +Now you can `import cppyy` in `python` + +```bash +python -c "import cppyy" +``` + +#### Run cppyy tests + +**Follow the steps in Run cppyy.** Change to the test directory, make the library files and run pytest: + +```bash +cd cppyy/test +make all +python -m pip install pytest +python -m pytest -sv +``` + +______________________________________________________________________ + +Further Reading: [C++ Language Interoperability Layer](https://compiler-research.org/libinterop/) + +\[^1\]: Cling is an interpretive Compiler for C++. +\[^2\]: Clang-REPL is an interactive C++ interpreter that enables incremental +compilation. +\[^3\]: LLVM is a Compiler Framework. It is a collection of modular compiler +and toolchain technologies. diff --git a/interpreter/CppInterOp/VERSION b/interpreter/CppInterOp/VERSION new file mode 100644 index 0000000000000..3c1aaa2b0fe7e --- /dev/null +++ b/interpreter/CppInterOp/VERSION @@ -0,0 +1 @@ +1.6.0;dev diff --git a/interpreter/CppInterOp/cmake/CppInterOp/CppInterOpConfig.cmake.in b/interpreter/CppInterOp/cmake/CppInterOp/CppInterOpConfig.cmake.in new file mode 100644 index 0000000000000..47707d9200de7 --- /dev/null +++ b/interpreter/CppInterOp/cmake/CppInterOp/CppInterOpConfig.cmake.in @@ -0,0 +1,78 @@ +# This file allows users to call find_package(CppInterOp) and pick up our targets. + +# Compute the installation prefix from this CppInterOpConfig.cmake file location. +get_filename_component(CPPINTEROP_INSTALL_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(CPPINTEROP_INSTALL_PREFIX "${CPPINTEROP_INSTALL_PREFIX}" PATH) +get_filename_component(CPPINTEROP_INSTALL_PREFIX "${CPPINTEROP_INSTALL_PREFIX}" PATH) +get_filename_component(CPPINTEROP_INSTALL_PREFIX "${CPPINTEROP_INSTALL_PREFIX}" PATH) + +# Determine CMAKE_SHARED_LIBRARY_SUFFIX based on operating system +include(CMakeSystemSpecificInformation) + +if(MSVC) + set(shared_lib_dir bin) +else() + set(shared_lib_dir lib) +endif() + +### build/install workaround +if (@BUILD_SHARED_LIBS@) + set(_lib_suffix ${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(_lib_prefix ${CMAKE_SHARED_LIBRARY_PREFIX}) +else() + set(_lib_suffix ${CMAKE_STATIC_LIBRARY_SUFFIX}) + set(_lib_prefix ${CMAKE_STATIC_LIBRARY_PREFIX}) +endif() + +if (IS_DIRECTORY "${CPPINTEROP_INSTALL_PREFIX}/include") + set(_include "${CPPINTEROP_INSTALL_PREFIX}/include") + set(_lib "${CPPINTEROP_INSTALL_PREFIX}/${shared_lib_dir}/${_lib_prefix}clangCppInterOp${_lib_suffix}") + set(_cmake "${CPPINTEROP_INSTALL_PREFIX}/${shared_lib_dir}/cmake/CppInterOp") +else() + set(_include "@CMAKE_CURRENT_SOURCE_DIR@/include") + set(_lib "@CMAKE_CURRENT_BINARY_DIR@/${shared_lib_dir}/${_lib_prefix}clangCppInterOp${_lib_suffix}") + set(_cmake "@CMAKE_CURRENT_BINARY_DIR@/${shared_lib_dir}/cmake/CppInterOp") +endif() + +### + +set(CPPINTEROP_EXPORTED_TARGETS "clangCppInterOp") +set(CPPINTEROP_CMAKE_DIR "${_cmake}") +set(CPPINTEROP_INCLUDE_DIRS "${_include}") +set(CPPINTEROP_LIBRARIES "${_lib}") + +set(CPPINTEROP_LLVM_VERSION "@LLVM_VERSION@") +set(CPPINTEROP_LLVM_VERSION_MAJOR "@LLVM_VERSION_MAJOR@") +set(CPPINTEROP_LLVM_VERSION_MINOR "@LLVM_VERSION_MINOR@") +set(CPPINTEROP_LLVM_VERSION_PATCH "@LLVM_VERSION_PATCH@") +set(CPPINTEROP_LLVM_VERSION_SUFFIX "@LLVM_VERSION_SUFFIX@") +set(CPPINTEROP_LLVM_PACKAGE_VERSION "@CPPINTEROP_LLVM_VERSION@") +set(CPPINTEROP_VERSION "@CPPINTEROP_VERSION@") + +# Provide all our library targets to users. +if (@BUILD_SHARED_LIBS@) + add_library(clangCppInterOp SHARED IMPORTED) +else() + add_library(clangCppInterOp STATIC IMPORTED) +endif() +set_target_properties(clangCppInterOp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${_include} + IMPORTED_LOCATION ${_lib} + ) +if (MSVC) + if (IS_DIRECTORY "${CPPINTEROP_INSTALL_PREFIX}/include") + set(_static_lib "${CPPINTEROP_INSTALL_PREFIX}/lib/${_lib_prefix}clangCppInterOp.lib") + else() + set(_static_lib "@CMAKE_CURRENT_BINARY_DIR@/lib/${_lib_prefix}clangCppInterOp.lib") + endif() + + set_target_properties(clangCppInterOp PROPERTIES + IMPORTED_IMPLIB ${_static_lib} + ) +endif(MSVC) + +unset(_lib_prefix) +unset(_lib_suffix) +unset(_cmake) +unset(_include) +unset(_lib) diff --git a/interpreter/CppInterOp/cmake/CppInterOp/CppInterOpConfigVersion.cmake.in b/interpreter/CppInterOp/cmake/CppInterOp/CppInterOpConfigVersion.cmake.in new file mode 100644 index 0000000000000..a88b99b0ceb06 --- /dev/null +++ b/interpreter/CppInterOp/cmake/CppInterOp/CppInterOpConfigVersion.cmake.in @@ -0,0 +1,9 @@ +set(PACKAGE_VERSION "@CPPINTEROP_VERSION@") + +if("${PACKAGE_FIND_VERSION_MAJOR}.${PACKAGE_FIND_VERSION_MINOR}" VERSION_LESS "@CPPINTEROP_VERSION_MAJOR@.@CPPINTEROP_VERSION_MINOR@" + AND NOT "@CPPINTEROP_VERSION_PATCH@" VERSION_LESS "${PACKAGE_FIND_VERSION_PATCH}") + set(PACKAGE_VERSION_COMPATIBLE 1) +endif() +if("@CPPINTEROP_VERSION_MAJOR@.@CPPINTEROP_VERSION_MINOR@.@CPPINTEROP_VERSION_PATCH@" VERSION_EQUAL "${PACKAGE_FIND_VERSION_MAJOR}.${PACKAGE_FIND_VERSION_MINOR}.${PACKAGE_FIND_VERSION_PATCH}") + set(PACKAGE_VERSION_EXACT 1) +endif() diff --git a/interpreter/CppInterOp/cmake/CreateSphinxTarget.cmake b/interpreter/CppInterOp/cmake/CreateSphinxTarget.cmake new file mode 100644 index 0000000000000..90ea5d9e3b4b6 --- /dev/null +++ b/interpreter/CppInterOp/cmake/CreateSphinxTarget.cmake @@ -0,0 +1,25 @@ +# Implementation of 'create_sphinx_target' in this file is copied from +# llvm implementation of 'AddSphinxTarget'. +# https://github.com/llvm/llvm-project/blob/main/llvm/cmake/modules/AddSphinxTarget.cmake + +find_package(Sphinx REQUIRED) + +function(create_sphinx_target) + cmake_parse_arguments(SPHINX + "" # options + "SOURCE_DIR;TARGET_NAME" + "" # multi-value keywords + ${ARGN} + ) + set(SPHINX_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/build) + set(SPHINX_DOC_TREE_DIR ${CMAKE_CURRENT_BINARY_DIR}/_doctrees) + + add_custom_target(${SPHINX_TARGET_NAME} + COMMAND + ${SPHINX_EXECUTABLE} -b html -d ${SPHINX_DOC_TREE_DIR} -q ${SPHINX_SOURCE_DIR} ${SPHINX_BUILD_DIR} + COMMENT + "Generating sphinx user documentation into \"${SPHINX_BUILD_DIR}\"" + VERBATIM + ) + message(STATUS "Added ${SPHINX_TARGET_NAME} target") +endfunction() \ No newline at end of file diff --git a/interpreter/CppInterOp/cmake/FindSphinx.cmake b/interpreter/CppInterOp/cmake/FindSphinx.cmake new file mode 100644 index 0000000000000..0fc54a1ee10d9 --- /dev/null +++ b/interpreter/CppInterOp/cmake/FindSphinx.cmake @@ -0,0 +1,27 @@ +# CMake find_package() Module for Sphinx documentation generator +# http://sphinx-doc.org/ +# +# Example usage: +# +# find_package(Sphinx) +# +# If successful the following variables will be defined +# SPHINX_FOUND +# SPHINX_EXECUTABLE + +find_program(SPHINX_EXECUTABLE + NAMES sphinx-build sphinx-build2 + DOC "Path to sphinx-build executable") + +# Handle REQUIRED and QUIET arguments +# this will also set SPHINX_FOUND to true if SPHINX_EXECUTABLE exists +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Sphinx + "Failed to locate sphinx-build executable" + SPHINX_EXECUTABLE) + +# Provide options for controlling different types of output +option(SPHINX_OUTPUT_HTML "Output standalone HTML files" ON) +option(SPHINX_OUTPUT_MAN "Output man pages" ON) + +option(SPHINX_WARNINGS_AS_ERRORS "When building documentation treat warnings as errors" ON) \ No newline at end of file diff --git a/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake b/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake new file mode 100644 index 0000000000000..a753c1c94fdf5 --- /dev/null +++ b/interpreter/CppInterOp/cmake/modules/GoogleTest.cmake @@ -0,0 +1,88 @@ +set(_gtest_byproduct_binary_dir + ${CMAKE_BINARY_DIR}/downloads/googletest-prefix/src/googletest-build) +set(_gtest_byproducts + ${_gtest_byproduct_binary_dir}/lib/libgtest.a + ${_gtest_byproduct_binary_dir}/lib/libgtest_main.a + ${_gtest_byproduct_binary_dir}/lib/libgmock.a + ${_gtest_byproduct_binary_dir}/lib/libgmock_main.a + ) + +if(WIN32) + set(EXTRA_GTEST_OPTS + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=${_gtest_byproduct_binary_dir}/lib/ + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL:PATH=${_gtest_byproduct_binary_dir}/lib/ + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=${_gtest_byproduct_binary_dir}/lib/ + -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO:PATH=${_gtest_byproduct_binary_dir}/lib/ + -Dgtest_force_shared_crt=ON + BUILD_COMMAND ${CMAKE_COMMAND} --build --config $) +elseif(APPLE) + set(EXTRA_GTEST_OPTS -DCMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT}) +endif() + +include(ExternalProject) +ExternalProject_Add( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_SHALLOW 1 + GIT_TAG v1.15.2 + UPDATE_COMMAND "" + # # Force separate output paths for debug and release builds to allow easy + # # identification of correct lib in subsequent TARGET_LINK_LIBRARIES commands + # CMAKE_ARGS -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs + # -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs + # -Dgtest_force_shared_crt=ON + CMAKE_ARGS -G ${CMAKE_GENERATOR} + -DCMAKE_BUILD_TYPE=$ + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} + -DCMAKE_AR=${CMAKE_AR} + -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} + ${EXTRA_GTEST_OPTS} + # Disable install step + INSTALL_COMMAND "" + BUILD_BYPRODUCTS ${_gtest_byproducts} + # Wrap download, configure and build steps in a script to log output + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + TIMEOUT 600 + ) + +# Specify include dirs for gtest and gmock +ExternalProject_Get_Property(googletest source_dir) +set(GTEST_INCLUDE_DIR ${source_dir}/googletest/include) +set(GMOCK_INCLUDE_DIR ${source_dir}/googlemock/include) +# Create the directories. Prevents bug https://gitlab.kitware.com/cmake/cmake/issues/15052 +file(MAKE_DIRECTORY ${GTEST_INCLUDE_DIR} ${GMOCK_INCLUDE_DIR}) + +# Libraries +ExternalProject_Get_Property(googletest binary_dir) +if(WIN32) + set(_G_LIBRARY_PATH ${_gtest_byproduct_binary_dir}/lib) +else() + set(_G_LIBRARY_PATH ${binary_dir}/lib/) +endif() + +# Use gmock_main instead of gtest_main because it initializes gtest as well. +# Note: The libraries are listed in reverse order of their dependancies. +foreach(lib gtest gtest_main gmock gmock_main) + add_library(${lib} IMPORTED STATIC GLOBAL) + set_target_properties(${lib} PROPERTIES + IMPORTED_LOCATION "${_G_LIBRARY_PATH}${CMAKE_STATIC_LIBRARY_PREFIX}${lib}${CMAKE_STATIC_LIBRARY_SUFFIX}" + INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIRS}" + ) + add_dependencies(${lib} googletest) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND + ${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER_EQUAL 9) + target_compile_options(${lib} INTERFACE -Wno-deprecated-copy) + endif() +endforeach() +target_include_directories(gtest INTERFACE ${GTEST_INCLUDE_DIR}) +target_include_directories(gmock INTERFACE ${GMOCK_INCLUDE_DIR}) + +set_property(TARGET gtest PROPERTY IMPORTED_LOCATION ${_G_LIBRARY_PATH}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}) +set_property(TARGET gtest_main PROPERTY IMPORTED_LOCATION ${_G_LIBRARY_PATH}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}) +set_property(TARGET gmock PROPERTY IMPORTED_LOCATION ${_G_LIBRARY_PATH}/${CMAKE_STATIC_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}) +set_property(TARGET gmock_main PROPERTY IMPORTED_LOCATION ${_G_LIBRARY_PATH}/${CMAKE_STATIC_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}) diff --git a/interpreter/CppInterOp/docs/CMakeLists.txt b/interpreter/CppInterOp/docs/CMakeLists.txt new file mode 100644 index 0000000000000..25d24d827ca18 --- /dev/null +++ b/interpreter/CppInterOp/docs/CMakeLists.txt @@ -0,0 +1,31 @@ +find_package(Doxygen REQUIRED) + +set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/doxygen.cfg.in) +set(DOXYFILE ${CMAKE_CURRENT_BINARY_DIR}/doxygen.cfg) + +set(docs_srcdir ${CMAKE_CURRENT_SOURCE_DIR}) +set(docs_builddir ${CMAKE_CURRENT_BINARY_DIR}) +set(cppinterop_srcdir ${CMAKE_SOURCE_DIR}) +# file(READ ${CMAKE_SOURCE_DIR}/VERSION PACKAGE_VERSION) + +configure_file(${DOXYFILE_IN} ${DOXYFILE} @ONLY) + +add_custom_target(doxygen-cppinterop + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Generate CppInterOp documentation with Doxygen" + VERBATIM) + + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +include(CreateSphinxTarget) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conf.py + ${CMAKE_CURRENT_BINARY_DIR}/conf.py + @ONLY + ) + +create_sphinx_target( + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} + TARGET_NAME sphinx-cppinterop +) \ No newline at end of file diff --git a/interpreter/CppInterOp/docs/DevelopersDocumentation.rst b/interpreter/CppInterOp/docs/DevelopersDocumentation.rst new file mode 100644 index 0000000000000..cde4282d4b4e0 --- /dev/null +++ b/interpreter/CppInterOp/docs/DevelopersDocumentation.rst @@ -0,0 +1,431 @@ +########################## + Developers Documentation +########################## + +###################### + Building from source +###################### + +Build instructions for CppInterOp and its dependencies are as follows. +CppInterOP can be built with either Cling and Clang-REPL, so instructions will +differ slightly depending on which option you would like to build, but should be +clear from the section title which instructions to follow. + +************************************ + Clone CppInterOp and cppyy-backend +************************************ + +First clone the CppInterOp repository, as this contains patches that need to be +applied to the subsequently cloned llvm-project repo (these patches are only +applied if building CppInterOp with Clang-REPL) + +.. code:: bash + + git clone --depth=1 https://github.com/compiler-research/CppInterOp.git + +and clone cppyy-backend repository where we will be installing the CppInterOp +library + +.. code:: bash + + git clone --depth=1 https://github.com/compiler-research/cppyy-backend.git + +****************** + Setup Clang-REPL +****************** + +Clone the 19.x release of the LLVM project repository. + +.. code:: bash + + git clone --depth=1 --branch release/19.x https://github.com/llvm/llvm-project.git + cd llvm-project + +For Clang 16 & 17, the following patches required for development work. To apply +these patches on Linux and MacOS execute the following command(substitute +`{version}` with your clang version): + +.. code:: bash + + git apply -v ../CppInterOp/patches/llvm/clang{version}-*.patch + +and + +.. code:: powershell + + cp -r ..\CppInterOp\patches\llvm\clang17* . + git apply -v clang{version}-*.patch + +on Windows. + +****************** + Build Clang-REPL +****************** + +Clang-REPL is an interpreter that CppInterOp works alongside. Build Clang (and +Clang-REPL along with it). On Linux and MaxOS you do this by executing the +following command + +.. code:: bash + + mkdir build + cd build + cmake -DLLVM_ENABLE_PROJECTS=clang \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_TERMINFO=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + ../llvm + cmake --build . --target clang clang-repl --parallel $(nproc --all) + +On Windows you would do this by executing the following + +.. code:: powershell + + $env:ncpus = $([Environment]::ProcessorCount) + mkdir build + cd build + cmake -DLLVM_ENABLE_PROJECTS=clang ` + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" ` + -DCMAKE_BUILD_TYPE=Release ` + -DLLVM_ENABLE_ASSERTIONS=ON ` + -DCLANG_ENABLE_STATIC_ANALYZER=OFF ` + -DCLANG_ENABLE_ARCMT=OFF ` + -DCLANG_ENABLE_FORMAT=OFF ` + -DCLANG_ENABLE_BOOTSTRAP=OFF ` + ..\llvm + cmake --build . --target clang clang-repl --parallel $env:ncpus + +Note the 'llvm-project' directory location. On linux and MacOS you execute the +following + +.. code:: bash + + cd ../ + export LLVM_DIR=$PWD + cd ../ + +On Windows you execute the following + +.. code:: powershell + + cd ..\ + $env:LLVM_DIR= $PWD.Path + cd ..\ + +************************************** + Build Cling and related dependencies +************************************** + +Besides the Clang-REPL interpreter, CppInterOp also works alongside the Cling +interpreter. Cling depends on its own customised version of `llvm-project`, +hosted under the `root-project` (see the git path below). Use the following +build instructions to build on Linux and MacOS + +.. code:: bash + + git clone https://github.com/root-project/cling.git + cd ./cling/ + git checkout tags/v1.0 + cd .. + git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git + mkdir llvm-project/build + cd llvm-project/build + cmake -DLLVM_ENABLE_PROJECTS=clang \ + -DLLVM_EXTERNAL_PROJECTS=cling \ + -DLLVM_EXTERNAL_CLING_SOURCE_DIR=../../cling \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_TERMINFO=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + ../llvm + cmake --build . --target clang --parallel $(nproc --all) + cmake --build . --target cling --parallel $(nproc --all) + cmake --build . --target gtest_main --parallel $(nproc --all) + +Use the following build instructions to build on Windows + +.. code:: powershell + + git clone https://github.com/root-project/cling.git + cd .\cling\ + git checkout tags/v1.0 + cd .. + git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git + $env:ncpus = %NUMBER_OF_PROCESSORS% + $env:PWD_DIR= $PWD.Path + $env:CLING_DIR="$env:PWD_DIR\cling" + mkdir llvm-project\build + cd llvm-project\build + cmake -DLLVM_ENABLE_PROJECTS=clang ` + -DLLVM_EXTERNAL_PROJECTS=cling ` + -DLLVM_EXTERNAL_CLING_SOURCE_DIR="$env:CLING_DIR" ` + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" ` + -DCMAKE_BUILD_TYPE=Release ` + -DLLVM_ENABLE_ASSERTIONS=ON ` + -DCLANG_ENABLE_STATIC_ANALYZER=OFF ` + -DCLANG_ENABLE_ARCMT=OFF ` + -DCLANG_ENABLE_FORMAT=OFF ` + -DCLANG_ENABLE_BOOTSTRAP=OFF ` + ../llvm + cmake --build . --target clang --parallel $env:ncpus + cmake --build . --target cling --parallel $env:ncpus + cmake --build . --target gtest_main --parallel $env:ncpus + +Note the 'llvm-project' directory location. On linux and MacOS you execute the +following + +.. code:: bash + + cd ../ + export LLVM_DIR=$PWD + cd ../ + +On Windows you execute the following + +.. code:: powershell + + cd ..\ + $env:LLVM_DIR= $PWD.Path + cd ..\ + +*********************** + Environment variables +*********************** + +Regardless of whether you are building CppInterOP with Cling or Clang-REPL you +will need to define the following environment variables (as they clear for a new +session, it is recommended that you also add these to your .bashrc in linux, +.bash_profile if on MacOS, or profile.ps1 on Windows). On Linux and MacOS you +define as follows + +.. code:: bash + + export CB_PYTHON_DIR="$PWD/cppyy-backend/python" + export CPPINTEROP_DIR="$CB_PYTHON_DIR/cppyy_backend" + export CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH}:${LLVM_DIR}/llvm/include:${LLVM_DIR}/clang/include:${LLVM_DIR}/build/include:${LLVM_DIR}/build/tools/clang/include" + +If on MacOS you will also need the following environment variable defined + +.. code:: bash + + export SDKROOT=`xcrun --show-sdk-path` + +On Windows you define as follows (assumes you have defined $env:PWD_DIR= +$PWD.Path ) + +.. code:: powershell + + $env:CB_PYTHON_DIR="$env:PWD_DIR\cppyy-backend\python" + $env:CPPINTEROP_DIR="$env:CB_PYTHON_DIR\cppyy_backend" + $env:CPLUS_INCLUDE_PATH="$env:CPLUS_INCLUDE_PATH;$env:LLVM_DIR\llvm\include;$env:LLVM_DIR\clang\include;$env:LLVM_DIR\build\include;$env:LLVM_DIR\build\tools\clang\include" + +****************** + Build CppInterOp +****************** + +Now CppInterOp can be installed. On Linux and MacOS execute + +.. code:: bash + + mkdir CppInterOp/build/ + cd CppInterOp/build/ + +On Windows execute + +.. code:: powershell + + mkdir CppInterOp\build\ + cd CppInterOp\build\ + +Now if you want to build CppInterOp with Clang-REPL then execute the following +commands on Linux and MacOS + +.. code:: bash + + cmake -DBUILD_SHARED_LIBS=ON -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. + cmake --build . --target install --parallel $(nproc --all) + +and + +.. code:: powershell + + cmake -DLLVM_DIR=$env:LLVM_DIR\build\lib\cmake\llvm -DClang_DIR=$env:LLVM_DIR\build\lib\cmake\clang -DCMAKE_INSTALL_PREFIX=$env:CPPINTEROP_DIR .. + cmake --build . --target install --parallel $env:ncpus + +on Windows. If alternatively you would like to install CppInterOp with Cling +then execute the following commands on Linux and MacOS + +.. code:: bash + + cmake -DBUILD_SHARED_LIBS=ON -DCPPINTEROP_USE_CLING=ON -DCPPINTEROP_USE_REPL=Off -DCling_DIR=$LLVM_DIR/build/tools/cling -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. + cmake --build . --target install --parallel $(nproc --all) + +and + +.. code:: powershell + + cmake -DCPPINTEROP_USE_CLING=ON -DCPPINTEROP_USE_REPL=Off -DCling_DIR=$env:LLVM_DIR\build\tools\cling -DLLVM_DIR=$env:LLVM_DIR\build\lib\cmake\llvm -DClang_DIR=$env:LLVM_DIR\build\lib\cmake\clang -DCMAKE_INSTALL_PREFIX=$env:CPPINTEROP_DIR .. + cmake --build . --target install --parallel $env:ncpus + +******************** + Testing CppInterOp +******************** + +To test the built CppInterOp execute the following command in the CppInterOP +build folder on Linux and MacOS + +.. code:: bash + + cmake --build . --target check-cppinterop --parallel $(nproc --all) + +and + +.. code:: powershell + + cmake --build . --target check-cppinterop --parallel $env:ncpus + +on Windows. Now go back to the top level directory in which your building +CppInterOP. On Linux and MacOS you do this by executing + +.. code:: bash + + cd ../.. + +and + +.. code:: powershell + + cd ..\.. + +on Windows. Now you are in a position to install cppyy following the +instructions below. + +************************************ + Building and Install cppyy-backend +************************************ + +Cd into the cppyy-backend directory, build it and copy library files into +`python/cppyy-backend` directory: + +.. code:: bash + + cd cppyy-backend + mkdir -p python/cppyy_backend/lib build + cd build + cmake -DCppInterOp_DIR=$CPPINTEROP_DIR .. + cmake --build . + +If on a linux system now execute the following command + +.. code:: bash + + cp libcppyy-backend.so ../python/cppyy_backend/lib/ + +and if on MacOS execute the following command + +.. code:: bash + + cp libcppyy-backend.dylib ../python/cppyy_backend/lib/ + +Note go back to the top level build directory + +.. code:: bash + + cd ../.. + +****************** + Install CPyCppyy +****************** + +Create virtual environment and activate it: + +.. code:: bash + + python3 -m venv .venv + source .venv/bin/activate + git clone --depth=1 https://github.com/compiler-research/CPyCppyy.git + mkdir CPyCppyy/build + cd CPyCppyy/build + cmake .. + cmake --build . + +Note down the path to the `build` directory as `CPYCPPYY_DIR`: + +.. code:: bash + + export CPYCPPYY_DIR=$PWD + cd ../.. + +Export the `libcppyy` path to python: + +.. code:: bash + + export PYTHONPATH=$PYTHONPATH:$CPYCPPYY_DIR:$CB_PYTHON_DIR + +and on Windows: + +.. code:: powershell + + $env:PYTHONPATH="$env:PYTHONPATH;$env:CPYCPPYY_DIR;$env:CB_PYTHON_DIR" + +*************** + Install cppyy +*************** + +.. code:: bash + + git clone --depth=1 https://github.com/compiler-research/cppyy.git + cd cppyy + python -m pip install --upgrade . --no-deps --no-build-isolation + cd .. + +*********** + Run cppyy +*********** + +Each time you want to run cppyy you need to: Activate the virtual environment + +.. code:: bash + + source .venv/bin/activate + +Now you can `import cppyy` in `python` .. code-block:: bash + + python -c "import cppyy" + +***************** + Run cppyy tests +***************** + +**Follow the steps in Run cppyy.** Change to the test directory, make the +library files and run pytest: + +.. code:: bash + + cd cppyy/test + make all + python -m pip install pytest + python -m pytest -sv + +*********************************** + CppInterOp Internal Documentation +*********************************** + +CppInterOp maintains an internal Doxygen documentation of its components. +Internal documentation aims to capture intrinsic details and overall usage of +code components. The goal of internal documentation is to make the codebase +easier to understand for the new developers. Internal documentation can be +visited : `here `_ diff --git a/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst b/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst new file mode 100644 index 0000000000000..aa0254c3f0d3b --- /dev/null +++ b/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst @@ -0,0 +1,182 @@ +######################### + Wasm Build Instructions +######################### + +It should be noted that the wasm build of CppInterOp is still +experimental and subject to change. + +************************************ + 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 for osx and Linux. We will first make folder to +build our wasm build of CppInterOp. This can be done by executing the +following command + +.. code:: bash + + mkdir CppInterOp-wasm + +Now move into this directory using the following command + +.. code:: bash + + cd ./CppInterOp-wasm + +To create a wasm build of CppInterOp we make use of the emsdk toolchain. +This can be installed by executing (we only currently support version +3.1.73) + +.. code:: bash + + git clone https://github.com/emscripten-core/emsdk.git + ./emsdk/emsdk install 3.1.73 + +and activate the emsdk environment + +.. code:: bash + + ./emsdk/emsdk activate 3.1.73 + source ./emsdk/emsdk_env.sh + +Now clone the 19.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 +appropriately) + +.. code:: bash + + git clone --depth=1 --branch release/19.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 + +.. code:: bash + + cd ./llvm-project/ + git apply -v ../CppInterOp/patches/llvm/emscripten-clang19-*.patch + +We are now in a position to build an emscripten build of llvm by +executing the following + +.. code:: bash + + mkdir build + cd build + emcmake cmake -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_HOST_TRIPLE=wasm32-unknown-emscripten \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DLLVM_TARGETS_TO_BUILD="WebAssembly" \ + -DLLVM_ENABLE_LIBEDIT=OFF \ + -DLLVM_ENABLE_PROJECTS="clang;lld" \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + -DCMAKE_CXX_FLAGS="-Dwait4=__syscall_wait4" \ + -DLLVM_INCLUDE_BENCHMARKS=OFF \ + -DLLVM_INCLUDE_EXAMPLES=OFF \ + -DLLVM_INCLUDE_TESTS=OFF \ + -DLLVM_ENABLE_THREADS=OFF \ + ../llvm + emmake make clang -j $(nproc --all) + emmake make clang-repl -j $(nproc --all) + emmake make lld -j $(nproc --all) + +Once this finishes building we need to take note of where we built our +llvm build. This can be done by executing the following + +.. code:: bash + + export LLVM_BUILD_DIR=$PWD + +We can move onto building the wasm version of CppInterOp. We will do +this within a Conda environment. We can achieve this by executing +(assumes you have micromamba installed and that your shell is +initialised for the micromamba install) + +.. code:: bash + + cd ../../CppInterOp/ + micromamba create -f environment-wasm.yml --platform=emscripten-wasm32 + micromamba activate CppInterOp-wasm + +You will also want to set a few environment variables + +.. code:: bash + + export PREFIX=$CONDA_PREFIX + export CMAKE_PREFIX_PATH=$PREFIX + export CMAKE_SYSTEM_PREFIX_PATH=$PREFIX + +Now to build CppInterOp execute the following + +.. code:: bash + + mkdir build + cd ./build/ + emcmake cmake -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_DIR=$LLVM_BUILD_DIR/lib/cmake/llvm \ + -DLLD_DIR=$LLVM_BUILD_DIR/lib/cmake/lld \ + -DClang_DIR=$LLVM_BUILD_DIR/lib/cmake/clang \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + ../ + emmake make -j $(nproc --all) install + +Once this finishes building we need to take note of where we built +CppInterOp. This can be done by executing the following + +.. code:: bash + + export CPPINTEROP_BUILD_DIR=$PWD + +## Xeus-cpp-lite Wasm Build Instructions + +A project which makes use of the wasm build of CppInterOp is xeus-cpp. +xeus-cpp is a C++ Jupyter kernel. Assuming you are in the CppInterOp +build folder, you can build the wasm version of xeus-cpp by executing + +.. code:: bash + + cd ../.. + export SYSROOT_PATH=$PWD/emsdk/upstream/emscripten/cache/sysroot + git clone --depth=1 https://github.com/compiler-research/xeus-cpp.git + cd ./xeus-cpp + mkdir build + cd build + emcmake cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=$PREFIX \ + -DCMAKE_INSTALL_PREFIX=$PREFIX \ + -DXEUS_CPP_EMSCRIPTEN_WASM_BUILD=ON \ + -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ + -DCppInterOp_DIR="$CPPINTEROP_BUILD_DIR/lib/cmake/CppInterOp" \ + -DSYSROOT_PATH=$SYSROOT_PATH \ + .. + emmake make -j $(nproc --all) install + +To build Jupyter Lite website with this kernel locally that you can use +for testing execute the following + +.. code:: bash + + cd ../.. + micromamba create -n xeus-lite-host jupyterlite-core -c conda-forge + micromamba activate xeus-lite-host + python -m pip install jupyterlite-xeus jupyter_server + jupyter lite build --XeusAddon.prefix=$PREFIX --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb + +Once the Jupyter Lite site has built you can test the website locally by +executing + +.. code:: bash + + jupyter lite serve --XeusAddon.prefix=$PREFIX diff --git a/interpreter/CppInterOp/docs/FAQ.rst b/interpreter/CppInterOp/docs/FAQ.rst new file mode 100644 index 0000000000000..8a1bd4528c3b4 --- /dev/null +++ b/interpreter/CppInterOp/docs/FAQ.rst @@ -0,0 +1,24 @@ +FAQ +---- + +1. **What is CppInterOp?**: + +- CppInterOp is a Clang-based C++ Interoperability library. + +2. **Why should you use CppInterOp?**: + +- It can help you to integrate C++ code with other languages, which can make your +code more portable and reusable. + +- It can help you to prototype environments for C++, which can make it easier to +experiment with new ideas and developments. + +- It can help you to develop scientific applications that use C++ and other +languages, which can make your applications more powerful and flexible. + +3. **How can we integrate C++ code with other languages using CppInterOp?**: +- You can refer the guides/tutorials here for:- + +- Installation And Usage Guide-:doc:`Installation and usage ` + +- Tutorials-:doc:`Tutorials ` \ No newline at end of file diff --git a/interpreter/CppInterOp/docs/InstallationAndUsage.rst b/interpreter/CppInterOp/docs/InstallationAndUsage.rst new file mode 100644 index 0000000000000..cb6be3d0a2298 --- /dev/null +++ b/interpreter/CppInterOp/docs/InstallationAndUsage.rst @@ -0,0 +1,421 @@ +######################## + Installation And Usage +######################## + +******************* + Build from source +******************* + +Build instructions for CppInterOp and its dependencies are as follows. +CppInterOP can be built with either Cling and Clang-REPL, so instructions will +differ slightly depending on which option you would like to build, but should be +clear from the section title which instructions to follow. + +************************************ + Clone CppInterOp and cppyy-backend +************************************ + +First clone the CppInterOp repository, as this contains patches that need to be +applied to the subsequently cloned llvm-project repo (these patches are only +applied if building CppInterOp with Clang-REPL) + +.. code:: bash + + git clone --depth=1 https://github.com/compiler-research/CppInterOp.git + +and clone cppyy-backend repository where we will be installing the CppInterOp +library + +.. code:: bash + + git clone --depth=1 https://github.com/compiler-research/cppyy-backend.git + +****************** + Setup Clang-REPL +****************** + +Clone the 19.x release of the LLVM project repository. + +.. code:: bash + + git clone --depth=1 --branch release/19.x https://github.com/llvm/llvm-project.git + cd llvm-project + +For Clang 16 & 17, the following patches required for development work. To apply +these patches on Linux and MacOS execute the following command(substitute +`{version}` with your clang version): + +.. code:: bash + + git apply -v ../CppInterOp/patches/llvm/clang{version}-*.patch + +and + +.. code:: powershell + + cp -r ..\CppInterOp\patches\llvm\clang17* . + git apply -v clang{version}-*.patch + +on Windows. + +****************** + Build Clang-REPL +****************** + +Clang-REPL is an interpreter that CppInterOp works alongside. Build Clang (and +Clang-REPL along with it). On Linux and MaxOS you do this by executing the +following command + +.. code:: bash + + mkdir build + cd build + cmake -DLLVM_ENABLE_PROJECTS=clang \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_TERMINFO=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + ../llvm + cmake --build . --target clang clang-repl --parallel $(nproc --all) + +On Windows you would do this by executing the following + +.. code:: powershell + + $env:ncpus = $([Environment]::ProcessorCount) + mkdir build + cd build + cmake -DLLVM_ENABLE_PROJECTS=clang ` + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" ` + -DCMAKE_BUILD_TYPE=Release ` + -DLLVM_ENABLE_ASSERTIONS=ON ` + -DCLANG_ENABLE_STATIC_ANALYZER=OFF ` + -DCLANG_ENABLE_ARCMT=OFF ` + -DCLANG_ENABLE_FORMAT=OFF ` + -DCLANG_ENABLE_BOOTSTRAP=OFF ` + ..\llvm + cmake --build . --target clang clang-repl --parallel $env:ncpus + +Note the 'llvm-project' directory location. On linux and MacOS you execute the +following + +.. code:: bash + + cd ../ + export LLVM_DIR=$PWD + cd ../ + +On Windows you execute the following + +.. code:: powershell + + cd ..\ + $env:LLVM_DIR= $PWD.Path + cd ..\ + +************************************** + Build Cling and related dependencies +************************************** + +Besides the Clang-REPL interpreter, CppInterOp also works alongside the Cling +interpreter. Cling depends on its own customised version of `llvm-project`, +hosted under the `root-project` (see the git path below). Use the following +build instructions to build on Linux and MacOS + +.. code:: bash + + git clone https://github.com/root-project/cling.git + cd ./cling/ + git checkout tags/v1.0 + cd .. + git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git + mkdir llvm-project/build + cd llvm-project/build + cmake -DLLVM_ENABLE_PROJECTS=clang \ + -DLLVM_EXTERNAL_PROJECTS=cling \ + -DLLVM_EXTERNAL_CLING_SOURCE_DIR=../../cling \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + -DLLVM_ENABLE_ZSTD=OFF \ + -DLLVM_ENABLE_TERMINFO=OFF \ + -DLLVM_ENABLE_LIBXML2=OFF \ + ../llvm + cmake --build . --target clang --parallel $(nproc --all) + cmake --build . --target cling --parallel $(nproc --all) + cmake --build . --target gtest_main --parallel $(nproc --all) + +Use the following build instructions to build on Windows + +.. code:: powershell + + git clone https://github.com/root-project/cling.git + cd .\cling\ + git checkout tags/v1.0 + cd .. + git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git + $env:ncpus = %NUMBER_OF_PROCESSORS% + $env:PWD_DIR= $PWD.Path + $env:CLING_DIR="$env:PWD_DIR\cling" + mkdir llvm-project\build + cd llvm-project\build + cmake -DLLVM_ENABLE_PROJECTS=clang ` + -DLLVM_EXTERNAL_PROJECTS=cling ` + -DLLVM_EXTERNAL_CLING_SOURCE_DIR="$env:CLING_DIR" ` + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" ` + -DCMAKE_BUILD_TYPE=Release ` + -DLLVM_ENABLE_ASSERTIONS=ON ` + -DCLANG_ENABLE_STATIC_ANALYZER=OFF ` + -DCLANG_ENABLE_ARCMT=OFF ` + -DCLANG_ENABLE_FORMAT=OFF ` + -DCLANG_ENABLE_BOOTSTRAP=OFF ` + ../llvm + cmake --build . --target clang --parallel $env:ncpus + cmake --build . --target cling --parallel $env:ncpus + cmake --build . --target gtest_main --parallel $env:ncpus + +Note the 'llvm-project' directory location. On linux and MacOS you execute the +following + +.. code:: bash + + cd ../ + export LLVM_DIR=$PWD + cd ../ + +On Windows you execute the following + +.. code:: powershell + + cd ..\ + $env:LLVM_DIR= $PWD.Path + cd ..\ + +*********************** + Environment variables +*********************** + +Regardless of whether you are building CppInterOP with Cling or Clang-REPL you +will need to define the following environment variables (as they clear for a new +session, it is recommended that you also add these to your .bashrc in linux, +.bash_profile if on MacOS, or profile.ps1 on Windows). On Linux and MacOS you +define as follows + +.. code:: bash + + export CB_PYTHON_DIR="$PWD/cppyy-backend/python" + export CPPINTEROP_DIR="$CB_PYTHON_DIR/cppyy_backend" + export CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH}:${LLVM_DIR}/llvm/include:${LLVM_DIR}/clang/include:${LLVM_DIR}/build/include:${LLVM_DIR}/build/tools/clang/include" + +If on MacOS you will also need the following environment variable defined + +.. code:: bash + + export SDKROOT=`xcrun --show-sdk-path` + +On Windows you define as follows (assumes you have defined $env:PWD_DIR= +$PWD.Path ) + +.. code:: powershell + + $env:CB_PYTHON_DIR="$env:PWD_DIR\cppyy-backend\python" + $env:CPPINTEROP_DIR="$env:CB_PYTHON_DIR\cppyy_backend" + $env:CPLUS_INCLUDE_PATH="$env:CPLUS_INCLUDE_PATH;$env:LLVM_DIR\llvm\include;$env:LLVM_DIR\clang\include;$env:LLVM_DIR\build\include;$env:LLVM_DIR\build\tools\clang\include" + +****************** + Build CppInterOp +****************** + +Now CppInterOp can be installed. On Linux and MacOS execute + +.. code:: bash + + mkdir CppInterOp/build/ + cd CppInterOp/build/ + +On Windows execute + +.. code:: powershell + + mkdir CppInterOp\build\ + cd CppInterOp\build\ + +Now if you want to build CppInterOp with Clang-REPL then execute the following +commands on Linux and MacOS + +.. code:: bash + + cmake -DBUILD_SHARED_LIBS=ON -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. + cmake --build . --target install --parallel $(nproc --all) + +and + +.. code:: powershell + + cmake -DLLVM_DIR=$env:LLVM_DIR\build\lib\cmake\llvm -DClang_DIR=$env:LLVM_DIR\build\lib\cmake\clang -DCMAKE_INSTALL_PREFIX=$env:CPPINTEROP_DIR .. + cmake --build . --target install --parallel $env:ncpus + +on Windows. If alternatively you would like to install CppInterOp with Cling +then execute the following commands on Linux and MacOS + +.. code:: bash + + cmake -DBUILD_SHARED_LIBS=ON -DCPPINTEROP_USE_CLING=ON -DCPPINTEROP_USE_REPL=Off -DCling_DIR=$LLVM_DIR/build/tools/cling -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. + cmake --build . --target install --parallel $(nproc --all) + +and + +.. code:: powershell + + cmake -DCPPINTEROP_USE_CLING=ON -DCPPINTEROP_USE_REPL=Off -DCling_DIR=$env:LLVM_DIR\build\tools\cling -DLLVM_DIR=$env:LLVM_DIR\build\lib\cmake\llvm -DClang_DIR=$env:LLVM_DIR\build\lib\cmake\clang -DCMAKE_INSTALL_PREFIX=$env:CPPINTEROP_DIR .. + cmake --build . --target install --parallel $env:ncpus + +******************** + Testing CppInterOp +******************** + +To test the built CppInterOp execute the following command in the CppInterOP +build folder on Linux and MacOS + +.. code:: bash + + cmake --build . --target check-cppinterop --parallel $(nproc --all) + +and + +.. code:: powershell + + cmake --build . --target check-cppinterop --parallel $env:ncpus + +on Windows. Now go back to the top level directory in which your building +CppInterOP. On Linux and MacOS you do this by executing + +.. code:: bash + + cd ../.. + +and + +.. code:: powershell + + cd ..\.. + +on Windows. Now you are in a position to install cppyy following the +instructions below. + +************************************ + Building and Install cppyy-backend +************************************ + +Cd into the cppyy-backend directory, build it and copy library files into +`python/cppyy-backend` directory: + +.. code:: bash + + cd cppyy-backend + mkdir -p python/cppyy_backend/lib build + cd build + cmake -DCppInterOp_DIR=$CPPINTEROP_DIR .. + cmake --build . + +If on a linux system now execute the following command + +.. code:: bash + + cp libcppyy-backend.so ../python/cppyy_backend/lib/ + +and if on MacOS execute the following command + +.. code:: bash + + cp libcppyy-backend.dylib ../python/cppyy_backend/lib/ + +Note go back to the top level build directory + +.. code:: bash + + cd ../.. + +****************** + Install CPyCppyy +****************** + +Create virtual environment and activate it: + +.. code:: bash + + python3 -m venv .venv + source .venv/bin/activate + git clone --depth=1 https://github.com/compiler-research/CPyCppyy.git + mkdir CPyCppyy/build + cd CPyCppyy/build + cmake .. + cmake --build . + +Note down the path to the `build` directory as `CPYCPPYY_DIR`: + +.. code:: bash + + export CPYCPPYY_DIR=$PWD + cd ../.. + +Export the `libcppyy` path to python: + +.. code:: bash + + export PYTHONPATH=$PYTHONPATH:$CPYCPPYY_DIR:$CB_PYTHON_DIR + +and on Windows: + +.. code:: powershell + + $env:PYTHONPATH="$env:PYTHONPATH;$env:CPYCPPYY_DIR;$env:CB_PYTHON_DIR" + +*************** + Install cppyy +*************** + +.. code:: bash + + git clone --depth=1 https://github.com/compiler-research/cppyy.git + cd cppyy + python -m pip install --upgrade . --no-deps --no-build-isolation + cd .. + +*********** + Run cppyy +*********** + +Each time you want to run cppyy you need to: Activate the virtual environment + +.. code:: bash + + source .venv/bin/activate + +Now you can `import cppyy` in `python` .. code-block:: bash + + python -c "import cppyy" + +***************** + Run cppyy tests +***************** + +**Follow the steps in Run cppyy.** Change to the test directory, make the +library files and run pytest: + +.. code:: bash + + cd cppyy/test + make all + python -m pip install pytest + python -m pytest -sv diff --git a/interpreter/CppInterOp/docs/ReleaseNotes.md b/interpreter/CppInterOp/docs/ReleaseNotes.md new file mode 100644 index 0000000000000..8b53f0b3c1967 --- /dev/null +++ b/interpreter/CppInterOp/docs/ReleaseNotes.md @@ -0,0 +1,66 @@ +# Introduction + +This document contains the release notes for the language interoperability +library CppInterOp, release 1.6.0. CppInterOp is built on top of +[Clang](http://clang.llvm.org) and [LLVM](http://llvm.org%3E) compiler +infrastructure. Here we describe the status of CppInterOp in some detail, +including major improvements from the previous release and new feature work. +Note that if you are reading this file from a git checkout, this document +applies to the *next* release, not the current one. + +CppInterOp exposes API from Clang and LLVM in a backward compatibe way. The API +support downstream tools that utilize interactive C++ by using the compiler as +a service. That is, embed Clang and LLVM as a libraries in their codebases. The +API are designed to be minimalistic and aid non-trivial tasks such as language +interoperability on the fly. In such scenarios CppInterOp can be used to provide +the necessary introspection information to the other side helping the language +cross talk. + +## What's New in CppInterOp 1.6.0? + +Some of the major new features and improvements to CppInterOp are listed here. +Generic improvements to CppInterOp as a whole or to its underlying +infrastructure are described first. + +## External Dependencies + +- CppInterOp now works with: + - llvm19 + +## Introspection + +- + +## Just-in-Time Compilation + +- + +## Incremental C++ + +- + +## Misc + +- + +## Fixed Bugs + +[XXX](https://github.com/compiler-research/CppInterOp/issues/XXX) + + + +## Special Kudos + +This release wouldn't have happened without the efforts of our contributors, +listed in the form of Firstname Lastname (#contributions): + +FirstName LastName (#commits) + +A B (N) + + diff --git a/interpreter/CppInterOp/docs/UsingCppInterOp.rst b/interpreter/CppInterOp/docs/UsingCppInterOp.rst new file mode 100644 index 0000000000000..90722326d6000 --- /dev/null +++ b/interpreter/CppInterOp/docs/UsingCppInterOp.rst @@ -0,0 +1,307 @@ +Using CppInterop +---------------- + +C++ Language Interoperability Layer +=================================== + +Loading Dynamic shared library +============================== + +The CppInterop comes with using it is a dynamic shared library, which resides +in the build/lib/ after building CppInterOp following the instructions in +:doc:`Installation and usage ` . + +.. code-block:: bash + + libInterop = ctypes.CDLL("./libclangCppInterOp.so") + +The above method of usage is for Python on Linux; for C, we can include the headers of +the library. Including this library in our program enables the user to use +the abilities of CppInterOp. CppInterOp helps programmers with multiple +verifications such as isClass, isBoolean, isStruct, and many more in different +languages. With the interop layer, we can access the scopes, namespaces of +classes and members that are being used. The interoperability layer helps us +with the instantiation of templates, diagnostic interaction, creation of +objects, and many more things. + +Using LLVM as external library +============================== + +In CppInterOp, we are leveraging Clang as a library for interoperability purposes. +To use Clang, we need to pass the Clang configuration to the CMake build system, +so that the build system recognizes the configuration and enables usage of Clang +and LLVM. +We can consider clang-repl as a state manager, where CppInterOp allows you to +query the state from the state manager. Thereafter, cppyy uses this to create +Python objects for C++. +This section briefly describes all the key **features** offered by +CppInterop. If you are just getting started with CppInterop, then this is the +best place to start. + +Incremental Adoption +==================== +CppInterOp can be adopted incrementally. While the rest of the framework is the +same, a small part of CppInterOp can be utilized. More components may be +adopted over time. + +Minimalist by design +==================== +While the library includes some tricky code, it is designed to be simple and +robust (simple function calls, no inheritance, etc.). The goal is to make it as +close to the compiler API as possible, and each routine should do just one thing. +that it was designed for. + +How cppyy leverages CppInterOp +=============================== + +cppyy is a run-time Python-C++ bindings generator for calling C++ from Python +and Python from C++. Interestingly, it uses C++ interactively by using the +compiler as a service. This is made possible by the CppInterOp library. +Following are some of the ways cppyy leverages CppInterOp for better +performance and usability. + +1. **CppInterOp enables interoperability with C++ code**: CppInterOp provides a + minimalist and robust interface for language interoperability on the fly, + which helps CPPYY generate dynamic Python-C++ bindings by using a C++ + interpreter (e.g., Clang-REPL/Cling) and LLVM. + +2. **Reducing dependencies**: Reducing domain-specific dependencies of cppyy + (e.g., on the Cling interpreter and the ROOT framework) to enable more + generalized usage. + +3. **LLVM Integration**: CppInterOp is designed to be used as a part of the + LLVM toolchain (as part of Clang-REPL) that can then be used as a runtime + compiler for CPPYY. This simplifies the codebase of CPPYY and enhances its + performance. + + 4. **Making C++ More Social**: CppInterOp and cppyy help data scientists that + are working with legacy C++ code experiment with simpler, more interactive + languages, while also interacting with larger communities. + +**CppInterOp enables interoperability with C++ code** + +cppyy is a major use case for CppInterOp. cppyy is an automatic run-time +bindings generator for Python and C++, and supports a wide range of C++ +features, including template instantiation. It operates on demand and generates +only what is necessary. It requires a compiler (Cling or Clang-REPL) that can +be available during program runtime. + +**Reducing Dependencies** + +Recent work done on cppyy has been focused on reducing dependencies on +domain-specific infrastructure (e.g., the ROOT framework). Using an independent +library such as CppInterOp helps accomplish that, while also improving the code +consistency in cppyy. + +The CppInterOp library can be configured to use the newly developed Clang-Repl +backend available in LLVM upstream (or to use the Cling legacy backend, for +compatibility with High Energy Physics applications). + +Only a small set of APIs are needed to connect to the interpreter (Clang-Repl/ +Cling), since other APIs are already available in the standard compiler. This +is one of the reasons that led to the creation of CppInterOp (a library of +helper functions), that can help extract out things that are unnecessary for +for core cppyy functionality. + +The cppyy API surface is now incomparably smaller and simpler than what it used +to be. + +**LLVM Integration** + +Once CppInterOp is integrated with LLVM's Clang-REPL component (that can then +be used as a runtime compiler for cppyy), it will further enhance cppyy's +performance in the following ways: + + +- *Simpler codebase:* The removal of string parsing logic will lead to a + simpler code base. + +- *Built into the LLVM toolchain:* The CppInterOp depends only on the LLVM + toolchain (as part of Clang-REPL). + +- *Better C++ Support:* Finer-grained control over template instantiation is + available through CppInterOp. + +- *Fewer Lines of Code:* A lot of dependencies and workarounds will be + removed, reducing the lines of code required to execute cppyy. + +- *Well tested interoperability Layer:* The CppInterOp interfaces have full + unit test coverage. + +**Making C++ More Social** + +cppyy is the first use case demonstrating how CppInterOp can enable C++ to be +more easily interoperable with other languages. This helps many data scientists +that are working with legacy C++ code and would like to use simpler, more +interactive languages. + +The goal of these enhancements is to eventually land these interoperability +tools (including CppInterOp) to broader communities like LLVM and Clang, to +enable C++ to interact with other languages besides Python. + +Example: Template Instantiation +------------------------------- + +The developmental cppyy version can run basic examples such as the one +here. Features such as standalone functions and basic classes are also +supported. + +C++ code (Tmpl.h) + +:: + + template + struct Tmpl { + T m_num; + T add (T n) { + return m_num + n; + } + }; + +Python Interpreter + +:: + + >>> import cppyy + >>> cppyy.include("Tmpl.h") + >>> tmpl = Tmpl[int]() + >>> tmpl.m_num = 4 + >>> print(tmpl.add(5)) + 9 + >>> tmpl = Tmpl[float]() + >>> tmpl.m_num = 3.0 + >>> print(tmpl.add(4.0)) + 7.0 + +Where does the cppyy code reside? +--------------------------------- + +Following are the main components where cppyy logic (with Compiler Research +Organization’s customizations started by `sudo-panda`_) resides: + +- `cppyy `_ +- `cppyy-backend `_ +- `CPyCppyy `_ + +.. + + Note: These are forks of the `upstream cppyy`_ repos created by `wlav`_. + +CppInterOp is a separate library that helps these packages communicate with C++ +code. + +- `CppInterOp `_ + +How cppyy components interact with each other +--------------------------------------------- + +cppyy is made up of the following packages: + +- A frontend: cppyy, + +- A backend: cppyy-backend, and + +- An extension: CPyCppyy. + +Besides these, the ``CppInterOp`` library serves as an additional layer on top +of Cling/Clang-REPL that helps these packages in communicating with C++ code. + +**1. cppyy-backend** + +The `cppyy-backend`_ package forms a layer over ``cppyy``, for example, +modifying some functionality to provide the functions required for +``CPyCppyy``. + + `CPyCppyy`_ is a CPython extension module built on top of the same backend + API as PyPy/_cppyy. It thus requires the installation of the cppyy-backend + for use, which will pull in Cling. + +``cppyy-backend`` also adds some `utilities`_ to help with repackaging and +redistribution. + +For example, ``cppyy-backend`` initializes the interpreter (using the +``clingwrapper::ApplicationStarter`` function), adds the required ``include`` +paths, and adds the headers required for cppyy to work. It also adds some +checks and combines two or more functions to help CPyCppyy work. + +These changes help ensure that any change in ``cppyy`` doesn’t directly +affect ``CPyCppyy``, and the API for ``CPyCppyy`` remains unchanged. + +**2. CPyCppyy** + +The ``CPyCppyy`` package uses the functionality provided by ``cppyy-backend`` +and provides Python objects for C++ entities. ``CPyCppyy`` uses separate proxy +classes for each type of object. It also includes helper classes, for example, +``Converters.cxx`` helps convert Python type objects to C++ type objects, while +``Executors.cxx`` is used to execute a function and convert its return value to +a Python object, so that it can be used inside Python. + +**3. cppyy** + +The cppyy package provides the front-end for Python. It is `included in code`_ +(using ``import cppyy``) to import cppyy in Python. It initializes things on +the backend side, provides helper functions (e.g., ``cppdef()``, ``cppexec()``, +etc.) that the user can utilize, and it calls the relevant backend functions +required to initialize cppyy. + + +Further Reading +--------------- + +- `High-performance Python-C++ bindings with PyPy and + Cling `_ + +- `Efficient and Accurate Automatic Python Bindings with cppyy & + Cling `_ + +- cppyy documentation: + `cppyy.readthedocs.io `_. + +- Notebook-based tutorial: `Cppyy + Tutorial `_. + +- `C++ Language Interoperability + Layer `_ + +**Credits:** + +- `Wim Lavrijsen `_ (Lawrence Berkeley National Lab.) + for his original work in cppyy and mentorship towards student contributors. + +- `Vassil Vasilev `_ (Princeton University) + for his mentorship towards Compiler Research Org's student contributors. + +- `Baidyanath Kundu `_ (Princeton University) + for his research work on cppyy and Numba with `Compiler Research Organization`_ + (as discussed in this document). + +- `Aaron Jomy `_ (Princeton University) for + continuing this research work with `Compiler Research Organization`_. + +In case you haven't already installed CppInterop, please do so before proceeding +with the Installation And Usage Guide. +:doc:`Installation and usage ` + +.. _Compiler Research Organization: https://compiler-research.org/ + +.. _upstream cppyy: https://github.com/wlav/cppyy + +.. _wlav: https://github.com/wlav + +.. _utilities: https://cppyy.readthedocs.io/en/latest/utilities.html + +.. _included in code: https://cppyy.readthedocs.io/en/latest/starting.html + +.. _sudo-panda: https://github.com/sudo-panda + +.. _cppyy: https://cppyy.readthedocs.io/en/latest/index.html + +.. _CppInterOp: https://github.com/compiler-research/CppInterOp + +.. _ROOT meta: https://github.com/root-project/root/tree/master/core/meta + +.. _enhancements in cppyy: https://arxiv.org/abs/2304.02712 + +.. _CPyCppyy: https://github.com/wlav/CPyCppyy + +.. _cppyy-backend: https://github.com/wlav/cppyy-backend diff --git a/interpreter/CppInterOp/docs/conf.py b/interpreter/CppInterOp/docs/conf.py new file mode 100644 index 0000000000000..9535d9bb53c49 --- /dev/null +++ b/interpreter/CppInterOp/docs/conf.py @@ -0,0 +1,57 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'CppInterOp' +copyright = '2023, Vassil Vassilev' +author = 'Vassil Vassilev' +release = 'Dev' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [] + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'alabaster' + +html_theme_options = { + "github_user": "compiler-research", + "github_repo": "CppInterOp", + "github_banner": True, + "fixed_sidebar": True, +} + +highlight_language = "C++" + +todo_include_todos = True + +mathjax_path = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" + +# Add latex physics package +mathjax3_config = { + "loader": {"load": ["[tex]/physics"]}, + "tex": {"packages": {"[+]": ["physics"]}}, +} + +import os +CPPINTEROP_ROOT = os.path.abspath('..') +html_extra_path = [CPPINTEROP_ROOT + '/build/docs/'] + +import subprocess +command = 'mkdir {0}/build; cd {0}/build; cmake ../ -DClang_DIR=/usr/lib/llvm-13/build/lib/cmake/clang\ + -DLLVM_DIR=/usr/lib/llvm-13/build/lib/cmake/llvm -DCPPINTEROP_ENABLE_DOXYGEN=ON\ + -DCPPINTEROP_INCLUDE_DOCS=ON'.format(CPPINTEROP_ROOT) +subprocess.call(command, shell=True) +subprocess.call('doxygen {0}/build/docs/doxygen.cfg'.format(CPPINTEROP_ROOT), shell=True) diff --git a/interpreter/CppInterOp/docs/doxygen-mainpage.dox b/interpreter/CppInterOp/docs/doxygen-mainpage.dox new file mode 100644 index 0000000000000..cba33b47d18c6 --- /dev/null +++ b/interpreter/CppInterOp/docs/doxygen-mainpage.dox @@ -0,0 +1,18 @@ +/// @mainpage CppInterOp +/// +/// @section main_intro Introduction +/// A Clang-based C++ Interoperability library, which allow C++ code to be accessed and used from other programming languages. +/// This involves creating a bridge or wrapper that exposes C++ functionality through a language-specific API. +/// The document explains the detailings of the API of the language interoperability layer. This library allows different languages +/// to interoperate with C++ code, instantiate a template and execute it. +/// +/// This documentation describes the @b internal software that makes +/// up CppInterOp, not the @b external use of CppInterOp. There are no complete instructions +/// here on how to use CppInterOp, only the APIs that make up the software. For +/// usage instructions, please see the programmer's guide or reference +/// manual. +/// +/// @section main_caveat Caveat +/// This documentation is generated directly from the source code with doxygen. +/// Since CppInterOp is constantly under active development, what you're about to +/// read is out of date! diff --git a/interpreter/CppInterOp/docs/doxygen.cfg.in b/interpreter/CppInterOp/docs/doxygen.cfg.in new file mode 100644 index 0000000000000..247d8beb1159d --- /dev/null +++ b/interpreter/CppInterOp/docs/doxygen.cfg.in @@ -0,0 +1,1949 @@ +# Doxyfile 1.8.13 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = CppInterOp + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @PACKAGE_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "C++ Language Interoperability Layer" + +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = @docs_builddir@/build + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = @cppinterop_srcdir@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = @cppinterop_srcdir@ + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set +# FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 3 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = NO + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = NO + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @cppinterop_srcdir@/include \ + @cppinterop_srcdir@/lib \ + @docs_srcdir@/doxygen-mainpage.dox + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = YES + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 4 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = Cpp:: + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = @docs_srcdir@/doxygen.footer + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvances is that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer ( doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: http://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# http://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = @CMAKE_SOURCE_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 +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = YES + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = NO + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font n the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/interpreter/CppInterOp/docs/doxygen.css b/interpreter/CppInterOp/docs/doxygen.css new file mode 100644 index 0000000000000..80c6cad558c0c --- /dev/null +++ b/interpreter/CppInterOp/docs/doxygen.css @@ -0,0 +1,408 @@ +BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV { + font-family: Verdana,Geneva,Arial,Helvetica,sans-serif; +} +BODY,TD { + font-size: 90%; +} +H1 { + text-align: center; + font-size: 140%; + font-weight: bold; +} +H2 { + font-size: 120%; + font-style: italic; +} +H3 { + font-size: 100%; +} +CAPTION { font-weight: bold } +DIV.qindex { + width: 100%; + background-color: #eeeeff; + border: 1px solid #b0b0b0; + text-align: center; + margin: 2px; + padding: 2px; + line-height: 140%; +} +DIV.nav { + width: 100%; + background-color: #eeeeff; + border: 1px solid #b0b0b0; + text-align: center; + margin: 2px; + padding: 2px; + line-height: 140%; +} +DIV.navtab { + background-color: #eeeeff; + border: 1px solid #b0b0b0; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} +TD.navtab { + font-size: 70%; +} +A.qindex { + text-decoration: none; + font-weight: bold; + color: #1A419D; +} +A.qindex:visited { + text-decoration: none; + font-weight: bold; + color: #1A419D +} +A.qindex:hover { + text-decoration: none; + background-color: #ddddff; +} +A.qindexHL { + text-decoration: none; + font-weight: bold; + background-color: #6666cc; + color: #ffffff; + border: 1px double #9295C2; +} +A.qindexHL:hover { + text-decoration: none; + background-color: #6666cc; + color: #ffffff; +} +A.qindexHL:visited { + text-decoration: none; background-color: #6666cc; color: #ffffff } +A.el { text-decoration: none; font-weight: bold } +A.elRef { font-weight: bold } +A.code:link { text-decoration: none; font-weight: normal; color: #0000FF} +A.code:visited { text-decoration: none; font-weight: normal; color: #0000FF} +A.codeRef:link { font-weight: normal; color: #0000FF} +A.codeRef:visited { font-weight: normal; color: #0000FF} +A:hover { text-decoration: none; background-color: #f2f2ff } +DL.el { margin-left: -1cm } +.fragment { + font-family: Fixed, monospace; + font-size: 95%; +} +PRE.fragment { + border: 1px solid #CCCCCC; + background-color: #f5f5f5; + margin-top: 4px; + margin-bottom: 4px; + margin-left: 2px; + margin-right: 8px; + padding-left: 6px; + padding-right: 6px; + padding-top: 4px; + padding-bottom: 4px; +} +DIV.ah { background-color: black; font-weight: bold; color: #ffffff; margin-bottom: 3px; margin-top: 3px } +TD.md { background-color: #F4F4FB; font-weight: bold; } +TD.mdPrefix { + background-color: #F4F4FB; + color: #606060; + font-size: 80%; +} +TD.mdname1 { background-color: #F4F4FB; font-weight: bold; color: #602020; } +TD.mdname { background-color: #F4F4FB; font-weight: bold; color: #602020; width: 600px; } +DIV.groupHeader { + margin-left: 16px; + margin-top: 12px; + margin-bottom: 6px; + font-weight: bold; +} +DIV.groupText { margin-left: 16px; font-style: italic; font-size: 90% } +BODY { + background: white; + color: black; + margin-right: 20px; + margin-left: 20px; +} +TD.indexkey { + background-color: #eeeeff; + font-weight: bold; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +TD.indexvalue { + background-color: #eeeeff; + font-style: italic; + padding-right : 10px; + padding-top : 2px; + padding-left : 10px; + padding-bottom : 2px; + margin-left : 0px; + margin-right : 0px; + margin-top : 2px; + margin-bottom : 2px; + border: 1px solid #CCCCCC; +} +TR.memlist { + background-color: #f0f0f0; +} +P.formulaDsp { text-align: center; } +IMG.formulaDsp { } +IMG.formulaInl { vertical-align: middle; } +SPAN.keyword { color: #008000 } +SPAN.keywordtype { color: #604020 } +SPAN.keywordflow { color: #e08000 } +SPAN.comment { color: #800000 } +SPAN.preprocessor { color: #806020 } +SPAN.stringliteral { color: #002080 } +SPAN.charliteral { color: #008080 } +.mdTable { + border: 1px solid #868686; + background-color: #F4F4FB; +} +.mdRow { + padding: 8px 10px; +} +.mdescLeft { + padding: 0px 8px 4px 8px; + font-size: 80%; + font-style: italic; + background-color: #FAFAFA; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; +} +.mdescRight { + padding: 0px 8px 4px 8px; + font-size: 80%; + font-style: italic; + background-color: #FAFAFA; + border-top: 1px none #E0E0E0; + border-right: 1px none #E0E0E0; + border-bottom: 1px none #E0E0E0; + border-left: 1px none #E0E0E0; + margin: 0px; +} +.memItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memItemRight { + padding: 1px 8px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memTemplItemLeft { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: none; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memTemplItemRight { + padding: 1px 8px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: none; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + background-color: #FAFAFA; + font-size: 80%; +} +.memTemplParams { + padding: 1px 0px 0px 8px; + margin: 4px; + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-color: #E0E0E0; + border-right-color: #E0E0E0; + border-bottom-color: #E0E0E0; + border-left-color: #E0E0E0; + border-top-style: solid; + border-right-style: none; + border-bottom-style: none; + border-left-style: none; + color: #606060; + background-color: #FAFAFA; + font-size: 80%; +} +.search { color: #003399; + font-weight: bold; +} +FORM.search { + margin-bottom: 0px; + margin-top: 0px; +} +INPUT.search { font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #eeeeff; +} +TD.tiny { font-size: 75%; +} +a { + color: #252E78; +} +a:visited { + color: #3D2185; +} +.dirtab { padding: 4px; + border-collapse: collapse; + border: 1px solid #b0b0b0; +} +TH.dirtab { background: #eeeeff; + font-weight: bold; +} +HR { height: 1px; + border: none; + border-top: 1px solid black; +} + +/* + * LLVM Modifications. + * Note: Everything above here is generated with "doxygen -w htlm" command. See + * "doxygen --help" for details. What follows are CSS overrides for LLVM + * specific formatting. We want to keep the above so it can be replaced with + * subsequent doxygen upgrades. + */ + +.footer { + font-size: 80%; + font-weight: bold; + text-align: center; + vertical-align: middle; +} +.title { + font-size: 25pt; + color: black; background: url("../img/lines.gif"); + font-weight: bold; + border-width: 1px; + border-style: solid none solid none; + text-align: center; + vertical-align: middle; + padding-left: 8pt; + padding-top: 1px; + padding-bottom: 2px +} +A:link { + cursor: pointer; + text-decoration: none; + font-weight: bolder; +} +A:visited { + cursor: pointer; + text-decoration: underline; + font-weight: bolder; +} +A:hover { + cursor: pointer; + text-decoration: underline; + font-weight: bolder; +} +A:active { + cursor: pointer; + text-decoration: underline; + font-weight: bolder; + font-style: italic; +} +H1 { + text-align: center; + font-size: 140%; + font-weight: bold; +} +H2 { + font-size: 120%; + font-style: italic; +} +H3 { + font-size: 100%; +} + +H2, H3 { + border-bottom: 2px solid; + margin-top: 2em; +} + +A.qindex {} +A.qindexRef {} +A.el { text-decoration: none; font-weight: bold } +A.elRef { font-weight: bold } +A.code { text-decoration: none; font-weight: normal; color: #4444ee } +A.codeRef { font-weight: normal; color: #4444ee } + +div.memitem { + border: 1px solid #999999; + margin-top: 1.0em; + margin-bottom: 1.0em; + -webkit-border-radius: 0.5em; + -webkit-box-shadow: 3px 3px 6px #777777; + -moz-border-radius: 0.5em; + -moz-box-shadow: black 3px 3px 3px; +} + +div.memproto { + background-color: #E3E4E5; + padding: 0.25em 0.5em; + -webkit-border-top-left-radius: 0.5em; + -webkit-border-top-right-radius: 0.5em; + -moz-border-radius-topleft: 0.5em; + -moz-border-radius-topright: 0.5em; +} + +div.memdoc { + padding-left: 1em; + padding-right: 1em; +} diff --git a/interpreter/CppInterOp/docs/doxygen.footer b/interpreter/CppInterOp/docs/doxygen.footer new file mode 100644 index 0000000000000..3b66365f324d3 --- /dev/null +++ b/interpreter/CppInterOp/docs/doxygen.footer @@ -0,0 +1,10 @@ +
+ + + + + diff --git a/interpreter/CppInterOp/docs/doxygen.header b/interpreter/CppInterOp/docs/doxygen.header new file mode 100644 index 0000000000000..7b336beb45106 --- /dev/null +++ b/interpreter/CppInterOp/docs/doxygen.header @@ -0,0 +1,9 @@ + + + + + +CppInterOp: $title + + +

CppInterOp API Documentation

diff --git a/interpreter/CppInterOp/docs/index.rst b/interpreter/CppInterOp/docs/index.rst new file mode 100644 index 0000000000000..1c30932a43c24 --- /dev/null +++ b/interpreter/CppInterOp/docs/index.rst @@ -0,0 +1,28 @@ +.. CppInterOp documentation master file, created by + sphinx-quickstart on Sat Jun 17 13:01:46 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + + +Welcome to CppInterOp's documentation! +====================================== + + +The CppInterOp library (previously LibInterOp) provides a minimalist approach +for other languages to identify C++ entities (variables, classes, etc.). This +enables interoperability with C++ code, bringing the speed and efficiency of +C++ to simpler, more interactive languages like Python. + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + InstallationAndUsage + Emscripten-build-instructions + UsingCppInterOp + reference + tutorials + FAQ + DevelopersDocumentation diff --git a/interpreter/CppInterOp/docs/manpage.css b/interpreter/CppInterOp/docs/manpage.css new file mode 100644 index 0000000000000..c922564dc3c38 --- /dev/null +++ b/interpreter/CppInterOp/docs/manpage.css @@ -0,0 +1,256 @@ +/* Based on http://www.perldoc.com/css/perldoc.css */ + +@import url("../llvm.css"); + +body { font-family: Arial,Helvetica; } + +blockquote { margin: 10pt; } + +h1, a { color: #336699; } + + +/*** Top menu style ****/ +.mmenuon { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #ff6600; font-size: 10pt; +} +.mmenuoff { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #ffffff; font-size: 10pt; +} +.cpyright { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #ffffff; font-size: xx-small; +} +.cpyrightText { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #ffffff; font-size: xx-small; +} +.sections { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: 11pt; +} +.dsections { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: 12pt; +} +.slink { + font-family: Arial,Helvetica; font-weight: normal; text-decoration: none; + color: #000000; font-size: 9pt; +} + +.slink2 { font-family: Arial,Helvetica; text-decoration: none; color: #336699; } + +.maintitle { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: 18pt; +} +.dblArrow { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: small; +} +.menuSec { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: small; +} + +.newstext { + font-family: Arial,Helvetica; font-size: small; +} + +.linkmenu { + font-family: Arial,Helvetica; color: #000000; font-weight: bold; + text-decoration: none; +} + +P { + font-family: Arial,Helvetica; +} + +PRE { + font-size: 10pt; +} +.quote { + font-family: Times; text-decoration: none; + color: #000000; font-size: 9pt; font-style: italic; +} +.smstd { font-family: Arial,Helvetica; color: #000000; font-size: x-small; } +.std { font-family: Arial,Helvetica; color: #000000; } +.meerkatTitle { + font-family: sans-serif; font-size: x-small; color: black; } + +.meerkatDescription { font-family: sans-serif; font-size: 10pt; color: black } +.meerkatCategory { + font-family: sans-serif; font-size: 9pt; font-weight: bold; font-style: italic; + color: brown; } +.meerkatChannel { + font-family: sans-serif; font-size: 9pt; font-style: italic; color: brown; } +.meerkatDate { font-family: sans-serif; font-size: xx-small; color: #336699; } + +.tocTitle { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #333333; font-size: 10pt; +} + +.toc-item { + font-family: Arial,Helvetica; font-weight: bold; + color: #336699; font-size: 10pt; text-decoration: underline; +} + +.perlVersion { + font-family: Arial,Helvetica; font-weight: bold; + color: #336699; font-size: 10pt; text-decoration: none; +} + +.podTitle { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #000000; +} + +.docTitle { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #000000; font-size: 10pt; +} +.dotDot { + font-family: Arial,Helvetica; font-weight: bold; + color: #000000; font-size: 9pt; +} + +.docSec { + font-family: Arial,Helvetica; font-weight: normal; + color: #333333; font-size: 9pt; +} +.docVersion { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: 10pt; +} + +.docSecs-on { + font-family: Arial,Helvetica; font-weight: normal; text-decoration: none; + color: #ff0000; font-size: 10pt; +} +.docSecs-off { + font-family: Arial,Helvetica; font-weight: normal; text-decoration: none; + color: #333333; font-size: 10pt; +} + +h2 { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: medium; +} +h1 { + font-family: Verdana,Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: large; +} + +DL { + font-family: Arial,Helvetica; font-weight: normal; text-decoration: none; + color: #333333; font-size: 10pt; +} + +UL > LI > A { + font-family: Arial,Helvetica; font-weight: bold; + color: #336699; font-size: 10pt; +} + +.moduleInfo { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #333333; font-size: 11pt; +} + +.moduleInfoSec { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: 10pt; +} + +.moduleInfoVal { + font-family: Arial,Helvetica; font-weight: normal; text-decoration: underline; + color: #000000; font-size: 10pt; +} + +.cpanNavTitle { + font-family: Arial,Helvetica; font-weight: bold; + color: #ffffff; font-size: 10pt; +} +.cpanNavLetter { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #333333; font-size: 9pt; +} +.cpanCat { + font-family: Arial,Helvetica; font-weight: bold; text-decoration: none; + color: #336699; font-size: 9pt; +} + +.bttndrkblue-bkgd-top { + background-color: #225688; + background-image: url(/global/mvc_objects/images/bttndrkblue_bgtop.gif); +} +.bttndrkblue-bkgd-left { + background-color: #225688; + background-image: url(/global/mvc_objects/images/bttndrkblue_bgleft.gif); +} +.bttndrkblue-bkgd { + padding-top: 0px; + padding-bottom: 0px; + margin-bottom: 0px; + margin-top: 0px; + background-repeat: no-repeat; + background-color: #225688; + background-image: url(/global/mvc_objects/images/bttndrkblue_bgmiddle.gif); + vertical-align: top; +} +.bttndrkblue-bkgd-right { + background-color: #225688; + background-image: url(/global/mvc_objects/images/bttndrkblue_bgright.gif); +} +.bttndrkblue-bkgd-bottom { + background-color: #225688; + background-image: url(/global/mvc_objects/images/bttndrkblue_bgbottom.gif); +} +.bttndrkblue-text a { + color: #ffffff; + text-decoration: none; +} +a.bttndrkblue-text:hover { + color: #ffDD3C; + text-decoration: none; +} +.bg-ltblue { + background-color: #f0f5fa; +} + +.border-left-b { + background: #f0f5fa url(/i/corner-leftline.gif) repeat-y; +} + +.border-right-b { + background: #f0f5fa url(/i/corner-rightline.gif) repeat-y; +} + +.border-top-b { + background: #f0f5fa url(/i/corner-topline.gif) repeat-x; +} + +.border-bottom-b { + background: #f0f5fa url(/i/corner-botline.gif) repeat-x; +} + +.border-right-w { + background: #ffffff url(/i/corner-rightline.gif) repeat-y; +} + +.border-top-w { + background: #ffffff url(/i/corner-topline.gif) repeat-x; +} + +.border-bottom-w { + background: #ffffff url(/i/corner-botline.gif) repeat-x; +} + +.bg-white { + background-color: #ffffff; +} + +.border-left-w { + background: #ffffff url(/i/corner-leftline.gif) repeat-y; +} diff --git a/interpreter/CppInterOp/docs/reference.rst b/interpreter/CppInterOp/docs/reference.rst new file mode 100644 index 0000000000000..8e82a6b818847 --- /dev/null +++ b/interpreter/CppInterOp/docs/reference.rst @@ -0,0 +1,39 @@ +Reference +--------- + +[1] Results of US Research Software Sustainability Institute (URSSI) +programming language survey, private communication from Dan Katz (UIUC/NCSA), +https://spectrum.ieee. org/computing/software/the-2017-top-programming-languages +(Visited August 2021). + +[2] Beazley, David M. “SWIG: An Easy to Use Tool for Integrating Scripting +Languages with C and C++.” Tcl/Tk Workshop. Vol. 43. 1996. + +[3] SIP software home page, https://www.riverbankcomputing.com/ software/sip/intro (Visited Sep 2021) + +[4] Boost.Python Reference Manual (for software version 1.72), +https: //www.boost.org/doc/libs/1_65_1/libs/python/doc/html/reference/index.html, (Visited August 2021) + +[5] Pybind11 project homepage, http://pybind11.readthedocs.io/ (Visited August 2021). + +[6] ​​Vassilev, Vassil, et al. “Cling–the new interactive interpreter for ROOT 6. +” Journal of Physics: Conference Series. Vol. 396. No. 5. IOP Publishing, 2012. + +[7] Cxx.jl GitHub repository, https://github.com/JuliaInterop/Cxx.jl (Visited September 2021) + +[8] Wim Lavrijsen, cppyy, presentation, https://compiler-research.org/meetings/#caas_02Sep2021 (Visited September 2021) + +[9] Cppyy Philosophy, https://cppyy.readthedocs.io/en/latest/philosophy.html#run-time-v-s-compile-time (Visited August 2021) + +[10] Clang: a C language family frontend for LLVM, https://clang.llvm.org/, (Visited August 2021) + +[11] Alexandru Militaru, Calling C++ libraries from a D-written DSL: A cling/cppyy-based approach, presentation, + https://compiler-research.org/meetings/#caas_04Feb2021, (Visited August 2021) + +[12] Keno Fischer, A brief history of Cxx.jl, https://compiler-research.org/meetings/#caas_05Aug2021, (Visited August 2021) + +[13] Template instantiation not happening in indirectly called function, + https://bitbucket.org/wlav/cppyy/issues/369/template-instantiation-not-happening-in (Visited August 2021). + +[14] Extend clang AST to provide information for the type as written in template instantiations, + https://llvm.org/OpenProjects.html#clang-template-instantiation-sugar (Visited August 2021). \ No newline at end of file diff --git a/interpreter/CppInterOp/docs/tutorials.rst b/interpreter/CppInterOp/docs/tutorials.rst new file mode 100644 index 0000000000000..9f636f26b2839 --- /dev/null +++ b/interpreter/CppInterOp/docs/tutorials.rst @@ -0,0 +1,304 @@ +Tutorials +---------- +This tutorial emphasises the abilities and usage of CppInterOp. Let's get +started! The tutorial demonstrates two examples, one in C and one in Python, +for interoperability. + +**Note:This example library shown below is to illustrate the concept on which +CppInterOp is based.** + +Python: +======= + +.. code-block:: python + + libInterop = ctypes.CDLL(libpath, mode = ctypes.RTLD_GLOBAL) + _cpp_compile = libInterop.Clang_Parse + _cpp_compile.argtypes = [ctypes.c_char_p] + + # We are using ctypes for inducting our library, and *Clang_Parse*, which is + # part of the library, for parsing the C++ code. + +.. code-block:: cpp + + Giving a glance at how the header file looks for our library : + The header keeps our function declarations for the functions used in our + library. + + # This basically parses our C++ code. + void Clang_Parse(const char* Code); + + # Looks up an entity with the given name, possibly in the given Context. + Decl_t Clang_LookupName(const char* Name, Decl_t Context); + + # Returns the address of a JIT'd function of the corresponding declaration. + FnAddr_t Clang_GetFunctionAddress(Decl_t D); + + # Returns the name of any named decl (class, namespace) & template arguments + std::string GetCompleteName(Decl_t A); + + # Allocates memory of underlying size of the passed declaration. + void * Clang_CreateObject(Decl_t RecordDecl); + + # Instantiates a given templated declaration. + Decl_t Clang_InstantiateTemplate(Decl_t D, const char* Name, const char* Args); + +The C++ code that is to be used in Python comes under this below section. This +code is parsed by the CppInterOp library in the previous snippet and further +compilation goes on. + +.. code-block:: cpp + + def cpp_compile(arg): + return _cpp_compile(arg.encode("ascii")) + + cpp_compile(r"""\ + void* operator new(__SIZE_TYPE__, void* __p) noexcept; + extern "C" int printf(const char*,...); + class A {}; + class C {}; + struct B : public A { + template + void callme(T, S, U*) { printf(" call me may B! \n"); } + }; + """) + +.. code-block:: python + + class CppInterOpLayerWrapper: + _get_scope = libInterop.Clang_LookupName + _get_scope.restype = ctypes.c_size_t + _get_scope.argtypes = [ctypes.c_char_p] + + _construct = libInterop.Clang_CreateObject + _construct.restype = ctypes.c_void_p + _construct.argtypes = [ctypes.c_size_t] + + _get_template_ct = libInterop.Clang_InstantiateTemplate + _get_template_ct.restype = ctypes.c_size_t + _get_template_ct.argtypes = [ctypes.c_size_t, ctypes.c_char_p, ctypes.c_char_p] + + def _get_template(self, scope, name, args): + return self._get_template_ct(scope, name.encode("ascii"), args.encode("ascii")) + + def get_scope(self, name): + return self._get_scope(name.encode("ascii")) + + def get_template(self, scope, name, tmpl_args = [], tpargs = []): + if tmpl_args: + # Instantiation is explicit from full name + full_name = name + '<' + ', '.join([a for a in tmpl_args]) + '>' + meth = self._get_template(scope, full_name, '') + elif tpargs: + # Instantiation is implicit from argument types + meth = self._get_template(scope, name, ', '.join([a.__name__ for a in tpargs])) + return CallCPPFunc(meth) + + def construct(self, cpptype): + return self._construct(cpptype) + +The class CppInterOpLayerWrapper is supposed to provide a Python wrapper over +the cppinterop layer. Here, we have the functions *Clang_LookupName, +Clang_CreateObject and Clang_InstantiateTemplate* are being used, so these +are being wrapped to be used in the Python language. + +We get to know the scope of the attribute of class by using get_scope. In a +similar manner, we can use get_namespace. + +.. code-block::python + + class TemplateWrapper: + def __init__(self, scope, name): + self._scope = scope + self._name = name + + def __getitem__(self, *args, **kwds): + # Look up the template and return the overload. + return gIL.get_template( + self._scope, self._name, tmpl_args = args) + + def __call__(self, *args, **kwds): + # Keyword arguments are not supported for this demo. + assert not kwds + + # Construct the template arguments from the types and find the overload. + ol = gIL.get_template( + self._scope, self._name, tpargs = [type(a) for a in args]) + + # Call actual method. + ol(*args, **kwds) + +In this example for instantiating templates, we need the wrapper for the +function being used, which is responsible for finding a template that matches +the arguments. + +.. code-block::python + + gIL = CppInterOpLayerWrapper() + + def cpp_allocate(proxy): + pyobj = object.__new__(proxy) + proxy.__init__(pyobj) + pyobj.cppobj = gIL.construct(proxy.handle) + return pyobj + + +.. code-block::python + + if __name__ == '__main__': + # create a couple of types to play with + CppA = type('A', (), { + 'handle' : gIL.get_scope('A'), + '__new__' : cpp_allocate + }) + h = gIL.get_scope('B') + CppB = type('B', (CppA,), { + 'handle' : h, + '__new__' : cpp_allocate, + 'callme' : TemplateWrapper(h, 'callme') + }) + CppC = type('C', (), { + 'handle' : gIL.get_scope('C'), + '__new__' : cpp_allocate + }) + + # call templates + a = CppA() + b = CppB() + c = CppC() + + b.callme(a, 42, c) + +* In the main, we create types to access the class attributes and the wrapper +can be supplied as the parameter in the map of the type given. + +* We are using a python wrapper around functions to be supplied to the map for +the identification and usage of the function. + +* Finally, the objects are created for the respective class and the desired +function is called, which is `callme` function in this case. + +The complete example can found below: +`Example `_. + +C: +=== + +Include **p3-ex4-lib.h**, which contains the declarations for the functions used +in this code. The detailed summary of header comes in the latter part. + + +The variable Code is given as a C-style string, it contains the C++ code +to be parsed. It has two classes, class `A` and a templated class `B` with a member +function callme. + + .. code-block:: C + + const char* Code = "void* operator new(__SIZE_TYPE__, void* __p) noexcept;" + "extern \"C\" int printf(const char*,...);" + "class A {};" + "\n #include \n" + "class B {" + "public:" + " template" + " void callme(T) {" + " printf(\" Instantiated with [%s] \\n \", typeid(T).name());" + " }" + "};"; + +The main() begins with the call to **Clang_Parse** from interop library responsible +for parsing the provided C++ code. + +Next there a number of initializations, **Instantiation** is initialized to zero, +it will be used to store the instantiated template. The **InstantiationArgs** +is initialized to "A", it will be used as the argument when instantiating the template. +`T` is initialized to zero, used to store the declaration of the type "T" used for +instantiation. + + .. code-block:: C + + Decl_t Instantiation = 0; + const char * InstantiationArgs = "A"; + Decl_t TemplatedClass = Clang_LookupName("B", /*Context=*/0); + Decl_t T = 0; + +This snippet checks command-line arguments were provided by the argc arguments. +We take the first argument (`argv[1]`), parse it, then take the second argument +(`argv[2]`) using **Clang_LookupName**, and reassigns **InstantiationArgs** to +the third argument (`argv[3]`). In the else case, we decide to go with the "A". + +The code proceeds to instantiate the template `B::callme` with the given +type, using the **Clang_InstantiateTemplate** function from the +library. The instantiated template is stored in the **Instantiation**. + + .. code-block:: C + + Instantiation = Clang_InstantiateTemplate(TemplatedClass, "callme", InstantiationArgs); + + +A function pointer **callme_fn_ptr** is declared with a type `fn_def` that represents +the function taking a `void*` argument and returning void. The result of +**Clang_GetFunctionAddress** is casted by the function pointer. + + .. code-block:: C + + typedef void (*fn_def)(void*); + fn_def callme_fn_ptr = (fn_def) Clang_GetFunctionAddress(Instantiation); + +The code then creates an object of type `A` using **Clang_CreateObject**, and the +pointer to this object is stored in `NewA`. + + .. code-block:: C + + void* NewA = Clang_CreateObject(T); + +Then the function pointer **callme_fn_ptr** is called with the `NewA`, which +calls the member function `B::callme` with the instantiated type. Thus, the instantiation +happens with type `A` and we get the below result. + +You get the **output** as : + .. code-block:: bash + + Instantiated with [1A] + +In conclusion, this C code uses the CppInterOp library to dynamically instantiate +templates and call member functions based on provided types. This example was to +show how we can instantiate templates, in a similar manner we can use the +CppInterOp library to use many other features and attributes of languages to +interoperate. + +**Header File:** + +We wrap the library functions within the extern "C" for its usage in C programs. +All the functions of the library which are to be used in the C program have to +be under the extern C for the compiler to know the C++ code within. + +.. code-block:: C + + extern "C" { + #endif // __cplusplus + /// Process C++ code. + /// + void Clang_Parse(const char* Code); + + /// Looks up an entity with the given name, possibly in the given Context. + /// + Decl_t Clang_LookupName(const char* Name, Decl_t Context); + + /// Returns the address of a JIT'd function of the corresponding declaration. + /// + FnAddr_t Clang_GetFunctionAddress(Decl_t D); + + /// Allocates memory of underlying size of the passed declaration. + /// + void * Clang_CreateObject(Decl_t RecordDecl); + + /// Instantiates a given templated declaration. + Decl_t Clang_InstantiateTemplate(Decl_t D, const char* Name, const char* Args); + #ifdef __cplusplus + } + + +The complete example can found below: +`Example `_. \ No newline at end of file diff --git a/interpreter/CppInterOp/environment-wasm.yml b/interpreter/CppInterOp/environment-wasm.yml new file mode 100644 index 0000000000000..4db4342d8aa8a --- /dev/null +++ b/interpreter/CppInterOp/environment-wasm.yml @@ -0,0 +1,10 @@ +name: CppInterOp-wasm +channels: + - https://repo.prefix.dev/emscripten-forge-dev +dependencies: + - emscripten-abi==3.1.73 + - nlohmann_json + - xeus-lite + - xeus + - cpp-argparse + - pugixml diff --git a/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h b/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h new file mode 100644 index 0000000000000..e5b057ddecdf7 --- /dev/null +++ b/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h @@ -0,0 +1,366 @@ +// 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. + */ +CXInterpreter clang_createInterpreter(const char* const* argv, int argc); + +typedef void* TInterp_t; + +/** + * Bridge between C API and C++ API. + * + * \returns a \c CXInterpreter. + */ +CXInterpreter clang_createInterpreterFromRawPtr(TInterp_t I); + +/** + * Returns a pointer to the underlying interpreter. + */ +void* clang_Interpreter_getClangInterpreter(CXInterpreter I); + +/** + * Returns a \c TInterp_t and takes the ownership. + */ +TInterp_t clang_Interpreter_takeInterpreterAsPtr(CXInterpreter I); + +/** + * Undo N previous incremental inputs. + */ +enum CXErrorCode clang_Interpreter_undo(CXInterpreter I, unsigned int N); + +/** + * Dispose of the given interpreter context. + */ +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; + +/** + * 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. + */ +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. + */ +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. + */ +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. + */ +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. + */ +CXValue clang_createValue(void); + +/** + * Dispose of the given CXValue. + * + * \param V The CXValue to dispose. + */ +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. + */ +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. + */ +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. + */ +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. + */ +void clang_Interpreter_unloadLibrary(CXInterpreter I, const char* lib_stem); + +/** + * @} + */ + +/** + * \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 +void clang_scope_dump(CXScope S); + +/** + * Checks if a class has a default constructor. + */ +bool clang_hasDefaultConstructor(CXScope S); + +/** + * Returns the default constructor of a class, if any. + */ +CXScope clang_getDefaultConstructor(CXScope S); + +/** + * Returns the class destructor, if any. + */ +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'). + */ +CXString clang_getFunctionSignature(CXScope func); + +/** + * Checks if a function is a templated function. + */ +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. + */ +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. + */ +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. + */ +CXString clang_getTypeAsString(CXQualType type); + +/** + * Returns the complex of the provided type. + */ +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. + */ +CXObject clang_allocate(unsigned int n); + +/** + * Deallocates memory for a given class. + */ +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. + */ +CXObject clang_construct(CXScope scope, void* arena); + +/** + * 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. + */ +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. + */ +void clang_destruct(CXObject This, CXScope S, bool withFree); + +/** + * @} + */ + +LLVM_CLANG_C_EXTERN_C_END + +#endif // LLVM_CLANG_C_CXCPPINTEROP_H + // NOLINTEND() \ No newline at end of file diff --git a/interpreter/CppInterOp/include/clang/Interpreter/CppInterOp.h b/interpreter/CppInterOp/include/clang/Interpreter/CppInterOp.h new file mode 100644 index 0000000000000..96677dbe1512e --- /dev/null +++ b/interpreter/CppInterOp/include/clang/Interpreter/CppInterOp.h @@ -0,0 +1,774 @@ +//--------------------------------------------------------------------*- C++ -*- +// CLING - the C++ LLVM-based InterpreterG :) +// author: Vassil Vassilev +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#ifndef CPPINTEROP_CPPINTEROP_H +#define CPPINTEROP_CPPINTEROP_H + +#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 +#endif + +namespace Cpp { + using TCppIndex_t = size_t; + using TCppScope_t = void*; + using TCppType_t = void*; + using TCppFunction_t = void*; + using TCppConstFunction_t = const void*; + using TCppFuncAddr_t = void*; + using TInterp_t = void*; + using TCppObject_t = void*; + + enum Operator { + 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 { kUnary = 1, kBinary, kBoth }; + + /// 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, + kDestructorCall, + }; + struct ArgList { + void** m_Args = nullptr; + size_t m_ArgSize = 0; + // Clang struggles with =default... + ArgList() : m_Args(nullptr), m_ArgSize(0) {} + 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. + using GenericCall = void (*)(void*, int, void**, void*); + using DestructorCall = void (*)(void*, unsigned long, int); + private: + union { + GenericCall m_GenericCall; + DestructorCall m_DestructorCall; + }; + const Kind m_Kind; + TCppConstFunction_t m_FD; + JitCall() : m_Kind(kUnknown), m_GenericCall(nullptr), m_FD(nullptr) {} + JitCall(Kind K, GenericCall C, TCppConstFunction_t FD) + : m_Kind(K), m_GenericCall(C), m_FD(FD) {} + JitCall(Kind K, DestructorCall C, TCppConstFunction_t Dtor) + : m_Kind(K), m_DestructorCall(C), m_FD(Dtor) {} + + /// Checks if the passed arguments are valid for the given function. + bool AreArgumentsValid(void* result, ArgList args, void* self) const; + + /// This function is used for debugging, it reports when the function was + /// called. + void ReportInvokeStart(void* result, ArgList args, void* self) const; + 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 { + // Forward if we intended to call a dtor with only 1 parameter. + if (m_Kind == kDestructorCall && result && !args.m_Args) + return InvokeDestructor(result, /*nary=*/0UL, /*withFree=*/true); + +#ifndef NDEBUG + assert(AreArgumentsValid(result, args, self) && "Invalid args!"); + ReportInvokeStart(result, args, self); +#endif // NDEBUG + m_GenericCall(self, args.m_ArgSize, args.m_Args, result); + } + /// 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); + } + }; + + ///\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 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(TCppType_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); + + /// 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); + + /// 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 Templated Methods that are in the Class that is + /// supplied as a parameter. + ///\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 void + 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); + + /// 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); + + ///\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); + + /// 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); + + /// 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 arity of the operator or kNone + OperatorArity GetOperatorArity(TCppFunction_t op); + + ///\returns list of operator overloads + void GetOperator(TCppScope_t scope, Operator op, + std::vector& operators, + OperatorArity kind = kBoth); + + /// Creates an instance of the interpreter we need for the various interop + /// services. + ///\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. + CPPINTEROP_API TInterp_t + CreateInterpreter(const std::vector& Args = {}, + const std::vector& GpuArgs = {}); + + /// 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(); + + /// 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 + /// + ///\returns Instantiated templated class/function/variable pointer + CPPINTEROP_API TCppScope_t + InstantiateTemplate(TCppScope_t tmpl, const TemplateArgInfo* template_args, + size_t template_args_size); + + /// 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 template match based on explicit template parameters and + /// argument types + /// + ///\param[in] candidates - Vector of suitable candidates that come under the + /// parent scope and have the same name (obtained using + /// GetClassTemplatedMethods) + ///\param[in] explicit_types - set of expicitly instantiated template types + ///\param[in] arg_types - set of argument types + ///\returns Instantiated function pointer + CPPINTEROP_API TCppFunction_t + BestTemplateFunctionMatch(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); + + 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 for a given class. + CPPINTEROP_API TCppObject_t Allocate(TCppScope_t scope); + + /// Deallocates memory for a given class. + CPPINTEROP_API void Deallocate(TCppScope_t scope, TCppObject_t address); + + /// Creates an object of class \c scope and calls its default constructor. If + /// \c arena is set it uses placement new. + CPPINTEROP_API TCppObject_t Construct(TCppScope_t scope, + void* arena = nullptr); + + /// Calls the destructor of object of type \c type. When withFree is true it + /// calls operator delete/free. + CPPINTEROP_API void Destruct(TCppObject_t This, TCppScope_t type, + bool withFree = true); + + /// @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 fragmet 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); + +} // end namespace Cpp + +#endif // CPPINTEROP_CPPINTEROP_H diff --git a/interpreter/CppInterOp/lib/CMakeLists.txt b/interpreter/CppInterOp/lib/CMakeLists.txt new file mode 100644 index 0000000000000..1951dee7505d2 --- /dev/null +++ b/interpreter/CppInterOp/lib/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(Interpreter) diff --git a/interpreter/CppInterOp/lib/Interpreter/CMakeLists.txt b/interpreter/CppInterOp/lib/Interpreter/CMakeLists.txt new file mode 100644 index 0000000000000..f52eb0d9cdbac --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/CMakeLists.txt @@ -0,0 +1,141 @@ +if(EMSCRIPTEN) + set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) + set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-s SIDE_MODULE=1") + set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS "-s SIDE_MODULE=1") + set(CMAKE_STRIP FALSE) + + add_llvm_library(clangCppInterOp + SHARED + + CppInterOp.cpp + CXCppInterOp.cpp + DynamicLibraryManager.cpp + DynamicLibraryManagerSymbol.cpp + Paths.cpp + + # Additional libraries from Clang and LLD + LINK_LIBS + clangInterpreter + ) + #FIXME: Setting no_soname=1 is needed until https://github.com/emscripten-core/emscripten/blob/ac676d5e437525d15df5fd46bc2c208ec6d376a3/cmake/Modules/Platform/Emscripten.cmake#L36 + # is patched out of emsdk, as --soname is not recognised by emscripten. A PR to do this has been done here https://github.com/emscripten-core/emscripten/pull/23453 + #FIXME: A patch is needed to llvm to remove -Wl,-z,defs since it is now recognised on emscripten. What needs to be removed is here + # https://github.com/llvm/llvm-project/blob/128e2e446e90c3b1827cfc7d4d19e3c0976beff3/llvm/cmake/modules/HandleLLVMOptions.cmake#L318 . The PR to do try to do this is here + # https://github.com/llvm/llvm-project/pull/123396 + set_target_properties(clangCppInterOp + PROPERTIES NO_SONAME 1 +) + target_link_options(clangCppInterOp PRIVATE + PUBLIC "SHELL: -s WASM_BIGINT" + ) +else() + set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + BinaryFormat + Core + Object + OrcJit + Support + ) + # FIXME: Investigate why this needs to be conditionally included. + if ("LLVMFrontendDriver" IN_LIST LLVM_AVAILABLE_LIBS) + list(APPEND LLVM_LINK_COMPONENTS FrontendDriver) + endif() + if ("LLVMOrcDebugging" IN_LIST LLVM_AVAILABLE_LIBS) + list(APPEND LLVM_LINK_COMPONENTS OrcDebugging) + endif() + + set(DLM + DynamicLibraryManager.cpp + DynamicLibraryManagerSymbol.cpp + Paths.cpp + ) + if (CPPINTEROP_USE_CLING) + set(LLVM_OPTIONAL_SOURCES ${LLVM_OPTIONAL_SOURCES} ${DLM}) + set(DLM) + endif(CPPINTEROP_USE_CLING) + if (CPPINTEROP_USE_REPL) + #Use DML optional sources + endif(CPPINTEROP_USE_REPL) + + if (CPPINTEROP_USE_CLING) + set(cling_clang_interp clingInterpreter) + endif() + if (CPPINTEROP_USE_REPL) + set(cling_clang_interp clangInterpreter) + endif() + + set(link_libs + ${cling_clang_interp} + clangAST + clangBasic + clangFrontend + clangLex + clangSema + ) + + if(NOT WIN32) + list(APPEND link_libs dl) + endif() + + # Get rid of libLLVM-X.so which is appended to the list of static libraries. + if (LLVM_LINK_LLVM_DYLIB) + set(new_libs ${link_libs}) + set(libs ${new_libs}) + while(NOT "${new_libs}" STREQUAL "") + foreach(lib ${new_libs}) + if(TARGET ${lib}) + get_target_property(transitive_libs ${lib} INTERFACE_LINK_LIBRARIES) + if (NOT transitive_libs) + continue() + endif() + foreach(transitive_lib ${transitive_libs}) + get_target_property(lib_type ${transitive_lib} TYPE) + if("${lib_type}" STREQUAL "STATIC_LIBRARY") + list(APPEND static_transitive_libs ${transitive_lib}) + else() + # Filter our libLLVM.so and friends. + continue() + endif() + if(NOT ${transitive_lib} IN_LIST libs) + list(APPEND newer_libs ${transitive_lib}) + list(APPEND libs ${transitive_lib}) + endif() + endforeach(transitive_lib) + # Update the target properties with the list of only static libraries. + set_target_properties(${lib} PROPERTIES INTERFACE_LINK_LIBRARIES "${static_transitive_libs}") + set(static_transitive_libs "") + endif() + endforeach(lib) + set(new_libs ${newer_libs}) + set(newer_libs "") + endwhile() + # We just got rid of the libLLVM.so and other components shipped as shared + # libraries, we need to make up for the missing dependency. + list(APPEND LLVM_LINK_COMPONENTS + Coverage + FrontendHLSL + LTO + ) + # We will need to append the missing dependencies to pull in the right + # LLVM library dependencies. + list(APPEND link_libs + clangCodeGen + clangStaticAnalyzerCore + ) + endif(LLVM_LINK_LLVM_DYLIB) + + add_llvm_library(clangCppInterOp + DISABLE_LLVM_LINK_LLVM_DYLIB + CppInterOp.cpp + CXCppInterOp.cpp + ${DLM} + LINK_LIBS + ${link_libs} + ) +endif() + +string(REPLACE ";" "\;" _VER CPPINTEROP_VERSION) +set_source_files_properties(CppInterOp.cpp PROPERTIES COMPILE_DEFINITIONS + "LLVM_BINARY_DIR=\"${LLVM_BINARY_DIR}\";CPPINTEROP_VERSION=\"${_VAR}\"" +) diff --git a/interpreter/CppInterOp/lib/Interpreter/CXCppInterOp.cpp b/interpreter/CppInterOp/lib/Interpreter/CXCppInterOp.cpp new file mode 100644 index 0000000000000..35025b0ede565 --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/CXCppInterOp.cpp @@ -0,0 +1,614 @@ +#include "clang-c/CXCppInterOp.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/Interpreter/CppInterOp.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 "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()) { +#if CLANG_VERSION_MAJOR >= 18 + 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; +#else + case TagTypeKind::TTK_Interface: // fall through + case TagTypeKind::TTK_Struct: + return CXCursor_StructDecl; + case TagTypeKind::TTK_Class: + return CXCursor_ClassDecl; + case TagTypeKind::TTK_Union: + return CXCursor_UnionDecl; + case TagTypeKind::TTK_Enum: + return CXCursor_EnumDecl; +#endif + } + } + } + + 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); + TKCASE(Elaborated); + TKCASE(Pipe); + TKCASE(Attributed); +#if CLANG_VERSION_MAJOR >= 16 + TKCASE(BTFTagAttributed); +#endif + 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 = new CXInterpreterImpl(); // NOLINT(*-owning-memory) + I->Interp = std::make_unique(argc, argv); + // 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; +} + +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 + return CXError_Failure; +#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); +} + +enum CXErrorCode clang_Interpreter_declare(CXInterpreter I, const char* code, + bool silent) { + auto* interp = getInterpreter(I); + auto& diag = interp->getSema().getDiagnostics(); + + const bool is_silent_old = diag.getSuppressAllDiagnostics(); + + diag.setSuppressAllDiagnostics(silent); + const auto result = interp->declare(code); + diag.setSuppressAllDiagnostics(is_silent_old); + + 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) { +#ifdef CPPINTEROP_USE_CLING + auto val = std::make_unique(); +#else + auto val = std::make_unique(); +#endif // CPPINTEROP_USE_CLING + + return val.release(); +} + +void clang_Value_dispose(CXValue V) { +#ifdef CPPINTEROP_USE_CLING + delete static_cast(V); // NOLINT(*-owning-memory) +#else + delete static_cast(V); // NOLINT(*-owning-memory) +#endif // CPPINTEROP_USE_CLING +} + +enum CXErrorCode clang_Interpreter_evaluate(CXInterpreter I, const char* code, + CXValue V) { +#ifdef CPPINTEROP_USE_CLING + auto* val = static_cast(V); +#else + auto* val = static_cast(V); +#endif // CPPINTEROP_USE_CLING + + 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); +} + +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 Cpp { +bool InsertOrReplaceJitSymbol(compat::Interpreter& I, + const char* linker_mangled_name, + uint64_t address); +} // namespace Cpp + +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(compat::FixTypeName(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)); +} + +CXScope clang_getDestructor(CXScope S) { + auto* D = getDecl(S); + + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { + getInterpreter(S)->getSema().ForceDeclarationOfImplicitMembers(CXXRD); + return clang::cxscope::MakeCXScope(CXXRD->getDestructor(), getNewTU(S)); + } + + 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(""); +} + +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 = Cpp::Cpp_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 Cpp { +TCppScope_t InstantiateTemplate(compat::Interpreter& I, TCppScope_t tmpl, + const TemplateArgInfo* template_args, + size_t template_args_size); +} // namespace Cpp + +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 Cpp { +void* Construct(compat::Interpreter& interp, TCppScope_t scope, + void* arena /*=nullptr*/); +} // namespace Cpp + +CXObject clang_construct(CXScope scope, void* arena) { + return Cpp::Construct(*getInterpreter(scope), + static_cast(getDecl(scope)), arena); +} + +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 Cpp { +void Destruct(compat::Interpreter& interp, TCppObject_t This, + clang::Decl* Class, bool withFree); +} // namespace Cpp + +void clang_destruct(CXObject This, CXScope S, bool withFree) { + Cpp::Destruct(*getInterpreter(S), This, getDecl(S), withFree); +} \ No newline at end of file diff --git a/interpreter/CppInterOp/lib/Interpreter/Compatibility.h b/interpreter/CppInterOp/lib/Interpreter/Compatibility.h new file mode 100644 index 0000000000000..88169028e2d12 --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/Compatibility.h @@ -0,0 +1,497 @@ +//--------------------------------------------------------------------*- C++ -*- +// CppInterOp Compatibility +// author: Alexander Penev +//------------------------------------------------------------------------------ +#ifndef CPPINTEROP_COMPATIBILITY_H +#define CPPINTEROP_COMPATIBILITY_H + +#include "clang/AST/GlobalDecl.h" +#include "clang/Basic/Version.h" +#include "clang/Config/config.h" + +#ifdef _MSC_VER +#define dup _dup +#define dup2 _dup2 +#define close _close +#define fileno _fileno +#endif + +static inline char* GetEnv(const char* Var_Name) { +#ifdef _MSC_VER + char* Env = nullptr; + size_t sz = 0; + getenv_s(&sz, Env, sz, Var_Name); + return Env; +#else + return getenv(Var_Name); +#endif +} + +#if CLANG_VERSION_MAJOR < 19 +#define Template_Deduction_Result Sema::TemplateDeductionResult +#define Template_Deduction_Result_Success \ + Sema::TemplateDeductionResult::TDK_Success +#else +#define Template_Deduction_Result TemplateDeductionResult +#define Template_Deduction_Result_Success TemplateDeductionResult::Success +#endif + +#if CLANG_VERSION_MAJOR < 19 +#define For_Visible_Redeclaration Sema::ForVisibleRedeclaration +#define Clang_For_Visible_Redeclaration clang::Sema::ForVisibleRedeclaration +#else +#define For_Visible_Redeclaration RedeclarationKind::ForVisibleRedeclaration +#define Clang_For_Visible_Redeclaration \ + RedeclarationKind::ForVisibleRedeclaration +#endif + +#if CLANG_VERSION_MAJOR < 19 +#define CXXSpecialMemberKindDefaultConstructor \ + clang::Sema::CXXDefaultConstructor +#define CXXSpecialMemberKindCopyConstructor clang::Sema::CXXCopyConstructor +#define CXXSpecialMemberKindMoveConstructor clang::Sema::CXXMoveConstructor +#else +#define CXXSpecialMemberKindDefaultConstructor \ + CXXSpecialMemberKind::DefaultConstructor +#define CXXSpecialMemberKindCopyConstructor \ + CXXSpecialMemberKind::CopyConstructor +#define CXXSpecialMemberKindMoveConstructor \ + CXXSpecialMemberKind::MoveConstructor +#endif + +#if LLVM_VERSION_MAJOR < 18 +#define starts_with startswith +#define ends_with endswith +#endif + +#if CLANG_VERSION_MAJOR >= 18 +#include "clang/Interpreter/CodeCompletion.h" +#endif + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/ExecutionEngine/JITSymbol.h" +#include "llvm/ExecutionEngine/Orc/LLJIT.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Path.h" + +// std::regex breaks pytorch's jit: pytorch/pytorch#49460 +#include "llvm/Support/Regex.h" + +#ifdef CPPINTEROP_USE_CLING + +#include "cling/Interpreter/DynamicLibraryManager.h" +#include "cling/Interpreter/Interpreter.h" +#include "cling/Interpreter/Transaction.h" +#include "cling/Interpreter/Value.h" + +#include "cling/Utils/AST.h" + +#include + +namespace Cpp { +namespace Cpp_utils = cling::utils; +} + +namespace compat { + +using Interpreter = cling::Interpreter; + +inline void maybeMangleDeclName(const clang::GlobalDecl& GD, + std::string& mangledName) { + cling::utils::Analyze::maybeMangleDeclName(GD, mangledName); +} + +/// For Cling <= LLVM 16, this is a horrible hack obtaining the private +/// llvm::orc::LLJIT by computing the object offsets in the cling::Interpreter +/// instance(IncrementalExecutor): sizeof (m_Opts) + sizeof(m_LLVMContext). The +/// IncrementalJIT and JIT itself have an offset of 0 as the first datamember. +/// +/// The getExecutionEngine() interface has been added for Cling based on LLVM +/// >=18 and should be used in future releases. +inline llvm::orc::LLJIT* getExecutionEngine(cling::Interpreter& I) { +#if CLANG_VERSION_MAJOR >= 18 + return I.getExecutionEngine(); +#endif + + unsigned m_ExecutorOffset = 0; + +#if CLANG_VERSION_MAJOR == 13 +#ifdef __APPLE__ + m_ExecutorOffset = 62; +#else + m_ExecutorOffset = 72; +#endif // __APPLE__ +#endif + +// Note: The offsets changed in Cling based on LLVM 16 with the introduction of +// a thread safe context - llvm::orc::ThreadSafeContext +#if CLANG_VERSION_MAJOR == 16 +#ifdef __APPLE__ + m_ExecutorOffset = 68; +#else + m_ExecutorOffset = 78; +#endif // __APPLE__ +#endif + + int* IncrementalExecutor = + ((int*)(const_cast(&I))) + m_ExecutorOffset; + int* IncrementalJit = *(int**)IncrementalExecutor + 0; + int* LLJIT = *(int**)IncrementalJit + 0; + return *(llvm::orc::LLJIT**)LLJIT; +} + +inline llvm::Expected +getSymbolAddress(cling::Interpreter& I, llvm::StringRef IRName) { + if (void* Addr = I.getAddressOfGlobal(IRName)) + return (llvm::JITTargetAddress)Addr; + + llvm::orc::LLJIT& Jit = *compat::getExecutionEngine(I); + llvm::orc::SymbolNameVector Names; + llvm::orc::ExecutionSession& ES = Jit.getExecutionSession(); + Names.push_back(ES.intern(IRName)); +#if CLANG_VERSION_MAJOR < 16 + return llvm::make_error(Names); +#else + return llvm::make_error(ES.getSymbolStringPool(), + std::move(Names)); +#endif // CLANG_VERSION_MAJOR +} + +inline void codeComplete(std::vector& Results, + const cling::Interpreter& I, const char* code, + unsigned complete_line = 1U, + unsigned complete_column = 1U) { + std::vector results; + size_t column = complete_column; + I.codeComplete(code, column, results); + std::string error; + llvm::Error Err = llvm::Error::success(); + // Regex patterns + llvm::Regex removeDefinition("\\[\\#.*\\#\\]"); + llvm::Regex removeVariableName("(\\ |\\*)+(\\w+)(\\#\\>)"); + llvm::Regex removeTrailingSpace("\\ *(\\#\\>)"); + llvm::Regex removeTags("\\<\\#([^#>]*)\\#\\>"); + + // append cleaned results + for (auto& r : results) { + // remove the definition at the beginning (e.g., [#int#]) + r = removeDefinition.sub("", r, &error); + if (!error.empty()) { + Err = llvm::make_error(error, + llvm::inconvertibleErrorCode()); + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "Invalid substitution in CodeComplete"); + return; + } + // remove the variable name in <#type name#> + r = removeVariableName.sub("$1$3", r, &error); + if (!error.empty()) { + Err = llvm::make_error(error, + llvm::inconvertibleErrorCode()); + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "Invalid substitution in CodeComplete"); + return; + } + // remove unnecessary space at the end of <#type #> + r = removeTrailingSpace.sub("$1", r, &error); + if (!error.empty()) { + Err = llvm::make_error(error, + llvm::inconvertibleErrorCode()); + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "Invalid substitution in CodeComplete"); + return; + } + // remove <# #> to keep only the type + r = removeTags.sub("$1", r, &error); + if (!error.empty()) { + Err = llvm::make_error(error, + llvm::inconvertibleErrorCode()); + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "Invalid substitution in CodeComplete"); + return; + } + + if (r.find(code) == 0) + Results.push_back(r); + } + llvm::consumeError(std::move(Err)); +} + +} // namespace compat + +#endif // CPPINTEROP_USE_CLING + +#ifndef CPPINTEROP_USE_CLING + +#include "DynamicLibraryManager.h" +#include "clang/AST/Mangle.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Interpreter/Interpreter.h" +#include "clang/Interpreter/Value.h" + +#include "llvm/Support/Error.h" + +namespace compat { + +inline std::unique_ptr +createClangInterpreter(std::vector& args) { +#if CLANG_VERSION_MAJOR < 16 + auto ciOrErr = clang::IncrementalCompilerBuilder::create(args); +#else + auto has_arg = [](const char* x, llvm::StringRef match = "cuda") { + llvm::StringRef Arg = x; + Arg = Arg.trim().ltrim('-'); + return Arg == match; + }; + auto it = std::find_if(args.begin(), args.end(), has_arg); + std::vector gpu_args = {it, args.end()}; +#ifdef __APPLE__ + bool CudaEnabled = false; +#else + bool CudaEnabled = !gpu_args.empty(); +#endif + + clang::IncrementalCompilerBuilder CB; + CB.SetCompilerArgs({args.begin(), it}); + + std::unique_ptr DeviceCI; + if (CudaEnabled) { + // FIXME: Parametrize cuda-path and offload-arch. + CB.SetOffloadArch("sm_35"); + auto devOrErr = CB.CreateCudaDevice(); + if (!devOrErr) { + llvm::logAllUnhandledErrors(devOrErr.takeError(), llvm::errs(), + "Failed to create device compiler:"); + return nullptr; + } + DeviceCI = std::move(*devOrErr); + } + auto ciOrErr = CudaEnabled ? CB.CreateCudaHost() : CB.CreateCpp(); +#endif // CLANG_VERSION_MAJOR < 16 + if (!ciOrErr) { + llvm::logAllUnhandledErrors(ciOrErr.takeError(), llvm::errs(), + "Failed to build Incremental compiler:"); + return nullptr; + } +#if CLANG_VERSION_MAJOR < 16 + auto innerOrErr = clang::Interpreter::create(std::move(*ciOrErr)); +#else + (*ciOrErr)->LoadRequestedPlugins(); + if (CudaEnabled) + DeviceCI->LoadRequestedPlugins(); + auto innerOrErr = + CudaEnabled ? clang::Interpreter::createWithCUDA(std::move(*ciOrErr), + std::move(DeviceCI)) + : clang::Interpreter::create(std::move(*ciOrErr)); +#endif // CLANG_VERSION_MAJOR < 16 + + if (!innerOrErr) { + llvm::logAllUnhandledErrors(innerOrErr.takeError(), llvm::errs(), + "Failed to build Interpreter:"); + return nullptr; + } + if (CudaEnabled) { + if (auto Err = (*innerOrErr)->LoadDynamicLibrary("libcudart.so")) + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "Failed load libcudart.so runtime:"); + } + + return std::move(*innerOrErr); +} + +inline void maybeMangleDeclName(const clang::GlobalDecl& GD, + std::string& mangledName) { + // copied and adapted from CodeGen::CodeGenModule::getMangledName + + clang::NamedDecl* D = + llvm::cast(const_cast(GD.getDecl())); + std::unique_ptr mangleCtx; + mangleCtx.reset(D->getASTContext().createMangleContext()); + if (!mangleCtx->shouldMangleDeclName(D)) { + clang::IdentifierInfo* II = D->getIdentifier(); + assert(II && "Attempt to mangle unnamed decl."); + mangledName = II->getName().str(); + return; + } + + llvm::raw_string_ostream RawStr(mangledName); + +#if defined(_WIN32) + // MicrosoftMangle.cpp:954 calls llvm_unreachable when mangling Dtor_Comdat + if (llvm::isa(GD.getDecl()) && + GD.getDtorType() == clang::Dtor_Comdat) { + if (const clang::IdentifierInfo* II = D->getIdentifier()) + RawStr << II->getName(); + } else +#endif + mangleCtx->mangleName(GD, RawStr); + RawStr.flush(); +} + +// Clang 13 - Initial implementation of Interpreter and clang-repl +// Clang 14 - Add new Interpreter methods: getExecutionEngine, +// getSymbolAddress, getSymbolAddressFromLinkerName +// Clang 15 - Add new Interpreter methods: Undo +// Clang 18 - Add new Interpreter methods: CodeComplete + +inline llvm::orc::LLJIT* getExecutionEngine(clang::Interpreter& I) { +#if CLANG_VERSION_MAJOR >= 14 + auto* engine = &llvm::cantFail(I.getExecutionEngine()); + return const_cast(engine); +#else + assert(0 && "Not implemented in Clang <14!"); + return nullptr; +#endif +} + +inline llvm::Expected +getSymbolAddress(clang::Interpreter& I, llvm::StringRef IRName) { +#if CLANG_VERSION_MAJOR < 14 + assert(0 && "Not implemented in Clang <14!"); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Not implemented in Clang <14!"); +#endif // CLANG_VERSION_MAJOR < 14 + + auto AddrOrErr = I.getSymbolAddress(IRName); + if (llvm::Error Err = AddrOrErr.takeError()) + return std::move(Err); + return AddrOrErr->getValue(); +} + +inline llvm::Expected +getSymbolAddress(clang::Interpreter& I, clang::GlobalDecl GD) { + std::string MangledName; + compat::maybeMangleDeclName(GD, MangledName); + return getSymbolAddress(I, llvm::StringRef(MangledName)); +} + +inline llvm::Expected +getSymbolAddressFromLinkerName(clang::Interpreter& I, + llvm::StringRef LinkerName) { +#if CLANG_VERSION_MAJOR >= 14 + const auto& DL = getExecutionEngine(I)->getDataLayout(); + char GlobalPrefix = DL.getGlobalPrefix(); + std::string LinkerNameTmp(LinkerName); + if (GlobalPrefix != '\0') { + LinkerNameTmp = std::string(1, GlobalPrefix) + LinkerNameTmp; + } + auto AddrOrErr = I.getSymbolAddressFromLinkerName(LinkerNameTmp); + if (llvm::Error Err = AddrOrErr.takeError()) + return std::move(Err); + return AddrOrErr->getValue(); +#else + assert(0 && "Not implemented in Clang <14!"); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Not implemented in Clang <14!"); +#endif +} + +inline llvm::Error Undo(clang::Interpreter& I, unsigned N = 1) { +#if CLANG_VERSION_MAJOR >= 15 + return I.Undo(N); +#else + assert(0 && "Not implemented in Clang <15!"); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Not implemented in Clang <15!"); +#endif +} + +inline void codeComplete(std::vector& Results, + clang::Interpreter& I, const char* code, + unsigned complete_line = 1U, + unsigned complete_column = 1U) { +#if CLANG_VERSION_MAJOR >= 18 + // FIXME: We should match the invocation arguments of the main interpreter. + // That can affect the returned completion results. + auto CB = clang::IncrementalCompilerBuilder(); + auto CI = CB.CreateCpp(); + if (auto Err = CI.takeError()) { + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); + return; + } + auto Interp = clang::Interpreter::create(std::move(*CI)); + if (auto Err = Interp.takeError()) { + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); + return; + } + + std::vector results; + std::vector Comps; + clang::CompilerInstance* MainCI = (*Interp)->getCompilerInstance(); + auto CC = clang::ReplCodeCompleter(); + CC.codeComplete(MainCI, code, complete_line, complete_column, + I.getCompilerInstance(), results); + for (llvm::StringRef r : results) + if (r.find(CC.Prefix) == 0) + Results.push_back(r.str()); +#else + assert(false && "CodeCompletion API only available in Clang >= 18."); +#endif +} + +} // namespace compat + +#include "CppInterOpInterpreter.h" + +namespace Cpp { +namespace Cpp_utils = Cpp::utils; +} + +namespace compat { +using Interpreter = Cpp::Interpreter; +} + +#endif // CPPINTEROP_USE_REPL + +namespace compat { + +// Clang >= 14 change type name to string (spaces formatting problem) +#if CLANG_VERSION_MAJOR >= 14 +inline std::string FixTypeName(const std::string type_name) { + return type_name; +} +#else +inline std::string FixTypeName(const std::string type_name) { + std::string result = type_name; + size_t pos = 0; + while ((pos = result.find(" [", pos)) != std::string::npos) { + result.erase(pos, 1); + pos++; + } + return result; +} +#endif + +// Clang >= 16 change CLANG_LIBDIR_SUFFIX to CLANG_INSTALL_LIBDIR_BASENAME +#if CLANG_VERSION_MAJOR < 16 +#define CLANG_INSTALL_LIBDIR_BASENAME (llvm::Twine("lib") + CLANG_LIBDIR_SUFFIX) +#endif +inline std::string MakeResourceDir(llvm::StringRef Dir) { + llvm::SmallString<128> P(Dir); + llvm::sys::path::append(P, CLANG_INSTALL_LIBDIR_BASENAME, "clang", +#if CLANG_VERSION_MAJOR < 16 + CLANG_VERSION_STRING +#else + CLANG_VERSION_MAJOR_STRING +#endif + ); + return std::string(P.str()); +} + +// Clang >= 16 (=16 with Value patch) change castAs to converTo +#ifdef CPPINTEROP_USE_CLING +template inline T convertTo(cling::Value V) { + return V.castAs(); +} +#else // CLANG_REPL +template inline T convertTo(clang::Value V) { + return V.convertTo(); +} +#endif // CPPINTEROP_USE_CLING + +} // namespace compat + +#endif // CPPINTEROP_COMPATIBILITY_H diff --git a/interpreter/CppInterOp/lib/Interpreter/CppInterOp.cpp b/interpreter/CppInterOp/lib/Interpreter/CppInterOp.cpp new file mode 100755 index 0000000000000..586eda5e53d61 --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/CppInterOp.cpp @@ -0,0 +1,3576 @@ +//--------------------------------------------------------------------*- C++ -*- +// CLING - the C++ LLVM-based InterpreterG :) +// author: Vassil Vassilev +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#include "clang/Interpreter/CppInterOp.h" + +#include "Compatibility.h" + +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/Mangle.h" +#include "clang/AST/QualTypeNames.h" +#include "clang/AST/RecordLayout.h" +#include "clang/Basic/DiagnosticSema.h" +#include "clang/Basic/Linkage.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/Lookup.h" +#include "clang/Sema/Sema.h" +#if CLANG_VERSION_MAJOR >= 19 +#include "clang/Sema/Redeclaration.h" +#endif +#include "clang/Sema/TemplateDeduction.h" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Demangle/Demangle.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_os_ostream.h" + +#include +#include +#include +#include +#include +#include + +// Stream redirect. +#ifdef _WIN32 +#include +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +// For exec(). +#include +#define popen(x, y) (_popen(x, y)) +#define pclose (_pclose) +#endif +#else +#include +#include +#endif // WIN32 + +#include + +namespace Cpp { + + using namespace clang; + using namespace llvm; + using namespace std; + + // Flag to indicate ownership when an external interpreter instance is used. + static bool OwningSInterpreter = true; + static compat::Interpreter* sInterpreter = nullptr; + // Valgrind complains about __cxa_pure_virtual called when deleting + // llvm::SectionMemoryManager::~SectionMemoryManager as part of the dtor chain + // of the Interpreter. + // This might fix the issue https://reviews.llvm.org/D107087 + // FIXME: For now we just leak the Interpreter. + struct InterpDeleter { + ~InterpDeleter() = default; + } Deleter; + + static compat::Interpreter& getInterp() { + assert(sInterpreter && + "Interpreter instance must be set before calling this!"); + return *sInterpreter; + } + static clang::Sema& getSema() { return getInterp().getCI()->getSema(); } + static clang::ASTContext& getASTContext() { return getSema().getASTContext(); } + +#define DEBUG_TYPE "jitcall" + bool JitCall::AreArgumentsValid(void* result, ArgList args, + void* self) const { + bool Valid = true; + if (Cpp::IsConstructor(m_FD)) { + assert(result && "Must pass the location of the created object!"); + Valid &= (bool)result; + } + if (Cpp::GetFunctionRequiredArgs(m_FD) > args.m_ArgSize) { + assert(0 && "Must pass at least the minimal number of args!"); + Valid = false; + } + if (args.m_ArgSize) { + assert(args.m_Args != nullptr && "Must pass an argument list!"); + Valid &= (bool)args.m_Args; + } + if (!Cpp::IsConstructor(m_FD) && !Cpp::IsDestructor(m_FD) && + Cpp::IsMethod(m_FD) && !Cpp::IsStaticMethod(m_FD)) { + assert(self && "Must pass the pointer to object"); + Valid &= (bool)self; + } + const auto* FD = cast((const Decl*)m_FD); + if (!FD->getReturnType()->isVoidType() && !result) { + assert(0 && "We are discarding the return type of the function!"); + Valid = false; + } + assert(m_Kind != kDestructorCall && "Wrong overload!"); + Valid &= m_Kind != kDestructorCall; + return Valid; + } + + void JitCall::ReportInvokeStart(void* result, ArgList args, void* self) const{ + std::string Name; + llvm::raw_string_ostream OS(Name); + auto FD = (const FunctionDecl*) 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"; + ); + } + + void JitCall::ReportInvokeStart(void* object, unsigned long nary, + int withFree) const { + std::string Name; + llvm::raw_string_ostream OS(Name); + auto FD = (const FunctionDecl*) m_FD; + FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), + /*Qualified=*/true); + LLVM_DEBUG(dbgs() << "Finish '" << Name + << "', compiled at: " << (void*) m_DestructorCall); + } + +#undef DEBUG_TYPE + + std::string GetVersion() { + const char* const VERSION = CPPINTEROP_VERSION; + std::string fullVersion = "CppInterOp version"; + fullVersion += VERSION; + fullVersion += "\n (based on " +#ifdef CPPINTEROP_USE_CLING + "cling "; +#else + "clang-repl"; +#endif // CPPINTEROP_USE_CLING + return fullVersion + "[" + clang::getClangFullVersion() + "])\n"; + } + + std::string Demangle(const std::string& mangled_name) { +#if CLANG_VERSION_MAJOR > 16 +#ifdef _WIN32 + std::string demangle = microsoftDemangle(mangled_name, nullptr, nullptr); +#else + std::string demangle = itaniumDemangle(mangled_name); +#endif +#else +#ifdef _WIN32 + std::string demangle = microsoftDemangle(mangled_name.c_str(), nullptr, + nullptr, nullptr, nullptr); +#else + std::string demangle = + itaniumDemangle(mangled_name.c_str(), nullptr, nullptr, nullptr); +#endif +#endif + return demangle; + } + + void EnableDebugOutput(bool value/* =true*/) { + llvm::DebugFlag = value; + } + + bool IsDebugOutputEnabled() { + return llvm::DebugFlag; + } + + bool IsAggregate(TCppScope_t scope) { + Decl *D = static_cast(scope); + + // Aggregates are only arrays or tag decls. + if (ValueDecl *ValD = dyn_cast(D)) + if (ValD->getType()->isArrayType()) + return true; + + // struct, class, union + if (CXXRecordDecl *CXXRD = dyn_cast(D)) + return CXXRD->isAggregate(); + + return false; + } + + bool IsNamespace(TCppScope_t scope) { + Decl *D = static_cast(scope); + return isa(D); + } + + bool IsClass(TCppScope_t scope) { + Decl *D = static_cast(scope); + return isa(D); + } + + bool IsFunctionPointerType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isFunctionPointerType(); + } + + bool IsClassPolymorphic(TCppScope_t klass) { + Decl* D = static_cast(klass); + if (auto* CXXRD = llvm::dyn_cast(D)) + if (auto* CXXRDD = CXXRD->getDefinition()) + return CXXRDD->isPolymorphic(); + return false; + } + + static SourceLocation GetValidSLoc(Sema& semaRef) { + auto& SM = semaRef.getSourceManager(); + return SM.getLocForStartOfFile(SM.getMainFileID()); + } + + // See TClingClassInfo::IsLoaded + bool IsComplete(TCppScope_t scope) { + if (!scope) + return false; + + Decl *D = static_cast(scope); + + if (isa(D)) { + QualType QT = QualType::getFromOpaquePtr(GetTypeFromScope(scope)); + clang::Sema &S = getSema(); + SourceLocation fakeLoc = GetValidSLoc(S); +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif // CPPINTEROP_USE_CLING + return S.isCompleteType(fakeLoc, QT); + } + + if (auto *CXXRD = dyn_cast(D)) + return CXXRD->hasDefinition(); + else if (auto *TD = dyn_cast(D)) + return TD->getDefinition(); + + // Everything else is considered complete. + return true; + } + + size_t SizeOf(TCppScope_t scope) { + assert (scope); + if (!IsComplete(scope)) + return 0; + + if (auto *RD = dyn_cast(static_cast(scope))) { + ASTContext &Context = RD->getASTContext(); + const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD); + return Layout.getSize().getQuantity(); + } + + return 0; + } + + bool IsBuiltin(TCppType_t type) { + QualType Ty = QualType::getFromOpaquePtr(type); + if (Ty->isBuiltinType() || Ty->isAnyComplexType()) + return true; + // FIXME: Figure out how to avoid the string comparison. + return llvm::StringRef(Ty.getAsString()).contains("complex"); + } + + bool IsTemplate(TCppScope_t handle) { + auto *D = (clang::Decl *)handle; + return llvm::isa_and_nonnull(D); + } + + bool IsTemplateSpecialization(TCppScope_t handle) { + auto *D = (clang::Decl *)handle; + return llvm::isa_and_nonnull(D); + } + + bool IsTypedefed(TCppScope_t handle) { + auto *D = (clang::Decl *)handle; + return llvm::isa_and_nonnull(D); + } + + bool IsAbstract(TCppType_t klass) { + auto *D = (clang::Decl *)klass; + if (auto *CXXRD = llvm::dyn_cast_or_null(D)) + return CXXRD->isAbstract(); + + return false; + } + + bool IsEnumScope(TCppScope_t handle) { + auto *D = (clang::Decl *)handle; + return llvm::isa_and_nonnull(D); + } + + bool IsEnumConstant(TCppScope_t handle) { + auto *D = (clang::Decl *)handle; + return llvm::isa_and_nonnull(D); + } + + bool IsEnumType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isEnumeralType(); + } + + static bool isSmartPointer(const RecordType* RT) { + auto IsUseCountPresent = [](const RecordDecl *Record) { + ASTContext &C = Record->getASTContext(); + return !Record->lookup(&C.Idents.get("use_count")).empty(); + }; + auto IsOverloadedOperatorPresent = [](const RecordDecl *Record, + OverloadedOperatorKind Op) { + ASTContext &C = Record->getASTContext(); + DeclContextLookupResult Result = + Record->lookup(C.DeclarationNames.getCXXOperatorName(Op)); + return !Result.empty(); + }; + + const RecordDecl *Record = RT->getDecl(); + if (IsUseCountPresent(Record)) + return true; + + bool foundStarOperator = IsOverloadedOperatorPresent(Record, OO_Star); + bool foundArrowOperator = IsOverloadedOperatorPresent(Record, OO_Arrow); + if (foundStarOperator && foundArrowOperator) + return true; + + const CXXRecordDecl *CXXRecord = dyn_cast(Record); + if (!CXXRecord) + return false; + + auto FindOverloadedOperators = [&](const CXXRecordDecl *Base) { + // If we find use_count, we are done. + if (IsUseCountPresent(Base)) + return false; // success. + if (!foundStarOperator) + foundStarOperator = IsOverloadedOperatorPresent(Base, OO_Star); + if (!foundArrowOperator) + foundArrowOperator = IsOverloadedOperatorPresent(Base, OO_Arrow); + if (foundStarOperator && foundArrowOperator) + return false; // success. + return true; + }; + + return !CXXRecord->forallBases(FindOverloadedOperators); + } + + bool IsSmartPtrType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + if (const RecordType *RT = QT->getAs()) { + // Add quick checks for the std smart prts to cover most of the cases. + std::string typeString = GetTypeAsString(type); + llvm::StringRef tsRef(typeString); + if (tsRef.starts_with("std::unique_ptr") || + tsRef.starts_with("std::shared_ptr") || + tsRef.starts_with("std::weak_ptr")) + return true; + return isSmartPointer(RT); + } + return false; + } + + TCppType_t GetIntegerTypeFromEnumScope(TCppScope_t handle) { + auto *D = (clang::Decl *)handle; + if (auto *ED = llvm::dyn_cast_or_null(D)) { + return ED->getIntegerType().getAsOpaquePtr(); + } + + return 0; + } + + TCppType_t GetIntegerTypeFromEnumType(TCppType_t enum_type) { + if (!enum_type) + return nullptr; + + QualType QT = QualType::getFromOpaquePtr(enum_type); + if (auto *ET = QT->getAs()) + return ET->getDecl()->getIntegerType().getAsOpaquePtr(); + + return nullptr; + } + + std::vector GetEnumConstants(TCppScope_t handle) { + auto *D = (clang::Decl *)handle; + + if (auto *ED = llvm::dyn_cast_or_null(D)) { + std::vector enum_constants; + for (auto *ECD : ED->enumerators()) { + enum_constants.push_back((TCppScope_t) ECD); + } + + return enum_constants; + } + + return {}; + } + + TCppType_t GetEnumConstantType(TCppScope_t handle) { + if (!handle) + return nullptr; + + auto *D = (clang::Decl *)handle; + if (auto *ECD = llvm::dyn_cast(D)) + return ECD->getType().getAsOpaquePtr(); + + return 0; + } + + TCppIndex_t GetEnumConstantValue(TCppScope_t handle) { + auto *D = (clang::Decl *)handle; + if (auto *ECD = llvm::dyn_cast_or_null(D)) { + const llvm::APSInt& Val = ECD->getInitVal(); + return Val.getExtValue(); + } + return 0; + } + + size_t GetSizeOfType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + if (const TagType *TT = QT->getAs()) + return SizeOf(TT->getDecl()); + + // FIXME: Can we get the size of a non-tag type? + auto TI = getSema().getASTContext().getTypeInfo(QT); + size_t TypeSize = TI.Width; + return TypeSize/8; + } + + bool IsVariable(TCppScope_t scope) { + auto *D = (clang::Decl *)scope; + return llvm::isa_and_nonnull(D); + } + + std::string GetName(TCppType_t klass) { + auto *D = (clang::NamedDecl *) klass; + + if (llvm::isa_and_nonnull(D)) { + return ""; + } + + if (auto *ND = llvm::dyn_cast_or_null(D)) { + return ND->getNameAsString(); + } + + return ""; + } + + std::string GetCompleteName(TCppType_t klass) + { + auto &C = getSema().getASTContext(); + auto *D = (Decl *) klass; + + if (auto *ND = llvm::dyn_cast_or_null(D)) { + if (auto *TD = llvm::dyn_cast(ND)) { + std::string type_name; + QualType QT = C.getTagDeclType(TD); + PrintingPolicy Policy = C.getPrintingPolicy(); + Policy.SuppressUnwrittenScope = true; + Policy.SuppressScope = true; + Policy.AnonymousTagLocations = false; + QT.getAsStringInternal(type_name, Policy); + + return type_name; + } + + return ND->getNameAsString(); + } + + if (llvm::isa_and_nonnull(D)) { + return ""; + } + + return ""; + } + + std::string GetQualifiedName(TCppType_t klass) + { + auto *D = (Decl *) klass; + if (auto *ND = llvm::dyn_cast_or_null(D)) { + return ND->getQualifiedNameAsString(); + } + + if (llvm::isa_and_nonnull(D)) { + return ""; + } + + return ""; + } + + //FIXME: Figure out how to merge with GetCompleteName. + std::string GetQualifiedCompleteName(TCppType_t klass) + { + auto &C = getSema().getASTContext(); + auto *D = (Decl *) klass; + + if (auto *ND = llvm::dyn_cast_or_null(D)) { + if (auto *TD = llvm::dyn_cast(ND)) { + std::string type_name; + QualType QT = C.getTagDeclType(TD); + QT.getAsStringInternal(type_name, C.getPrintingPolicy()); + + return type_name; + } + + return ND->getQualifiedNameAsString(); + } + + if (llvm::isa_and_nonnull(D)) { + return ""; + } + + return ""; + } + + std::vector GetUsingNamespaces(TCppScope_t scope) { + auto *D = (clang::Decl *) scope; + + if (auto *DC = llvm::dyn_cast_or_null(D)) { + std::vector namespaces; + for (auto UD : DC->using_directives()) { + namespaces.push_back((TCppScope_t) UD->getNominatedNamespace()); + } + return namespaces; + } + + return {}; + } + + TCppScope_t GetGlobalScope() + { + return getSema().getASTContext().getTranslationUnitDecl()->getFirstDecl(); + } + + static Decl *GetScopeFromType(QualType QT) { + if (auto* Type = QT.getCanonicalType().getTypePtrOrNull()) { + Type = Type->getPointeeOrArrayElementType(); + Type = Type->getUnqualifiedDesugaredType(); + if (auto *ET = llvm::dyn_cast(Type)) + return ET->getDecl(); + if (auto* FnType = llvm::dyn_cast(Type)) + Type = const_cast(FnType->getReturnType().getTypePtr()); + return Type->getAsCXXRecordDecl(); + } + return 0; + } + + TCppScope_t GetScopeFromType(TCppType_t type) + { + QualType QT = QualType::getFromOpaquePtr(type); + return (TCppScope_t) GetScopeFromType(QT); + } + + static clang::Decl* GetUnderlyingScope(clang::Decl * D) { + if (auto *TND = dyn_cast_or_null(D)) { + if (auto* Scope = GetScopeFromType(TND->getUnderlyingType())) + D = Scope; + } else if (auto* USS = dyn_cast_or_null(D)) { + if (auto* Scope = USS->getTargetDecl()) + D = Scope; + } + + return D; + } + + TCppScope_t GetUnderlyingScope(TCppScope_t scope) { + if (!scope) + return 0; + return GetUnderlyingScope((clang::Decl *) scope); + } + + TCppScope_t GetScope(const std::string &name, TCppScope_t parent) + { + // FIXME: GetScope should be replaced by a general purpose lookup + // and filter function. The function should be like GetNamed but + // also take in a filter parameter which determines which results + // to pass back + if (name == "") + return GetGlobalScope(); + + auto *ND = (NamedDecl*)GetNamed(name, parent); + + if (!ND || ND == (NamedDecl *) -1) + return 0; + + if (llvm::isa(ND) || + llvm::isa(ND) || + llvm::isa(ND) || + llvm::isa(ND)) + return (TCppScope_t)(ND->getCanonicalDecl()); + + return 0; + } + + TCppScope_t GetScopeFromCompleteName(const std::string &name) + { + std::string delim = "::"; + size_t start = 0; + size_t end = name.find(delim); + TCppScope_t curr_scope = 0; + while (end != std::string::npos) + { + curr_scope = GetScope(name.substr(start, end - start), curr_scope); + start = end + delim.length(); + end = name.find(delim, start); + } + return GetScope(name.substr(start, end), curr_scope); + } + + TCppScope_t GetNamed(const std::string &name, + TCppScope_t parent /*= nullptr*/) + { + clang::DeclContext *Within = 0; + if (parent) { + auto *D = (clang::Decl *)parent; + D = GetUnderlyingScope(D); + Within = llvm::dyn_cast(D); + } + + auto *ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); + if (ND && ND != (clang::NamedDecl*) -1) { + return (TCppScope_t)(ND->getCanonicalDecl()); + } + + return 0; + } + + TCppScope_t GetParentScope(TCppScope_t scope) + { + auto *D = (clang::Decl *) scope; + + if (llvm::isa_and_nonnull(D)) { + return 0; + } + auto *ParentDC = D->getDeclContext(); + + if (!ParentDC) + return 0; + + auto* P = clang::Decl::castFromDeclContext(ParentDC)->getCanonicalDecl(); + + if (auto* TU = llvm::dyn_cast_or_null(P)) + return (TCppScope_t)TU->getFirstDecl(); + + return (TCppScope_t)P; + } + + TCppIndex_t GetNumBases(TCppScope_t klass) + { + auto *D = (Decl *) klass; + + if (auto *CXXRD = llvm::dyn_cast_or_null(D)) { + if (CXXRD->hasDefinition()) + return CXXRD->getNumBases(); + } + + return 0; + } + + TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase) + { + auto *D = (Decl *) klass; + auto *CXXRD = llvm::dyn_cast_or_null(D); + if (!CXXRD || CXXRD->getNumBases() <= ibase) return 0; + + auto type = (CXXRD->bases_begin() + ibase)->getType(); + if (auto RT = type->getAs()) + return (TCppScope_t)RT->getDecl(); + + return 0; + } + + // FIXME: Consider dropping this interface as it seems the same as + // IsTypeDerivedFrom. + bool IsSubclass(TCppScope_t derived, TCppScope_t base) + { + if (derived == base) + return true; + + if (!derived || !base) + return false; + + auto *derived_D = (clang::Decl *) derived; + auto *base_D = (clang::Decl *) base; + + if (!isa(derived_D) || !isa(base_D)) + return false; + + auto Derived = cast(derived_D); + auto Base = cast(base_D); + return IsTypeDerivedFrom(GetTypeFromScope(Derived), + GetTypeFromScope(Base)); + } + + // Copied from VTableBuilder.cpp + // This is an internal helper function for the CppInterOp library (as evident + // by the 'static' declaration), while the similar GetBaseClassOffset() + // function below is exposed to library users. + static unsigned ComputeBaseOffset(const ASTContext &Context, + const CXXRecordDecl *DerivedRD, + const CXXBasePath &Path) { + CharUnits NonVirtualOffset = CharUnits::Zero(); + + unsigned NonVirtualStart = 0; + const CXXRecordDecl *VirtualBase = nullptr; + + // First, look for the virtual base class. + for (int I = Path.size(), E = 0; I != E; --I) { + const CXXBasePathElement &Element = Path[I - 1]; + + if (Element.Base->isVirtual()) { + NonVirtualStart = I; + QualType VBaseType = Element.Base->getType(); + VirtualBase = VBaseType->getAsCXXRecordDecl(); + break; + } + } + + // Now compute the non-virtual offset. + for (unsigned I = NonVirtualStart, E = Path.size(); I != E; ++I) { + const CXXBasePathElement &Element = Path[I]; + + // Check the base class offset. + const ASTRecordLayout &Layout = Context.getASTRecordLayout(Element.Class); + + const CXXRecordDecl *Base = Element.Base->getType()->getAsCXXRecordDecl(); + + NonVirtualOffset += Layout.getBaseClassOffset(Base); + } + + // FIXME: This should probably use CharUnits or something. Maybe we should + // even change the base offsets in ASTRecordLayout to be specified in + // CharUnits. + //return BaseOffset(DerivedRD, VirtuaBose, aBlnVirtualOffset); + if (VirtualBase) { + const ASTRecordLayout &Layout = Context.getASTRecordLayout(DerivedRD); + CharUnits VirtualOffset = Layout.getVBaseClassOffset(VirtualBase); + return (NonVirtualOffset + VirtualOffset).getQuantity(); + } + return NonVirtualOffset.getQuantity(); + + } + + int64_t GetBaseClassOffset(TCppScope_t derived, TCppScope_t base) { + if (base == derived) + return 0; + + assert(derived || base); + + auto *DD = (Decl *) derived; + auto *BD = (Decl *) base; + if (!isa(DD) || !isa(BD)) + return -1; + CXXRecordDecl *DCXXRD = cast(DD); + CXXRecordDecl *BCXXRD = cast(BD); + CXXBasePaths Paths(/*FindAmbiguities=*/false, /*RecordPaths=*/true, + /*DetectVirtual=*/false); + DCXXRD->isDerivedFrom(BCXXRD, Paths); + + // FIXME: We might want to cache these requests as they seem expensive. + return ComputeBaseOffset(getSema().getASTContext(), DCXXRD, Paths.front()); + } + + template + static void GetClassDecls(TCppScope_t klass, + std::vector& methods) { + if (!klass) + return; + + auto* D = (clang::Decl*)klass; + + if (auto* TD = dyn_cast(D)) + D = GetScopeFromType(TD->getUnderlyingType()); + + if (!D || !isa(D)) + return; + + auto* CXXRD = dyn_cast(D); +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif // CPPINTEROP_USE_CLING + getSema().ForceDeclarationOfImplicitMembers(CXXRD); + for (Decl* DI : CXXRD->decls()) { + if (auto* MD = dyn_cast(DI)) + methods.push_back(MD); + else if (auto* USD = dyn_cast(DI)) + if (auto* MD = dyn_cast(USD->getTargetDecl())) + methods.push_back(MD); + } + } + + void GetClassMethods(TCppScope_t klass, + std::vector& methods) { + GetClassDecls(klass, methods); + } + + void GetFunctionTemplatedDecls(TCppScope_t klass, + std::vector& methods) { + GetClassDecls(klass, methods); + } + + bool HasDefaultConstructor(TCppScope_t scope) { + auto *D = (clang::Decl *) scope; + + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) + return CXXRD->hasDefaultConstructor(); + + return false; + } + + TCppFunction_t GetDefaultConstructor(TCppScope_t scope) { + if (!HasDefaultConstructor(scope)) + return nullptr; + + auto *CXXRD = (clang::CXXRecordDecl*)scope; + return getSema().LookupDefaultConstructor(CXXRD); + } + + TCppFunction_t GetDestructor(TCppScope_t scope) { + auto *D = (clang::Decl *) scope; + + if (auto *CXXRD = llvm::dyn_cast_or_null(D)) { + getSema().ForceDeclarationOfImplicitMembers(CXXRD); + return CXXRD->getDestructor(); + } + + return 0; + } + + void DumpScope(TCppScope_t scope) + { + auto *D = (clang::Decl *) scope; + D->dump(); + } + + std::vector GetFunctionsUsingName( + TCppScope_t scope, const std::string& name) + { + auto *D = (Decl *) scope; + + if (!scope || name.empty()) + return {}; + + D = GetUnderlyingScope(D); + + std::vector funcs; + llvm::StringRef Name(name); + auto &S = getSema(); + DeclarationName DName = &getASTContext().Idents.get(name); + clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, + For_Visible_Redeclaration); + + Cpp_utils::Lookup::Named(&S, R, Decl::castToDeclContext(D)); + + if (R.empty()) + return funcs; + + R.resolveKind(); + + for (auto *Found : R) + if (llvm::isa(Found)) + funcs.push_back(Found); + + return funcs; + } + + TCppType_t GetFunctionReturnType(TCppFunction_t func) + { + auto *D = (clang::Decl *) func; + if (auto* FD = llvm::dyn_cast_or_null(D)) + return FD->getReturnType().getAsOpaquePtr(); + + if (auto* FD = llvm::dyn_cast_or_null(D)) + return (FD->getTemplatedDecl())->getReturnType().getAsOpaquePtr(); + + return 0; + } + + TCppIndex_t GetFunctionNumArgs(TCppFunction_t func) + { + auto *D = (clang::Decl *) func; + if (auto* FD = llvm::dyn_cast_or_null(D)) + return FD->getNumParams(); + + if (auto* FD = llvm::dyn_cast_or_null(D)) + return (FD->getTemplatedDecl())->getNumParams(); + + return 0; + } + + TCppIndex_t GetFunctionRequiredArgs(TCppConstFunction_t func) + { + const auto* D = static_cast(func); + if (auto* FD = llvm::dyn_cast_or_null(D)) + return FD->getMinRequiredArguments(); + + if (auto* FD = llvm::dyn_cast_or_null(D)) + return (FD->getTemplatedDecl())->getMinRequiredArguments(); + + return 0; + } + + TCppType_t GetFunctionArgType(TCppFunction_t func, TCppIndex_t iarg) + { + auto *D = (clang::Decl *) func; + + if (auto *FD = llvm::dyn_cast_or_null(D)) { + if (iarg < FD->getNumParams()) { + auto *PVD = FD->getParamDecl(iarg); + return PVD->getOriginalType().getAsOpaquePtr(); + } + } + + return 0; + } + + std::string GetFunctionSignature(TCppFunction_t func) { + if (!func) + return ""; + + auto *D = (clang::Decl *) func; + if (auto *FD = llvm::dyn_cast(D)) { + std::string Signature; + raw_string_ostream SS(Signature); + PrintingPolicy Policy = getASTContext().getPrintingPolicy(); + // Skip printing the body + Policy.TerseOutput = true; + Policy.FullyQualifiedName = true; + Policy.SuppressDefaultTemplateArgs = false; + FD->print(SS, Policy); + SS.flush(); + return Signature; + } + + return ""; + } + + // Internal functions that are not needed outside the library are + // encompassed in an anonymous namespace as follows. + namespace { + bool IsTemplatedFunction(Decl *D) { + if (llvm::isa_and_nonnull(D)) + return true; + + if (auto *FD = llvm::dyn_cast_or_null(D)) { + auto TK = FD->getTemplatedKind(); + return TK == FunctionDecl::TemplatedKind:: + TK_FunctionTemplateSpecialization + || TK == FunctionDecl::TemplatedKind:: + TK_DependentFunctionTemplateSpecialization + || TK == FunctionDecl::TemplatedKind::TK_FunctionTemplate; + } + + return false; + } + } + + bool IsFunctionDeleted(TCppConstFunction_t function) { + const auto* FD = + cast(static_cast(function)); + return FD->isDeleted(); + } + + bool IsTemplatedFunction(TCppFunction_t func) + { + auto *D = (Decl *) func; + return IsTemplatedFunction(D); + } + + bool ExistsFunctionTemplate(const std::string& name, + TCppScope_t parent) + { + DeclContext *Within = 0; + if (parent) { + auto* D = (Decl*)parent; + Within = llvm::dyn_cast(D); + } + + auto *ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); + + if ((intptr_t) ND == (intptr_t) 0) + return false; + + if ((intptr_t) ND != (intptr_t) -1) + return IsTemplatedFunction(ND); + + // FIXME: Cycle through the Decls and check if there is a templated function + return true; + } + + void GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, + std::vector& funcs) { + + auto* D = (Decl*)parent; + + if (!parent || name.empty()) + return; + + D = GetUnderlyingScope(D); + + llvm::StringRef Name(name); + auto& S = getSema(); + DeclarationName DName = &getASTContext().Idents.get(name); + clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, + For_Visible_Redeclaration); + + Cpp_utils::Lookup::Named(&S, R, Decl::castToDeclContext(D)); + + if (R.empty()) + return; + + R.resolveKind(); + + for (auto* Found : R) + if (llvm::isa(Found)) + funcs.push_back(Found); + } + + TCppFunction_t + BestTemplateFunctionMatch(const std::vector& candidates, + const std::vector& explicit_types, + const std::vector& arg_types) { + + for (const auto& candidate : candidates) { + auto* TFD = (FunctionTemplateDecl*)candidate; + clang::TemplateParameterList* tpl = TFD->getTemplateParameters(); + + // template parameter size does not match + if (tpl->size() < explicit_types.size()) + continue; + + // right now uninstantiated functions give template typenames instead of + // actual types. We make this match solely based on count + + const FunctionDecl* func = TFD->getTemplatedDecl(); + +#ifdef CPPINTEROP_USE_CLING + if (func->getNumParams() > arg_types.size()) + continue; +#else // CLANG_REPL + if (func->getMinRequiredArguments() > arg_types.size()) + continue; +#endif + + // TODO(aaronj0) : first score based on the type similarity before forcing + // instantiation. + + TCppFunction_t instantiated = + InstantiateTemplate(candidate, arg_types.data(), arg_types.size()); + if (instantiated) + return instantiated; + + // Force the instantiation with template params in case of no args + // maybe steer instantiation better with arg set returned from + // TemplateProxy? + instantiated = InstantiateTemplate(candidate, explicit_types.data(), + explicit_types.size()); + if (instantiated) + return instantiated; + + // join explicit and arg_types + std::vector total_arg_set; + total_arg_set.reserve(explicit_types.size() + arg_types.size()); + total_arg_set.insert(total_arg_set.end(), explicit_types.begin(), + explicit_types.end()); + total_arg_set.insert(total_arg_set.end(), arg_types.begin(), + arg_types.end()); + + instantiated = InstantiateTemplate(candidate, total_arg_set.data(), + total_arg_set.size()); + if (instantiated) + return instantiated; + } + return nullptr; + } + + // Gets the AccessSpecifier of the function and checks if it is equal to + // the provided AccessSpecifier. + bool CheckMethodAccess(TCppFunction_t method, AccessSpecifier AS) + { + auto *D = (Decl *) method; + if (auto *CXXMD = llvm::dyn_cast_or_null(D)) { + return CXXMD->getAccess() == AS; + } + + return false; + } + + bool IsMethod(TCppConstFunction_t method) + { + return dyn_cast_or_null( + static_cast(method)); + } + + bool IsPublicMethod(TCppFunction_t method) + { + return CheckMethodAccess(method, AccessSpecifier::AS_public); + } + + bool IsProtectedMethod(TCppFunction_t method) { + return CheckMethodAccess(method, AccessSpecifier::AS_protected); + } + + bool IsPrivateMethod(TCppFunction_t method) + { + return CheckMethodAccess(method, AccessSpecifier::AS_private); + } + + bool IsConstructor(TCppConstFunction_t method) + { + const auto* D = static_cast(method); + return llvm::isa_and_nonnull(D); + } + + bool IsDestructor(TCppConstFunction_t method) + { + const auto* D = static_cast(method); + return llvm::isa_and_nonnull(D); + } + + bool IsStaticMethod(TCppConstFunction_t method) { + const auto* D = static_cast(method); + if (auto *CXXMD = llvm::dyn_cast_or_null(D)) { + return CXXMD->isStatic(); + } + + return false; + } + + TCppFuncAddr_t GetFunctionAddress(const char* mangled_name) { + auto& I = getInterp(); + auto FDAorErr = compat::getSymbolAddress(I, mangled_name); + if (llvm::Error Err = FDAorErr.takeError()) + llvm::consumeError(std::move(Err)); // nullptr if missing + else + return llvm::jitTargetAddressToPointer(*FDAorErr); + + return nullptr; + } + + TCppFuncAddr_t GetFunctionAddress(TCppFunction_t method) + { + auto *D = (Decl *) method; + + const auto get_mangled_name = [](FunctionDecl* FD) { + auto MangleCtxt = getASTContext().createMangleContext(); + + if (!MangleCtxt->shouldMangleDeclName(FD)) { + return FD->getNameInfo().getName().getAsString(); + } + + std::string mangled_name; + llvm::raw_string_ostream ostream(mangled_name); + + MangleCtxt->mangleName(FD, ostream); + + ostream.flush(); + delete MangleCtxt; + + return mangled_name; + }; + + if (auto* FD = llvm::dyn_cast_or_null(D)) + return GetFunctionAddress(get_mangled_name(FD).c_str()); + + return 0; + } + + bool IsVirtualMethod(TCppFunction_t method) { + auto *D = (Decl *) method; + if (auto *CXXMD = llvm::dyn_cast_or_null(D)) { + return CXXMD->isVirtual(); + } + + return false; + } + + void GetDatamembers(TCppScope_t scope, + std::vector& datamembers) { + auto *D = (Decl *) scope; + + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { + getSema().ForceDeclarationOfImplicitMembers(CXXRD); + + llvm::SmallVector stack_begin; + llvm::SmallVector stack_end; + stack_begin.push_back(CXXRD->decls_begin()); + stack_end.push_back(CXXRD->decls_end()); + while (!stack_begin.empty()) { + if (stack_begin.back() == stack_end.back()) { + stack_begin.pop_back(); + stack_end.pop_back(); + continue; + } + Decl* D = *(stack_begin.back()); + if (auto* FD = llvm::dyn_cast(D)) { + if (FD->isAnonymousStructOrUnion()) { + if (const auto* RT = FD->getType()->getAs()) { + if (auto* CXXRD = llvm::dyn_cast(RT->getDecl())) { + stack_begin.back()++; + stack_begin.push_back(CXXRD->decls_begin()); + stack_end.push_back(CXXRD->decls_end()); + continue; + } + } + } + datamembers.push_back((TCppScope_t)D); + + } else if (auto* USD = llvm::dyn_cast(D)) { + if (llvm::isa(USD->getTargetDecl())) + datamembers.push_back(USD); + } + stack_begin.back()++; + } + } + } + + void GetStaticDatamembers(TCppScope_t scope, + std::vector& datamembers) { + GetClassDecls(scope, datamembers); + } + + void GetEnumConstantDatamembers(TCppScope_t scope, + std::vector& datamembers, + bool include_enum_class) { + std::vector EDs; + GetClassDecls(scope, EDs); + for (TCppScope_t i : EDs) { + auto* ED = static_cast(i); + + bool is_class_tagged = ED->isScopedUsingClassTag(); + if (is_class_tagged && !include_enum_class) + continue; + + std::copy(ED->enumerator_begin(), ED->enumerator_end(), + std::back_inserter(datamembers)); + } + } + + TCppScope_t LookupDatamember(const std::string& name, TCppScope_t parent) { + clang::DeclContext *Within = 0; + if (parent) { + auto *D = (clang::Decl *)parent; + Within = llvm::dyn_cast(D); + } + + auto *ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); + if (ND && ND != (clang::NamedDecl*) -1) { + if (llvm::isa_and_nonnull(ND)) { + return (TCppScope_t)ND; + } + } + + return 0; + } + + TCppType_t GetVariableType(TCppScope_t var) { + auto* D = static_cast(var); + + if (auto DD = llvm::dyn_cast_or_null(D)) { + QualType QT = DD->getType(); + + // Check if the type is a typedef type + if (QT->isTypedefNameType()) { + return QT.getAsOpaquePtr(); + } + + // Else, return the canonical type + QT = QT.getCanonicalType(); + return QT.getAsOpaquePtr(); + } + + if (auto* ECD = llvm::dyn_cast_or_null(D)) + return ECD->getType().getAsOpaquePtr(); + + return 0; + } + + intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, + CXXRecordDecl* BaseCXXRD) { + if (!D) + return 0; + + auto& C = I.getSema().getASTContext(); + + if (auto* FD = llvm::dyn_cast(D)) { + clang::RecordDecl* FieldParentRecordDecl = FD->getParent(); + intptr_t offset = + C.toCharUnitsFromBits(C.getFieldOffset(FD)).getQuantity(); + while (FieldParentRecordDecl->isAnonymousStructOrUnion()) { + clang::RecordDecl* anon = FieldParentRecordDecl; + FieldParentRecordDecl = llvm::dyn_cast(anon->getParent()); + for (auto F = FieldParentRecordDecl->field_begin(); + F != FieldParentRecordDecl->field_end(); ++F) { + const auto* RT = F->getType()->getAs(); + if (!RT) + continue; + if (anon == RT->getDecl()) { + FD = *F; + break; + } + } + offset += C.toCharUnitsFromBits(C.getFieldOffset(FD)).getQuantity(); + } + if (BaseCXXRD && BaseCXXRD != FieldParentRecordDecl) { + // FieldDecl FD belongs to some class C, but the base class BaseCXXRD is + // not C. That means BaseCXXRD derives from C. Offset needs to be + // calculated for Derived class + + // Depth first Search is performed to the class that declears FD from + // the base class + std::vector stack; + std::map direction; + stack.push_back(BaseCXXRD); + while (!stack.empty()) { + CXXRecordDecl* RD = stack.back(); + stack.pop_back(); + size_t num_bases = GetNumBases(RD); + bool flag = false; + for (size_t i = 0; i < num_bases; i++) { + auto* CRD = static_cast(GetBaseClass(RD, i)); + direction[CRD] = RD; + if (CRD == FieldParentRecordDecl) { + flag = true; + break; + } + stack.push_back(CRD); + } + if (flag) + break; + } + if (auto* RD = llvm::dyn_cast(FieldParentRecordDecl)) { + // add in the offsets for the (multi level) base classes + while (BaseCXXRD != RD) { + CXXRecordDecl* Parent = direction.at(RD); + offset += C.getASTRecordLayout(Parent) + .getBaseClassOffset(RD) + .getQuantity(); + RD = Parent; + } + } else { + assert(false && "Unreachable"); + } + } + return offset; + } + + if (auto *VD = llvm::dyn_cast(D)) { + auto GD = GlobalDecl(VD); + std::string mangledName; + compat::maybeMangleDeclName(GD, mangledName); + void* address = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol( + mangledName.c_str()); + + if (!address) + address = I.getAddressOfGlobal(GD); + if (!address) { + if (!VD->hasInit()) { +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif // CPPINTEROP_USE_CLING + getSema().InstantiateVariableDefinition(SourceLocation(), VD); + } + if (VD->hasInit() && + (VD->isConstexpr() || VD->getType().isConstQualified())) { + if (const APValue* val = VD->evaluateValue()) { + if (VD->getType()->isIntegralType(C)) { + return (intptr_t)val->getInt().getRawData(); + } + } + } + } + if (!address) { + auto Linkage = C.GetGVALinkageForVariable(VD); + // The decl was deferred by CodeGen. Force its emission. + // FIXME: In ASTContext::DeclMustBeEmitted we should check if the + // Decl::isUsed is set or we should be able to access CodeGen's + // addCompilerUsedGlobal. + if (isDiscardableGVALinkage(Linkage)) + VD->addAttr(UsedAttr::CreateImplicit(C)); +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&I); + I.getCI()->getASTConsumer().HandleTopLevelDecl(DeclGroupRef(VD)); +#else // CLANG_REPL + I.getCI()->getASTConsumer().HandleTopLevelDecl(DeclGroupRef(VD)); + // Take the newest llvm::Module produced by CodeGen and send it to JIT. + auto GeneratedPTU = I.Parse(""); + if (!GeneratedPTU) + llvm::logAllUnhandledErrors(GeneratedPTU.takeError(), llvm::errs(), + "[GetVariableOffset] Failed to generate PTU:"); + + // From cling's BackendPasses.cpp + // FIXME: We need to upstream this code in IncrementalExecutor::addModule + for (auto &GV : GeneratedPTU->TheModule->globals()) { + llvm::GlobalValue::LinkageTypes LT = GV.getLinkage(); + if (GV.isDeclaration() || !GV.hasName() || + GV.getName().starts_with(".str") || + !GV.isDiscardableIfUnused(LT) || + LT != llvm::GlobalValue::InternalLinkage) + continue; //nothing to do + GV.setLinkage(llvm::GlobalValue::WeakAnyLinkage); + } + if (auto Err = I.Execute(*GeneratedPTU)) + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "[GetVariableOffset] Failed to execute PTU:"); +#endif + } + auto VDAorErr = compat::getSymbolAddress(I, StringRef(mangledName)); + if (!VDAorErr) { + llvm::logAllUnhandledErrors(VDAorErr.takeError(), llvm::errs(), + "Failed to GetVariableOffset:"); + return 0; + } + return (intptr_t)jitTargetAddressToPointer(VDAorErr.get()); + } + + return 0; + } + + intptr_t GetVariableOffset(TCppScope_t var, TCppScope_t parent) { + auto* D = static_cast(var); + auto* RD = + llvm::dyn_cast_or_null(static_cast(parent)); + return GetVariableOffset(getInterp(), D, RD); + } + + // Check if the Access Specifier of the variable matches the provided value. + bool CheckVariableAccess(TCppScope_t var, AccessSpecifier AS) + { + auto *D = (Decl *) var; + return D->getAccess() == AS; + } + + bool IsPublicVariable(TCppScope_t var) + { + return CheckVariableAccess(var, AccessSpecifier::AS_public); + } + + bool IsProtectedVariable(TCppScope_t var) + { + return CheckVariableAccess(var, AccessSpecifier::AS_protected); + } + + bool IsPrivateVariable(TCppScope_t var) + { + return CheckVariableAccess(var, AccessSpecifier::AS_private); + } + + bool IsStaticVariable(TCppScope_t var) + { + auto *D = (Decl *) var; + if (llvm::isa_and_nonnull(D)) { + return true; + } + + return false; + } + + bool IsConstVariable(TCppScope_t var) + { + auto *D = (clang::Decl *) var; + + if (auto *VD = llvm::dyn_cast_or_null(D)) { + return VD->getType().isConstQualified(); + } + + return false; + } + + bool IsRecordType(TCppType_t type) + { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isRecordType(); + } + + bool IsPODType(TCppType_t type) + { + QualType QT = QualType::getFromOpaquePtr(type); + + if (QT.isNull()) + return false; + + return QT.isPODType(getASTContext()); + } + + TCppType_t GetUnderlyingType(TCppType_t type) + { + QualType QT = QualType::getFromOpaquePtr(type); + QT = QT->getCanonicalTypeUnqualified(); + + // Recursively remove array dimensions + while (QT->isArrayType()) + QT = QualType(QT->getArrayElementTypeNoTypeQual(), 0); + + // Recursively reduce pointer depth till we are left with a pointerless + // type. + for (auto PT = QT->getPointeeType(); !PT.isNull(); PT = QT->getPointeeType()){ + QT = PT; + } + QT = QT->getCanonicalTypeUnqualified(); + return QT.getAsOpaquePtr(); + } + + std::string GetTypeAsString(TCppType_t var) + { + QualType QT = QualType::getFromOpaquePtr(var); + // FIXME: Get the default printing policy from the ASTContext. + PrintingPolicy Policy((LangOptions())); + Policy.Bool = true; // Print bool instead of _Bool. + Policy.SuppressTagKeyword = true; // Do not print `class std::string`. + return compat::FixTypeName(QT.getAsString(Policy)); + } + + TCppType_t GetCanonicalType(TCppType_t type) + { + if (!type) + return 0; + QualType QT = QualType::getFromOpaquePtr(type); + return QT.getCanonicalType().getAsOpaquePtr(); + } + + // Internal functions that are not needed outside the library are + // encompassed in an anonymous namespace as follows. This function converts + // from a string to the actual type. It is used in the GetType() function. + namespace { + static QualType findBuiltinType(llvm::StringRef typeName, ASTContext &Context) + { + bool issigned = false; + bool isunsigned = false; + if (typeName.starts_with("signed ")) { + issigned = true; + typeName = StringRef(typeName.data()+7, typeName.size()-7); + } + if (!issigned && typeName.starts_with("unsigned ")) { + isunsigned = true; + typeName = StringRef(typeName.data()+9, typeName.size()-9); + } + if (typeName == "char") { + if (isunsigned) return Context.UnsignedCharTy; + return Context.SignedCharTy; + } + if (typeName == "short") { + if (isunsigned) return Context.UnsignedShortTy; + return Context.ShortTy; + } + if (typeName == "int") { + if (isunsigned) return Context.UnsignedIntTy; + return Context.IntTy; + } + if (typeName == "long") { + if (isunsigned) return Context.UnsignedLongTy; + return Context.LongTy; + } + if (typeName == "long long") { + if (isunsigned) + return Context.UnsignedLongLongTy; + return Context.LongLongTy; + } + if (!issigned && !isunsigned) { + if (typeName == "bool") + return Context.BoolTy; + if (typeName == "float") + return Context.FloatTy; + if (typeName == "double") + return Context.DoubleTy; + if (typeName == "long double") + return Context.LongDoubleTy; + + if (typeName == "wchar_t") + return Context.WCharTy; + if (typeName == "char16_t") + return Context.Char16Ty; + if (typeName == "char32_t") + return Context.Char32Ty; + } + /* Missing + CanQualType WideCharTy; // Same as WCharTy in C++, integer type in C99. + CanQualType WIntTy; // [C99 7.24.1], integer type unchanged by default promotions. + */ + return QualType(); + } + } + + TCppType_t GetType(const std::string &name) { + QualType builtin = findBuiltinType(name, getASTContext()); + if (!builtin.isNull()) + return builtin.getAsOpaquePtr(); + + auto *D = (Decl *) GetNamed(name, /* Within= */ 0); + if (auto *TD = llvm::dyn_cast_or_null(D)) { + return QualType(TD->getTypeForDecl(), 0).getAsOpaquePtr(); + } + + return (TCppType_t)0; + } + + TCppType_t GetComplexType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + + return getASTContext().getComplexType(QT).getAsOpaquePtr(); + } + + TCppType_t GetTypeFromScope(TCppScope_t klass) { + if (!klass) + return 0; + + auto *D = (Decl *) klass; + ASTContext &C = getASTContext(); + + if (ValueDecl *VD = dyn_cast(D)) + return VD->getType().getAsOpaquePtr(); + + return C.getTypeDeclType(cast(D)).getAsOpaquePtr(); + } + + // Internal functions that are not needed outside the library are + // encompassed in an anonymous namespace as follows. + namespace { + static unsigned long long gWrapperSerial = 0LL; + + enum EReferenceType { kNotReference, kLValueReference, kRValueReference }; + + // Start of JitCall Helper Functions + +#define DEBUG_TYPE "jitcall" + + // FIXME: Use that routine throughout CallFunc's port in places such as + // make_narg_call. + static inline void indent(ostringstream &buf, int indent_level) { + static const std::string kIndentString(" "); + for (int i = 0; i < indent_level; ++i) + buf << kIndentString; + } + + void *compile_wrapper(compat::Interpreter& I, + const std::string& wrapper_name, + const std::string& wrapper, + bool withAccessControl = true) { + LLVM_DEBUG(dbgs() << "Compiling '" << wrapper_name << "'\n"); + return I.compileFunction(wrapper_name, wrapper, false /*ifUnique*/, + withAccessControl); + } + + void get_type_as_string(QualType QT, std::string& type_name, ASTContext& C, + PrintingPolicy Policy) { + //TODO: Implement cling desugaring from utils::AST + // cling::utils::Transform::GetPartiallyDesugaredType() + if (!QT->isTypedefNameType() || QT->isBuiltinType()) + QT = QT.getDesugaredType(C); +#if CLANG_VERSION_MAJOR > 16 + Policy.SuppressElaboration = true; +#endif + Policy.FullyQualifiedName = true; + QT.getAsStringInternal(type_name, Policy); + } + + void collect_type_info(const FunctionDecl* FD, QualType& QT, + std::ostringstream& typedefbuf, + std::ostringstream& callbuf, std::string& type_name, + EReferenceType& refType, bool& isPointer, + int indent_level, bool forArgument) { + // + // Collect information about the type of a function parameter + // needed for building the wrapper function. + // + ASTContext& C = FD->getASTContext(); + PrintingPolicy Policy(C.getPrintingPolicy()); + refType = kNotReference; + if (QT->isRecordType() && forArgument) { + get_type_as_string(QT, type_name, C, Policy); + return; + } + if (QT->isFunctionPointerType()) { + std::string fp_typedef_name; + { + std::ostringstream nm; + nm << "FP" << gWrapperSerial++; + type_name = nm.str(); + raw_string_ostream OS(fp_typedef_name); + QT.print(OS, Policy, type_name); + OS.flush(); + } + + indent(typedefbuf, indent_level); + + typedefbuf << "typedef " << fp_typedef_name << ";\n"; + return; + } else if (QT->isMemberPointerType()) { + std::string mp_typedef_name; + { + std::ostringstream nm; + nm << "MP" << gWrapperSerial++; + type_name = nm.str(); + raw_string_ostream OS(mp_typedef_name); + QT.print(OS, Policy, type_name); + OS.flush(); + } + + indent(typedefbuf, indent_level); + + typedefbuf << "typedef " << mp_typedef_name << ";\n"; + return; + } else if (QT->isPointerType()) { + isPointer = true; + QT = cast(QT.getCanonicalType())->getPointeeType(); + } else if (QT->isReferenceType()) { + if (QT->isRValueReferenceType()) + refType = kRValueReference; + else + refType = kLValueReference; + QT = cast(QT.getCanonicalType())->getPointeeType(); + } + // Fall through for the array type to deal with reference/pointer ro array + // type. + if (QT->isArrayType()) { + std::string ar_typedef_name; + { + std::ostringstream ar; + ar << "AR" << gWrapperSerial++; + type_name = ar.str(); + raw_string_ostream OS(ar_typedef_name); + QT.print(OS, Policy, type_name); + OS.flush(); + } + indent(typedefbuf, indent_level); + typedefbuf << "typedef " << ar_typedef_name << ";\n"; + return; + } + get_type_as_string(QT, type_name, C, Policy); + } + + void make_narg_ctor(const FunctionDecl* FD, const unsigned N, + std::ostringstream& typedefbuf, + std::ostringstream& callbuf, + const std::string& class_name, int indent_level) { + // Make a code string that follows this pattern: + // + // ClassName(args...) + // + + callbuf << class_name << "("; + for (unsigned i = 0U; i < N; ++i) { + const ParmVarDecl* PVD = FD->getParamDecl(i); + QualType Ty = PVD->getType(); + QualType QT = Ty.getCanonicalType(); + std::string type_name; + EReferenceType refType = kNotReference; + bool isPointer = false; + collect_type_info(FD, QT, typedefbuf, callbuf, type_name, refType, + isPointer, indent_level, true); + if (i) { + callbuf << ','; + if (i % 2) { + callbuf << ' '; + } else { + callbuf << "\n"; + indent(callbuf, indent_level); + } + } + if (refType != kNotReference) { + callbuf << "(" << type_name.c_str() + << (refType == kLValueReference ? "&" : "&&") << ")*(" + << type_name.c_str() << "*)args[" << i << "]"; + } else if (isPointer) { + callbuf << "*(" << type_name.c_str() << "**)args[" << i << "]"; + } else { + callbuf << "*(" << type_name.c_str() << "*)args[" << i << "]"; + } + } + callbuf << ")"; + } + + const DeclContext* get_non_transparent_decl_context(const FunctionDecl* FD) { + auto *DC = FD->getDeclContext(); + while (DC->isTransparentContext()) { + DC = DC->getParent(); + assert(DC && "All transparent contexts should have a parent!"); + } + return DC; + } + + void make_narg_call(const FunctionDecl* FD, const std::string& return_type, + const unsigned N, std::ostringstream& typedefbuf, + std::ostringstream& callbuf, + const std::string& class_name, int indent_level) { + // + // Make a code string that follows this pattern: + // + // ((*)obj)->(*(*)args[i], ...) + // + + // Sometimes it's necessary that we cast the function we want to call + // first to its explicit function type before calling it. This is supposed + // to prevent that we accidentally ending up in a function that is not + // the one we're supposed to call here (e.g. because the C++ function + // lookup decides to take another function that better fits). This method + // has some problems, e.g. when we call a function with default arguments + // and we don't provide all arguments, we would fail with this pattern. + // Same applies with member methods which seem to cause parse failures + // even when we supply the object parameter. Therefore we only use it in + // cases where we know it works and set this variable to true when we do. + bool ShouldCastFunction = + !isa(FD) && N == FD->getNumParams(); + if (ShouldCastFunction) { + callbuf << "("; + callbuf << "("; + callbuf << return_type << " (&)"; + { + callbuf << "("; + for (unsigned i = 0U; i < N; ++i) { + if (i) { + callbuf << ','; + if (i % 2) { + callbuf << ' '; + } else { + callbuf << "\n"; + indent(callbuf, indent_level); + } + } + const ParmVarDecl* PVD = FD->getParamDecl(i); + QualType Ty = PVD->getType(); + QualType QT = Ty.getCanonicalType(); + std::string arg_type; + ASTContext& C = FD->getASTContext(); + get_type_as_string(QT, arg_type, C, C.getPrintingPolicy()); + callbuf << arg_type; + } + if (FD->isVariadic()) + callbuf << ", ..."; + callbuf << ")"; + } + + callbuf << ")"; + } + + if (const CXXMethodDecl* MD = dyn_cast(FD)) { + // This is a class, struct, or union member. + if (MD->isConst()) + callbuf << "((const " << class_name << "*)obj)->"; + else + callbuf << "((" << class_name << "*)obj)->"; + } else if (const NamedDecl* ND = + dyn_cast(get_non_transparent_decl_context(FD))) { + // This is a namespace member. + (void)ND; + callbuf << class_name << "::"; + } + // callbuf << fMethod->Name() << "("; + { + std::string name; + { + llvm::raw_string_ostream stream(name); + FD->getNameForDiagnostic(stream, + FD->getASTContext().getPrintingPolicy(), + /*Qualified=*/false); + } + callbuf << name; + } + if (ShouldCastFunction) + callbuf << ")"; + + callbuf << "("; + for (unsigned i = 0U; i < N; ++i) { + const ParmVarDecl* PVD = FD->getParamDecl(i); + QualType Ty = PVD->getType(); + QualType QT = Ty.getCanonicalType(); + std::string type_name; + EReferenceType refType = kNotReference; + bool isPointer = false; + collect_type_info(FD, QT, typedefbuf, callbuf, type_name, refType, + isPointer, indent_level, true); + + if (i) { + callbuf << ','; + if (i % 2) { + callbuf << ' '; + } else { + callbuf << "\n"; + indent(callbuf, indent_level); + } + } + + if (refType != kNotReference) { + callbuf << "(" << type_name.c_str() + << (refType == kLValueReference ? "&" : "&&") << ")*(" + << type_name.c_str() << "*)args[" << i << "]"; + } else if (isPointer) { + callbuf << "*(" << 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). + callbuf << "*(" << type_name.c_str() << "*)args[" << i << "]"; + } + } + callbuf << ")"; + } + + void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N, + const std::string& class_name, + std::ostringstream& buf, int indent_level) { + // Make a code string that follows this pattern: + // + // (*(ClassName**)ret) = (obj) ? + // new (*(ClassName**)ret) ClassName(args...) : new ClassName(args...); + // + { + std::ostringstream typedefbuf; + std::ostringstream callbuf; + // + // Write the return value assignment part. + // + indent(callbuf, indent_level); + callbuf << "(*(" << class_name << "**)ret) = "; + callbuf << "(obj) ? new (*(" << class_name << "**)ret) "; + make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level); + + callbuf << ": new "; + // + // Write the actual expression. + // + make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level); + // + // End the new expression statement. + // + callbuf << ";\n"; + // + // Output the whole new expression and return statement. + // + buf << typedefbuf.str() << callbuf.str(); + } + } + + void make_narg_call_with_return(compat::Interpreter& I, + const FunctionDecl* FD, const unsigned N, + const std::string& class_name, + std::ostringstream& buf, int indent_level) { + // Make a code string that follows this pattern: + // + // if (ret) { + // new (ret) (return_type) ((class_name*)obj)->func(args...); + // } + // else { + // (void)(((class_name*)obj)->func(args...)); + // } + // + if (const CXXConstructorDecl* CD = dyn_cast(FD)) { + if (N <= 1 && llvm::isa(FD)) { + auto SpecMemKind = I.getCI()->getSema().getSpecialMember(CD); + if ((N == 0 && + SpecMemKind == CXXSpecialMemberKindDefaultConstructor) || + (N == 1 && + (SpecMemKind == CXXSpecialMemberKindCopyConstructor || + SpecMemKind == CXXSpecialMemberKindMoveConstructor))) { + // Using declarations cannot inject special members; do not call + // them as such. This might happen by using `Base(Base&, int = 12)`, + // which is fine to be called as `Derived d(someBase, 42)` but not + // as copy constructor of `Derived`. + return; + } + } + make_narg_ctor_with_return(FD, N, class_name, buf, indent_level); + return; + } + QualType QT = FD->getReturnType(); + if (QT->isVoidType()) { + std::ostringstream typedefbuf; + std::ostringstream callbuf; + indent(callbuf, indent_level); + make_narg_call(FD, "void", N, typedefbuf, callbuf, class_name, + indent_level); + callbuf << ";\n"; + indent(callbuf, indent_level); + callbuf << "return;\n"; + buf << typedefbuf.str() << callbuf.str(); + } else { + indent(buf, indent_level); + + std::string type_name; + EReferenceType refType = kNotReference; + bool isPointer = false; + + buf << "if (ret) {\n"; + ++indent_level; + { + std::ostringstream typedefbuf; + std::ostringstream callbuf; + // + // Write the placement part of the placement new. + // + indent(callbuf, indent_level); + callbuf << "new (ret) "; + collect_type_info(FD, QT, typedefbuf, callbuf, type_name, refType, + isPointer, indent_level, false); + // + // Write the type part of the placement new. + // + callbuf << "(" << type_name.c_str(); + if (refType != kNotReference) { + callbuf << "*) (&"; + type_name += "&"; + } else if (isPointer) { + callbuf << "*) ("; + type_name += "*"; + } else { + callbuf << ") ("; + } + // + // Write the actual function call. + // + make_narg_call(FD, type_name, N, typedefbuf, callbuf, class_name, + indent_level); + // + // End the placement new. + // + callbuf << ");\n"; + indent(callbuf, indent_level); + callbuf << "return;\n"; + // + // Output the whole placement new expression and return statement. + // + buf << typedefbuf.str() << callbuf.str(); + } + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + indent(buf, indent_level); + buf << "else {\n"; + ++indent_level; + { + std::ostringstream typedefbuf; + std::ostringstream callbuf; + indent(callbuf, indent_level); + callbuf << "(void)("; + make_narg_call(FD, type_name, N, typedefbuf, callbuf, class_name, + indent_level); + callbuf << ");\n"; + indent(callbuf, indent_level); + callbuf << "return;\n"; + buf << typedefbuf.str() << callbuf.str(); + } + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + } + } + + int get_wrapper_code(compat::Interpreter& I, const FunctionDecl* FD, + std::string& wrapper_name, std::string& wrapper) { + assert(FD && "generate_wrapper called without a function decl!"); + ASTContext& Context = FD->getASTContext(); + PrintingPolicy Policy(Context.getPrintingPolicy()); + // + // Get the class or namespace name. + // + std::string class_name; + const clang::DeclContext* DC = get_non_transparent_decl_context(FD); + if (const TypeDecl* TD = dyn_cast(DC)) { + // This is a class, struct, or union member. + QualType QT(TD->getTypeForDecl(), 0); + get_type_as_string(QT, class_name, Context, Policy); + } else if (const NamedDecl* ND = dyn_cast(DC)) { + // This is a namespace member. + raw_string_ostream stream(class_name); + ND->getNameForDiagnostic(stream, Policy, /*Qualified=*/true); + stream.flush(); + } + // + // Check to make sure that we can + // instantiate and codegen this function. + // + bool needInstantiation = false; + const FunctionDecl* Definition = 0; + if (!FD->isDefined(Definition)) { + FunctionDecl::TemplatedKind TK = FD->getTemplatedKind(); + switch (TK) { + case FunctionDecl::TK_NonTemplate: { + // Ordinary function, not a template specialization. + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << + // "Cannot make wrapper for a function which is " + // "declared but not defined!"; + // return 0; + } break; + case FunctionDecl::TK_FunctionTemplate: { + // This decl is actually a function template, + // not a function at all. + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a function template!"; + return 0; + } break; + case FunctionDecl::TK_MemberSpecialization: { + // This function is the result of instantiating an ordinary + // member function of a class template, or of instantiating + // an ordinary member function of a class member of a class + // template, or of specializing a member function template + // of a class template, or of specializing a member function + // template of a class member of a class template. + if (!FD->isTemplateInstantiation()) { + // We are either TSK_Undeclared or + // TSK_ExplicitSpecialization. + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << + // "Cannot make wrapper for a function template " + // "explicit specialization which is declared " + // "but not defined!"; + // return 0; + break; + } + const FunctionDecl* Pattern = FD->getTemplateInstantiationPattern(); + if (!Pattern) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a member function " + "instantiation with no pattern!"; + return 0; + } + FunctionDecl::TemplatedKind PTK = Pattern->getTemplatedKind(); + TemplateSpecializationKind PTSK = + Pattern->getTemplateSpecializationKind(); + if ( + // The pattern is an ordinary member function. + (PTK == FunctionDecl::TK_NonTemplate) || + // The pattern is an explicit specialization, and + // so is not a template. + ((PTK != FunctionDecl::TK_FunctionTemplate) && + ((PTSK == TSK_Undeclared) || + (PTSK == TSK_ExplicitSpecialization)))) { + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + break; + } else if (!Pattern->hasBody()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a member function " + "instantiation with no body!"; + return 0; + } + if (FD->isImplicitlyInstantiable()) { + needInstantiation = true; + } + } break; + case FunctionDecl::TK_FunctionTemplateSpecialization: { + // This function is the result of instantiating a function + // template or possibly an explicit specialization of a + // function template. Could be a namespace scope function or a + // member function. + if (!FD->isTemplateInstantiation()) { + // We are either TSK_Undeclared or + // TSK_ExplicitSpecialization. + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << + // "Cannot make wrapper for a function template " + // "explicit specialization which is declared " + // "but not defined!"; + // return 0; + break; + } + const FunctionDecl* Pattern = FD->getTemplateInstantiationPattern(); + if (!Pattern) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a function template" + "instantiation with no pattern!"; + return 0; + } + FunctionDecl::TemplatedKind PTK = Pattern->getTemplatedKind(); + TemplateSpecializationKind PTSK = + Pattern->getTemplateSpecializationKind(); + if ( + // The pattern is an ordinary member function. + (PTK == FunctionDecl::TK_NonTemplate) || + // The pattern is an explicit specialization, and + // so is not a template. + ((PTK != FunctionDecl::TK_FunctionTemplate) && + ((PTSK == TSK_Undeclared) || + (PTSK == TSK_ExplicitSpecialization)))) { + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + break; + } + if (!Pattern->hasBody()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a function template" + "instantiation with no body!"; + return 0; + } + if (FD->isImplicitlyInstantiable()) { + needInstantiation = true; + } + } break; + case FunctionDecl::TK_DependentFunctionTemplateSpecialization: { + // This function is the result of instantiating or + // specializing a member function of a class template, + // or a member function of a class member of a class template, + // or a member function template of a class template, or a + // member function template of a class member of a class + // template where at least some part of the function is + // dependent on a template argument. + if (!FD->isTemplateInstantiation()) { + // We are either TSK_Undeclared or + // TSK_ExplicitSpecialization. + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << + // "Cannot make wrapper for a dependent function " + // "template explicit specialization which is declared " + // "but not defined!"; + // return 0; + break; + } + const FunctionDecl* Pattern = FD->getTemplateInstantiationPattern(); + if (!Pattern) { + llvm::errs() + << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a dependent function template" + "instantiation with no pattern!"; + return 0; + } + FunctionDecl::TemplatedKind PTK = Pattern->getTemplatedKind(); + TemplateSpecializationKind PTSK = + Pattern->getTemplateSpecializationKind(); + if ( + // The pattern is an ordinary member function. + (PTK == FunctionDecl::TK_NonTemplate) || + // The pattern is an explicit specialization, and + // so is not a template. + ((PTK != FunctionDecl::TK_FunctionTemplate) && + ((PTSK == TSK_Undeclared) || + (PTSK == TSK_ExplicitSpecialization)))) { + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + break; + } + if (!Pattern->hasBody()) { + llvm::errs() + << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a dependent function template" + "instantiation with no body!"; + return 0; + } + if (FD->isImplicitlyInstantiable()) { + needInstantiation = true; + } + } break; + default: { + // Will only happen if clang implementation changes. + // Protect ourselves in case that happens. + llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << + "Unhandled template kind!"; + return 0; + } break; + } + // We do not set needInstantiation to true in these cases: + // + // isInvalidDecl() + // TSK_Undeclared + // TSK_ExplicitInstantiationDefinition + // TSK_ExplicitSpecialization && !getClassScopeSpecializationPattern() + // TSK_ExplicitInstantiationDeclaration && + // getTemplateInstantiationPattern() && + // PatternDecl->hasBody() && + // !PatternDecl->isInlined() + // + // Set it true in these cases: + // + // TSK_ImplicitInstantiation + // TSK_ExplicitInstantiationDeclaration && (!getPatternDecl() || + // !PatternDecl->hasBody() || PatternDecl->isInlined()) + // + } + if (needInstantiation) { + clang::FunctionDecl* FDmod = const_cast(FD); + clang::Sema& S = I.getCI()->getSema(); + // Could trigger deserialization of decls. +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&I); +#endif + S.InstantiateFunctionDefinition(SourceLocation(), FDmod, + /*Recursive=*/true, + /*DefinitionRequired=*/true); + if (!FD->isDefined(Definition)) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Failed to force template instantiation!"; + return 0; + } + } + if (Definition) { + FunctionDecl::TemplatedKind TK = Definition->getTemplatedKind(); + switch (TK) { + case FunctionDecl::TK_NonTemplate: { + // Ordinary function, not a template specialization. + if (Definition->isDeleted()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a deleted function!"; + return 0; + } else if (Definition->isLateTemplateParsed()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a late template parsed " + "function!"; + return 0; + } + // else if (Definition->isDefaulted()) { + // // Might not have a body, but we can still use it. + //} + // else { + // // Has a body. + //} + } break; + case FunctionDecl::TK_FunctionTemplate: { + // This decl is actually a function template, + // not a function at all. + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a function template!"; + return 0; + } break; + case FunctionDecl::TK_MemberSpecialization: { + // This function is the result of instantiating an ordinary + // member function of a class template or of a member class + // of a class template. + if (Definition->isDeleted()) { + llvm::errs() + << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a deleted member function " + "of a specialization!"; + return 0; + } else if (Definition->isLateTemplateParsed()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a late template parsed " + "member function of a specialization!"; + return 0; + } + // else if (Definition->isDefaulted()) { + // // Might not have a body, but we can still use it. + //} + // else { + // // Has a body. + //} + } break; + case FunctionDecl::TK_FunctionTemplateSpecialization: { + // This function is the result of instantiating a function + // template or possibly an explicit specialization of a + // function template. Could be a namespace scope function or a + // member function. + if (Definition->isDeleted()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a deleted function " + "template specialization!"; + return 0; + } else if (Definition->isLateTemplateParsed()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a late template parsed " + "function template specialization!"; + return 0; + } + // else if (Definition->isDefaulted()) { + // // Might not have a body, but we can still use it. + //} + // else { + // // Has a body. + //} + } break; + case FunctionDecl::TK_DependentFunctionTemplateSpecialization: { + // This function is the result of instantiating or + // specializing a member function of a class template, + // or a member function of a class member of a class template, + // or a member function template of a class template, or a + // member function template of a class member of a class + // template where at least some part of the function is + // dependent on a template argument. + if (Definition->isDeleted()) { + llvm::errs() + << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a deleted dependent function " + "template specialization!"; + return 0; + } else if (Definition->isLateTemplateParsed()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a late template parsed " + "dependent function template specialization!"; + return 0; + } + // else if (Definition->isDefaulted()) { + // // Might not have a body, but we can still use it. + //} + // else { + // // Has a body. + //} + } break; + default: { + // Will only happen if clang implementation changes. + // Protect ourselves in case that happens. + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Unhandled template kind!"; + return 0; + } break; + } + } + unsigned min_args = FD->getMinRequiredArguments(); + unsigned num_params = FD->getNumParams(); + // + // Make the wrapper name. + // + { + std::ostringstream buf; + buf << "__cf"; + // const NamedDecl* ND = dyn_cast(FD); + // std::string mn; + // fInterp->maybeMangleDeclName(ND, mn); + // buf << '_' << mn; + buf << '_' << gWrapperSerial++; + wrapper_name = buf.str(); + } + // + // Write the wrapper code. + // FIXME: this should be synthesized into the AST! + // + int indent_level = 0; + std::ostringstream buf; + buf << "#pragma clang diagnostic push\n" + "#pragma clang diagnostic ignored \"-Wformat-security\"\n" + "__attribute__((used)) " + "__attribute__((annotate(\"__cling__ptrcheck(off)\")))\n" + "extern \"C\" void "; + buf << wrapper_name; + buf << "(void* obj, int nargs, void** args, void* ret)\n" + "{\n"; + ++indent_level; + if (min_args == num_params) { + // No parameters with defaults. + make_narg_call_with_return(I, FD, num_params, class_name, buf, + indent_level); + } else { + // We need one function call clause compiled for every + // possible number of arguments per call. + for (unsigned N = min_args; N <= num_params; ++N) { + indent(buf, indent_level); + buf << "if (nargs == " << N << ") {\n"; + ++indent_level; + make_narg_call_with_return(I, FD, N, class_name, buf, indent_level); + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + } + } + --indent_level; + buf << "}\n" + "#pragma clang diagnostic pop"; + wrapper = buf.str(); + return 1; + } + + JitCall::GenericCall make_wrapper(compat::Interpreter& I, + const FunctionDecl* FD) { + static std::map gWrapperStore; + + auto R = gWrapperStore.find(FD); + if (R != gWrapperStore.end()) + return (JitCall::GenericCall) R->second; + + std::string wrapper_name; + std::string wrapper_code; + + if (get_wrapper_code(I, FD, wrapper_name, wrapper_code) == 0) + return 0; + + // + // Compile the wrapper code. + // + bool withAccessControl = true; + // We should be able to call private default constructors. + if (auto Ctor = dyn_cast(FD)) + withAccessControl = !Ctor->isDefaultConstructor(); + void *wrapper = compile_wrapper(I, wrapper_name, wrapper_code, + withAccessControl); + if (wrapper) { + gWrapperStore.insert(std::make_pair(FD, wrapper)); + } else { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Failed to compile\n" + << "==== SOURCE BEGIN ====\n" + << wrapper_code << "\n" + << "==== SOURCE END ====\n"; + } + LLVM_DEBUG(dbgs() << "Compiled '" << (wrapper ? "" : "un") + << "successfully:\n" << wrapper_code << "'\n"); + return (JitCall::GenericCall)wrapper; + } + + // FIXME: Sink in the code duplication from get_wrapper_code. + static std::string PrepareTorWrapper(const Decl* D, + const char* wrapper_prefix, + std::string& class_name) { + ASTContext &Context = D->getASTContext(); + PrintingPolicy Policy(Context.getPrintingPolicy()); + Policy.SuppressTagKeyword = true; + Policy.SuppressUnwrittenScope = true; + // + // Get the class or namespace name. + // + if (const TypeDecl *TD = dyn_cast(D)) { + // This is a class, struct, or union member. + // Handle the typedefs to anonymous types. + QualType QT; + if (const TypedefDecl *Typedef = dyn_cast(TD)) + QT = Typedef->getTypeSourceInfo()->getType(); + else + QT = {TD->getTypeForDecl(), 0}; + get_type_as_string(QT, class_name, Context, Policy); + } else if (const NamedDecl *ND = dyn_cast(D)) { + // This is a namespace member. + raw_string_ostream stream(class_name); + ND->getNameForDiagnostic(stream, Policy, /*Qualified=*/true); + stream.flush(); + } + + // + // Make the wrapper name. + // + string wrapper_name; + { + ostringstream buf; + buf << wrapper_prefix; + //const NamedDecl* ND = dyn_cast(FD); + //string mn; + //fInterp->maybeMangleDeclName(ND, mn); + //buf << '_dtor_' << mn; + buf << '_' << gWrapperSerial++; + wrapper_name = buf.str(); + } + + return wrapper_name; + } + + static JitCall::DestructorCall make_dtor_wrapper(compat::Interpreter& interp, + const Decl *D) { + // Make a code string that follows this pattern: + // + // void + // unique_wrapper_ddd(void* obj, unsigned long nary, int withFree) + // { + // if (withFree) { + // if (!nary) { + // delete (ClassName*) obj; + // } + // else { + // delete[] (ClassName*) obj; + // } + // } + // else { + // typedef ClassName DtorName; + // if (!nary) { + // ((ClassName*)obj)->~DtorName(); + // } + // else { + // for (unsigned long i = nary - 1; i > -1; --i) { + // (((ClassName*)obj)+i)->~DtorName(); + // } + // } + // } + // } + // + //-- + + static map gDtorWrapperStore; + + auto I = gDtorWrapperStore.find(D); + if (I != gDtorWrapperStore.end()) + return (JitCall::DestructorCall) I->second; + + // + // Make the wrapper name. + // + std::string class_name; + string wrapper_name = PrepareTorWrapper(D, "__dtor", class_name); + // + // Write the wrapper code. + // + int indent_level = 0; + ostringstream buf; + buf << "__attribute__((used)) "; + buf << "extern \"C\" void "; + buf << wrapper_name; + buf << "(void* obj, unsigned long nary, int withFree)\n"; + buf << "{\n"; + // if (withFree) { + // if (!nary) { + // delete (ClassName*) obj; + // } + // else { + // delete[] (ClassName*) obj; + // } + // } + ++indent_level; + indent(buf, indent_level); + buf << "if (withFree) {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "if (!nary) {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "delete (" << class_name << "*) obj;\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + indent(buf, indent_level); + buf << "else {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "delete[] (" << class_name << "*) obj;\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + // else { + // typedef ClassName Nm; + // if (!nary) { + // ((Nm*)obj)->~Nm(); + // } + // else { + // for (unsigned long i = nary - 1; i > -1; --i) { + // (((Nm*)obj)+i)->~Nm(); + // } + // } + // } + indent(buf, indent_level); + buf << "else {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "typedef " << class_name << " Nm;\n"; + buf << "if (!nary) {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "((Nm*)obj)->~Nm();\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + indent(buf, indent_level); + buf << "else {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "do {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "(((Nm*)obj)+(--nary))->~Nm();\n"; + --indent_level; + indent(buf, indent_level); + buf << "} while (nary);\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + // End wrapper. + --indent_level; + buf << "}\n"; + // Done. + string wrapper(buf.str()); + //fprintf(stderr, "%s\n", wrapper.c_str()); + // + // Compile the wrapper code. + // + void *F = compile_wrapper(interp, wrapper_name, wrapper, + /*withAccessControl=*/false); + if (F) { + gDtorWrapperStore.insert(make_pair(D, F)); + } else { + llvm::errs() << "make_dtor_wrapper" + << "Failed to compile\n" + << "==== SOURCE BEGIN ====\n" + << wrapper + << "\n ==== SOURCE END ===="; + } + LLVM_DEBUG(dbgs() << "Compiled '" << (F ? "" : "un") + << "successfully:\n" << wrapper << "'\n"); + return (JitCall::DestructorCall)F; + } +#undef DEBUG_TYPE + } // namespace + // End of JitCall Helper Functions + + CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, + TCppConstFunction_t func) { + const auto* D = static_cast(func); + if (!D) + return {}; + + auto* interp = static_cast(I); + + // FIXME: Unify with make_wrapper. + if (const auto* Dtor = dyn_cast(D)) { + if (auto Wrapper = make_dtor_wrapper(*interp, Dtor->getParent())) + return {JitCall::kDestructorCall, Wrapper, Dtor}; + // FIXME: else error we failed to compile the wrapper. + return {}; + } + + if (auto Wrapper = make_wrapper(*interp, cast(D))) { + return {JitCall::kGenericCall, Wrapper, cast(D)}; + } + // FIXME: else error we failed to compile the wrapper. + return {}; + } + + CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func) { + return MakeFunctionCallable(&getInterp(), func); + } + + namespace { + static std::string MakeResourcesPath() { + StringRef Dir; +#ifdef LLVM_BINARY_DIR + Dir = LLVM_BINARY_DIR; +#else + // Dir is bin/ or lib/, depending on where BinaryPath is. + void *MainAddr = (void *)(intptr_t)GetExecutablePath; + std::string BinaryPath = GetExecutablePath(/*Argv0=*/nullptr, MainAddr); + + // build/tools/clang/unittests/Interpreter/Executable -> build/ + StringRef Dir = sys::path::parent_path(BinaryPath); + + Dir = sys::path::parent_path(Dir); + Dir = sys::path::parent_path(Dir); + Dir = sys::path::parent_path(Dir); + Dir = sys::path::parent_path(Dir); + //Dir = sys::path::parent_path(Dir); +#endif // LLVM_BINARY_DIR + return compat::MakeResourceDir(Dir); + } + } // namespace + + TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, + const std::vector& GpuArgs /*={}*/) { + std::string MainExecutableName = + sys::fs::getMainExecutable(nullptr, nullptr); + std::string ResourceDir = MakeResourcesPath(); + std::vector ClingArgv = {"-resource-dir", ResourceDir.c_str(), + "-std=c++14"}; + ClingArgv.insert(ClingArgv.begin(), MainExecutableName.c_str()); +#ifdef _WIN32 + // FIXME : Workaround Sema::PushDeclContext assert on windows + ClingArgv.push_back("-fno-delayed-template-parsing"); +#endif + ClingArgv.insert(ClingArgv.end(), Args.begin(), Args.end()); + // To keep the Interpreter creation interface between cling and clang-repl + // to some extent compatible we should put Args and GpuArgs together. On the + // receiving end we should check for -xcuda to know. + if (!GpuArgs.empty()) { + llvm::StringRef Arg0 = GpuArgs[0]; + Arg0 = Arg0.trim().ltrim('-'); + if (Arg0 != "cuda") { + llvm::errs() << "[CreateInterpreter]: Make sure --cuda is passed as the" + << " first argument of the GpuArgs\n"; + return nullptr; + } + } + ClingArgv.insert(ClingArgv.end(), GpuArgs.begin(), GpuArgs.end()); + + // Process externally passed arguments if present. + std::vector ExtraArgs; + auto EnvOpt = + llvm::sys::Process::GetEnv("CPPINTEROP_EXTRA_INTERPRETER_ARGS"); + if (EnvOpt) { + StringRef Env(*EnvOpt); + while (!Env.empty()) { + StringRef Arg; + std::tie(Arg, Env) = Env.split(' '); + ExtraArgs.push_back(Arg.str()); + } + } + std::transform(ExtraArgs.begin(), ExtraArgs.end(), + std::back_inserter(ClingArgv), + [&](const std::string& str) { return str.c_str(); }); + + auto I = new compat::Interpreter(ClingArgv.size(), &ClingArgv[0]); + + // Honor -mllvm. + // + // FIXME: Remove this, one day. + // This should happen AFTER plugins have been loaded! + const CompilerInstance* Clang = I->getCI(); + 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()); + } + // FIXME: Enable this assert once we figure out how to fix the multiple + // calls to CreateInterpreter. + //assert(!sInterpreter && "Interpreter already set."); + sInterpreter = I; + return I; + } + + TInterp_t GetInterpreter() { return sInterpreter; } + + void UseExternalInterpreter(TInterp_t I) { + assert(!sInterpreter && "sInterpreter already in use!"); + sInterpreter = static_cast(I); + OwningSInterpreter = false; + } + + void AddSearchPath(const char *dir, bool isUser, + bool prepend) { + getInterp().getDynamicLibraryManager()->addSearchPath(dir, isUser, prepend); + } + + const char* GetResourceDir() { + return getInterp().getCI()->getHeaderSearchOpts().ResourceDir.c_str(); + } + + ///\returns 0 on success. + static bool exec(const char* cmd, std::vector& outputs) { +#define DEBUG_TYPE "exec" + + std::array buffer; + struct file_deleter { + void operator()(FILE* fp) { pclose(fp); } + }; + std::unique_ptr pipe{popen(cmd, "r")}; + LLVM_DEBUG(dbgs() << "Executing command '" << cmd << "'\n"); + + if (!pipe) { + LLVM_DEBUG(dbgs() << "Execute failed!\n"); + perror("exec: "); + return false; + } + + LLVM_DEBUG(dbgs() << "Execute returned:\n"); + while (fgets(buffer.data(), static_cast(buffer.size()), pipe.get())) { + LLVM_DEBUG(dbgs() << buffer.data()); + llvm::StringRef trimmed = buffer.data(); + outputs.push_back(trimmed.trim().str()); + } + +#undef DEBUG_TYPE + + return true; + } + + std::string DetectResourceDir(const char* ClangBinaryName /* = clang */) { + std::string cmd = std::string(ClangBinaryName) + " -print-resource-dir"; + std::vector outs; + exec(cmd.c_str(), outs); + if (outs.empty() || outs.size() > 1) + return ""; + + std::string detected_resource_dir = outs.back(); + + std::string version = +#if CLANG_VERSION_MAJOR < 16 + CLANG_VERSION_STRING; +#else + CLANG_VERSION_MAJOR_STRING; +#endif + // We need to check if the detected resource directory is compatible. + if (llvm::sys::path::filename(detected_resource_dir) != version) + return ""; + + return detected_resource_dir; + } + + void DetectSystemCompilerIncludePaths(std::vector& Paths, + const char* CompilerName /*= "c++"*/) { + std::string cmd = "LC_ALL=C "; + cmd += CompilerName; + cmd += " -xc++ -E -v /dev/null 2>&1 | sed -n -e '/^.include/,${' -e '/^ " + "\\/.*/p' -e '}'"; + std::vector outs; + exec(cmd.c_str(), Paths); + } + + void AddIncludePath(const char *dir) { + getInterp().AddIncludePath(dir); + } + + void GetIncludePaths(std::vector& IncludePaths, bool withSystem, + bool withFlags) { + llvm::SmallVector paths(1); + getInterp().GetIncludePaths(paths, withSystem, withFlags); + for (auto& i : paths) + IncludePaths.push_back(i); + } + + namespace { + + class clangSilent { + public: + clangSilent(clang::DiagnosticsEngine &diag) : fDiagEngine(diag) { + fOldDiagValue = fDiagEngine.getSuppressAllDiagnostics(); + fDiagEngine.setSuppressAllDiagnostics(true); + } + + ~clangSilent() { fDiagEngine.setSuppressAllDiagnostics(fOldDiagValue); } + + protected: + clang::DiagnosticsEngine &fDiagEngine; + bool fOldDiagValue; + }; + } // namespace + + int Declare(const char* code, bool silent) { + auto& I = getInterp(); + + if (silent) { + clangSilent diagSuppr(I.getSema().getDiagnostics()); + return I.declare(code); + } + + return I.declare(code); + } + + int Process(const char *code) { + return getInterp().process(code); + } + + intptr_t Evaluate(const char *code, + bool *HadError/*=nullptr*/) { +#ifdef CPPINTEROP_USE_CLING + cling::Value V; +#else + clang::Value V; +#endif // CPPINTEROP_USE_CLING + + if (HadError) + *HadError = false; + + auto res = getInterp().evaluate(code, V); + if (res != 0) { // 0 is success + if (HadError) + *HadError = true; + // FIXME: Make this return llvm::Expected + return ~0UL; + } + + return compat::convertTo(V); + } + + std::string LookupLibrary(const char* lib_name) { + return getInterp().getDynamicLibraryManager()->lookupLibrary(lib_name); + } + + bool LoadLibrary(const char* lib_stem, bool lookup) { + compat::Interpreter::CompilationResult res = + getInterp().loadLibrary(lib_stem, lookup); + + return res == compat::Interpreter::kSuccess; + } + + void UnloadLibrary(const char* lib_stem) { + getInterp().getDynamicLibraryManager()->unloadLibrary(lib_stem); + } + + std::string SearchLibrariesForSymbol(const char* mangled_name, + bool search_system /*true*/) { + auto* DLM = getInterp().getDynamicLibraryManager(); + return DLM->searchLibrariesForSymbol(mangled_name, search_system); + } + + bool InsertOrReplaceJitSymbol(compat::Interpreter& I, + const char* linker_mangled_name, + uint64_t address) { + // FIXME: This approach is problematic since we could replace a symbol + // whose address was already taken by clients. + // + // A safer approach would be to define our symbol replacements early in the + // bootstrap process like: + // auto J = LLJITBuilder().create(); + // if (!J) + // return Err; + // + // if (Jupyter) { + // llvm::orc::SymbolMap Overrides; + // Overrides[J->mangleAndIntern("printf")] = + // { ExecutorAddr::fromPtr(&printf), JITSymbolFlags::Exported }; + // Overrides[...] = + // { ... }; + // if (auto Err = + // J->getProcessSymbolsJITDylib().define(absoluteSymbols(std::move(Overrides))) + // return Err; + // } + + // FIXME: If we still want to do symbol replacement we should use the + // ReplacementManager which is available in llvm 18. + using namespace llvm; + using namespace llvm::orc; + + auto Symbol = compat::getSymbolAddress(I, linker_mangled_name); + llvm::orc::LLJIT& Jit = *compat::getExecutionEngine(I); + llvm::orc::ExecutionSession& ES = Jit.getExecutionSession(); +#if CLANG_VERSION_MAJOR < 17 + JITDylib& DyLib = Jit.getMainJITDylib(); +#else + JITDylib& DyLib = *Jit.getProcessSymbolsJITDylib().get(); +#endif // CLANG_VERSION_MAJOR + + if (Error Err = Symbol.takeError()) { + logAllUnhandledErrors(std::move(Err), errs(), + "[InsertOrReplaceJitSymbol] error: "); +#define DEBUG_TYPE "orc" + LLVM_DEBUG(ES.dump(dbgs())); +#undef DEBUG_TYPE + return true; + } + + // Nothing to define, we are redefining the same function. + if (*Symbol && *Symbol == address) { + errs() << "[InsertOrReplaceJitSymbol] warning: redefining '" + << linker_mangled_name << "' with the same address\n"; + return true; + } + + // Let's inject it. + llvm::orc::SymbolMap InjectedSymbols; + auto& DL = compat::getExecutionEngine(I)->getDataLayout(); + char GlobalPrefix = DL.getGlobalPrefix(); + std::string tmp(linker_mangled_name); + if (GlobalPrefix != '\0') { + tmp = std::string(1, GlobalPrefix) + tmp; + } + auto Name = ES.intern(tmp); + InjectedSymbols[Name] = +#if CLANG_VERSION_MAJOR < 17 + JITEvaluatedSymbol(address, +#else + ExecutorSymbolDef(ExecutorAddr(address), +#endif // CLANG_VERSION_MAJOR < 17 + JITSymbolFlags::Exported); + + // We want to replace a symbol with a custom provided one. + if (Symbol && address) + // The symbol be in the DyLib or in-process. + if (auto Err = DyLib.remove({Name})) { + logAllUnhandledErrors(std::move(Err), errs(), + "[InsertOrReplaceJitSymbol] error: "); + return true; + } + + if (Error Err = DyLib.define(absoluteSymbols(InjectedSymbols))) { + logAllUnhandledErrors(std::move(Err), errs(), + "[InsertOrReplaceJitSymbol] error: "); + return true; + } + + return false; + } + + bool InsertOrReplaceJitSymbol(const char* linker_mangled_name, + uint64_t address) { + return InsertOrReplaceJitSymbol(getInterp(), linker_mangled_name, address); + } + + std::string ObjToString(const char *type, void *obj) { + return getInterp().toString(type, obj); + } + + static Decl* InstantiateTemplate(TemplateDecl* TemplateD, + TemplateArgumentListInfo& TLI, Sema& S) { + // This is not right but we don't have a lot of options to choose from as a + // template instantiation requires a valid source location. + SourceLocation fakeLoc = GetValidSLoc(S); + if (auto* FunctionTemplate = dyn_cast(TemplateD)) { + FunctionDecl* Specialization = nullptr; + clang::sema::TemplateDeductionInfo Info(fakeLoc); + Template_Deduction_Result Result = S.DeduceTemplateArguments( + FunctionTemplate, &TLI, Specialization, Info, + /*IsAddressOfFunction*/ true); + if (Result != Template_Deduction_Result_Success) { + // FIXME: Diagnose what happened. + (void)Result; + } + return Specialization; + } + + if (auto* VarTemplate = dyn_cast(TemplateD)) { + DeclResult R = S.CheckVarTemplateId(VarTemplate, fakeLoc, fakeLoc, TLI); + if (R.isInvalid()) { + // FIXME: Diagnose + } + return R.get(); + } + + // This will instantiate tape type and return it. + SourceLocation noLoc; + QualType TT = S.CheckTemplateIdType(TemplateName(TemplateD), noLoc, TLI); + + // Perhaps we can extract this into a new interface. + S.RequireCompleteType(fakeLoc, TT, diag::err_tentative_def_incomplete_type); + return GetScopeFromType(TT); + + // ASTContext &C = S.getASTContext(); + // // Get clad namespace and its identifier clad::. + // CXXScopeSpec CSS; + // CSS.Extend(C, GetCladNamespace(), noLoc, noLoc); + // NestedNameSpecifier* NS = CSS.getScopeRep(); + + // // Create elaborated type with namespace specifier, + // // i.e. class -> clad::class + // return C.getElaboratedType(ETK_None, NS, TT); + } + + Decl* InstantiateTemplate(TemplateDecl* TemplateD, + ArrayRef TemplateArgs, Sema& S) { + // Create a list of template arguments. + TemplateArgumentListInfo TLI{}; + for (auto TA : TemplateArgs) + TLI.addArgument(S.getTrivialTemplateArgumentLoc(TA,QualType(), + SourceLocation())); + + return InstantiateTemplate(TemplateD, TLI, S); + } + + TCppScope_t InstantiateTemplate(compat::Interpreter& I, TCppScope_t tmpl, + const TemplateArgInfo* template_args, + size_t template_args_size) { + auto& S = I.getSema(); + auto& C = S.getASTContext(); + + llvm::SmallVector TemplateArgs; + TemplateArgs.reserve(template_args_size); + for (size_t i = 0; i < template_args_size; ++i) { + QualType ArgTy = QualType::getFromOpaquePtr(template_args[i].m_Type); + if (template_args[i].m_IntegralValue) { + // We have a non-type template parameter. Create an integral value from + // the string representation. + auto Res = llvm::APSInt(template_args[i].m_IntegralValue); + Res = Res.extOrTrunc(C.getIntWidth(ArgTy)); + TemplateArgs.push_back(TemplateArgument(C, Res, ArgTy)); + } else { + TemplateArgs.push_back(ArgTy); + } + } + + TemplateDecl* TmplD = static_cast(tmpl); + + // We will create a new decl, push a transaction. +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&I); +#endif + return InstantiateTemplate(TmplD, TemplateArgs, S); + } + + TCppScope_t InstantiateTemplate(TCppScope_t tmpl, + const TemplateArgInfo* template_args, + size_t template_args_size) { + return InstantiateTemplate(getInterp(), tmpl, template_args, + template_args_size); + } + + void GetClassTemplateInstantiationArgs(TCppScope_t templ_instance, + std::vector &args) { + auto* CTSD = static_cast(templ_instance); + for(const auto& TA : CTSD->getTemplateInstantiationArgs().asArray()) { + switch (TA.getKind()) { + default: + assert(0 && "Not yet supported!"); + break; + case TemplateArgument::Pack: + for (auto SubTA : TA.pack_elements()) + args.push_back({SubTA.getAsType().getAsOpaquePtr()}); + break; + case TemplateArgument::Integral: + // FIXME: Support this case where the problem is where we provide the + // storage for the m_IntegralValue. + //llvm::APSInt Val = TA.getAsIntegral(); + //args.push_back({TA.getIntegralType(), TA.getAsIntegral()}) + //break; + case TemplateArgument::Type: + args.push_back({TA.getAsType().getAsOpaquePtr()}); + } + } + } + + TCppFunction_t + InstantiateTemplateFunctionFromString(const char* function_template) { + // FIXME: Drop this interface and replace it with the proper overload + // resolution handling and template instantiation selection. + + // Try to force template instantiation and overload resolution. + static unsigned long long var_count = 0; + std::string id = "__Cppyy_GetMethTmpl_" + std::to_string(var_count++); + std::string instance = "auto " + id + " = " + function_template + ";\n"; + + if (!Cpp::Declare(instance.c_str(), /*silent=*/false)) { + VarDecl* VD = (VarDecl*)Cpp::GetNamed(id, 0); + DeclRefExpr* DRE = (DeclRefExpr*)VD->getInit()->IgnoreImpCasts(); + return DRE->getDecl(); + } + return nullptr; + } + + void GetAllCppNames(TCppScope_t scope, std::set& names) { + auto *D = (clang::Decl *)scope; + clang::DeclContext *DC; + clang::DeclContext::decl_iterator decl; + + if (auto *TD = dyn_cast_or_null(D)) { + DC = clang::TagDecl::castToDeclContext(TD); + decl = DC->decls_begin(); + decl++; + } else if (auto *ND = dyn_cast_or_null(D)) { + DC = clang::NamespaceDecl::castToDeclContext(ND); + decl = DC->decls_begin(); + } else if (auto *TUD = dyn_cast_or_null(D)) { + DC = clang::TranslationUnitDecl::castToDeclContext(TUD); + decl = DC->decls_begin(); + } else { + return; + } + + for (/* decl set above */; decl != DC->decls_end(); decl++) { + if (auto *ND = llvm::dyn_cast_or_null(*decl)) { + names.insert(ND->getNameAsString()); + } + } + } + + void GetEnums(TCppScope_t scope, std::vector& Result) { + auto* D = static_cast(scope); + + if (!llvm::isa_and_nonnull(D)) + return; + + auto* DC = llvm::dyn_cast(D); + + llvm::SmallVector DCs; + DC->collectAllContexts(DCs); + + // FIXME: We should use a lookup based approach instead of brute force + for (auto* DC : DCs) { + for (auto decl = DC->decls_begin(); decl != DC->decls_end(); decl++) { + if (auto* ND = llvm::dyn_cast_or_null(*decl)) { + Result.push_back(ND->getNameAsString()); + } + } + } + } + + // FIXME: On the CPyCppyy side the receiver is of type + // vector instead of vector + std::vector GetDimensions(TCppType_t type) + { + QualType Qual = QualType::getFromOpaquePtr(type); + if (Qual.isNull()) + return {}; + Qual = Qual.getCanonicalType(); + std::vector dims; + if (Qual->isArrayType()) + { + const clang::ArrayType *ArrayType = dyn_cast(Qual.getTypePtr()); + while (ArrayType) + { + if (const auto *CAT = dyn_cast_or_null(ArrayType)) { + llvm::APSInt Size(CAT->getSize()); + long int ArraySize = Size.getLimitedValue(); + dims.push_back(ArraySize); + } else /* VariableArrayType, DependentSizedArrayType, IncompleteArrayType */ { + dims.push_back(DimensionValue::UNKNOWN_SIZE); + } + ArrayType = ArrayType->getElementType()->getAsArrayTypeUnsafe(); + } + return dims; + } + return dims; + } + + bool IsTypeDerivedFrom(TCppType_t derived, TCppType_t base) + { + auto &S = getSema(); + auto fakeLoc = GetValidSLoc(S); + auto derivedType = clang::QualType::getFromOpaquePtr(derived); + auto baseType = clang::QualType::getFromOpaquePtr(base); + +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif + return S.IsDerivedFrom(fakeLoc,derivedType,baseType); + } + + std::string GetFunctionArgDefault(TCppFunction_t func, + TCppIndex_t param_index) { + auto *D = (clang::Decl *)func; + clang::ParmVarDecl* PI = nullptr; + + if (auto* FD = llvm::dyn_cast_or_null(D)) + PI = FD->getParamDecl(param_index); + + else if (auto* FD = llvm::dyn_cast_or_null(D)) + PI = (FD->getTemplatedDecl())->getParamDecl(param_index); + + if (PI->hasDefaultArg()) + { + std::string Result; + llvm::raw_string_ostream OS(Result); + Expr *DefaultArgExpr = const_cast(PI->getDefaultArg()); + DefaultArgExpr->printPretty(OS, nullptr, PrintingPolicy(LangOptions())); + + // FIXME: Floats are printed in clang with the precision of their underlying representation + // and not as written. This is a deficiency in the printing mechanism of clang which we require + // extra work to mitigate. For example float PI = 3.14 is printed as 3.1400000000000001 + if (PI->getType()->isFloatingType()) + { + if (!Result.empty() && Result.back() == '.') + return Result; + auto DefaultArgValue = std::stod(Result); + std::ostringstream oss; + oss << DefaultArgValue; + Result = oss.str(); + } + return Result; + } + return ""; + } + + bool IsConstMethod(TCppFunction_t method) + { + if (!method) + return false; + + auto *D = (clang::Decl *)method; + if (auto *func = dyn_cast(D)) + return func->getMethodQualifiers().hasConst(); + + return false; + } + + std::string GetFunctionArgName(TCppFunction_t func, TCppIndex_t param_index) + { + auto *D = (clang::Decl *)func; + clang::ParmVarDecl* PI = nullptr; + + if (auto* FD = llvm::dyn_cast_or_null(D)) + PI = FD->getParamDecl(param_index); + else if (auto* FD = llvm::dyn_cast_or_null(D)) + PI = (FD->getTemplatedDecl())->getParamDecl(param_index); + + return PI->getNameAsString(); + } + + OperatorArity GetOperatorArity(TCppFunction_t op) { + Decl* D = static_cast(op); + if (auto* FD = llvm::dyn_cast(D)) { + if (FD->isOverloadedOperator()) { + switch (FD->getOverloadedOperator()) { +#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, \ + MemberOnly) \ + case OO_##Name: \ + if ((Unary) && (Binary)) \ + return kBoth; \ + if (Unary) \ + return kUnary; \ + if (Binary) \ + return kBinary; \ + break; +#include "clang/Basic/OperatorKinds.def" + default: + break; + } + } + } + return (OperatorArity)~0U; + } + + void GetOperator(TCppScope_t scope, Operator op, + std::vector& operators, OperatorArity kind) { + Decl* D = static_cast(scope); + if (auto* DC = llvm::dyn_cast_or_null(D)) { + ASTContext& C = getSema().getASTContext(); + DeclContextLookupResult Result = + DC->lookup(C.DeclarationNames.getCXXOperatorName( + (clang::OverloadedOperatorKind)op)); + + for (auto* i : Result) { + if (kind & GetOperatorArity(i)) + operators.push_back(i); + } + } + } + + TCppObject_t Allocate(TCppScope_t scope) { + return (TCppObject_t)::operator new(Cpp::SizeOf(scope)); + } + + void Deallocate(TCppScope_t scope, TCppObject_t address) { + ::operator delete(address); + } + + // FIXME: Add optional arguments to the operator new. + TCppObject_t Construct(compat::Interpreter& interp, TCppScope_t scope, + void* arena /*=nullptr*/) { + auto* Class = (Decl*) scope; + // FIXME: Diagnose. + if (!HasDefaultConstructor(Class)) + return nullptr; + + auto* const Ctor = GetDefaultConstructor(Class); + if (JitCall JC = MakeFunctionCallable(&interp, Ctor)) { + if (arena) { + JC.Invoke(&arena, {}, (void*)~0); // Tell Invoke to use placement new. + return arena; + } + + void *obj = nullptr; + JC.Invoke(&obj); + return obj; + } + return nullptr; + } + + TCppObject_t Construct(TCppScope_t scope, void* arena /*=nullptr*/) { + return Construct(getInterp(), scope, arena); + } + + void Destruct(compat::Interpreter& interp, TCppObject_t This, Decl* Class, + bool withFree) { + if (auto wrapper = make_dtor_wrapper(interp, Class)) { + (*wrapper)(This, /*nary=*/0, withFree); + return; + } + // FIXME: Diagnose. + } + + void Destruct(TCppObject_t This, TCppScope_t scope, bool withFree /*=true*/) { + auto* Class = static_cast(scope); + Destruct(getInterp(), This, Class, withFree); + } + + class StreamCaptureInfo { + struct file_deleter { + void operator()(FILE* fp) { pclose(fp); } + }; + std::unique_ptr m_TempFile; + int m_FD = -1; + int m_DupFD = -1; + + public: +#ifdef _MSC_VER + StreamCaptureInfo(int FD) + : m_TempFile{[]() { + FILE* stream = nullptr; + errno_t err; + err = tmpfile_s(&stream); + if (err) + printf("Cannot create temporary file!\n"); + return stream; + }()}, + m_FD(FD) { +#else + StreamCaptureInfo(int FD) : m_TempFile{tmpfile()}, m_FD(FD) { +#endif + if (!m_TempFile) { + perror("StreamCaptureInfo: Unable to create temp file"); + return; + } + + m_DupFD = dup(FD); + + // Flush now or can drop the buffer when dup2 is called with Fd later. + // This seems only neccessary when piping stdout or stderr, but do it + // for ttys to avoid over complicated code for minimal benefit. + ::fflush(FD == STDOUT_FILENO ? stdout : stderr); + if (dup2(fileno(m_TempFile.get()), FD) < 0) + perror("StreamCaptureInfo:"); + } + StreamCaptureInfo(const StreamCaptureInfo&) = delete; + StreamCaptureInfo& operator=(const StreamCaptureInfo&) = delete; + StreamCaptureInfo(StreamCaptureInfo&&) = delete; + StreamCaptureInfo& operator=(StreamCaptureInfo&&) = delete; + + ~StreamCaptureInfo() { + assert(m_DupFD == -1 && "Captured output not used?"); + } + + std::string GetCapturedString() { + assert(m_DupFD != -1 && "Multiple calls to GetCapturedString"); + + fflush(nullptr); + if (dup2(m_DupFD, m_FD) < 0) + perror("StreamCaptureInfo:"); + // Go to the end of the file. + if (fseek(m_TempFile.get(), 0L, SEEK_END) != 0) + perror("StreamCaptureInfo:"); + + // Get the size of the file. + long bufsize = ftell(m_TempFile.get()); + if (bufsize == -1) + perror("StreamCaptureInfo:"); + + // Allocate our buffer to that size. + std::unique_ptr content(new char[bufsize + 1]); + + // Go back to the start of the file. + if (fseek(m_TempFile.get(), 0L, SEEK_SET) != 0) + perror("StreamCaptureInfo:"); + + // Read the entire file into memory. + size_t newLen = + fread(content.get(), sizeof(char), bufsize, m_TempFile.get()); + if (ferror(m_TempFile.get()) != 0) + fputs("Error reading file", stderr); + else + content[newLen++] = '\0'; // Just to be safe. + + std::string result = content.get(); + close(m_DupFD); + m_DupFD = -1; + return result; + } + }; + + static std::stack& GetRedirectionStack() { + static std::stack sRedirectionStack; + return sRedirectionStack; + } + + void BeginStdStreamCapture(CaptureStreamKind fd_kind) { + GetRedirectionStack().emplace((int)fd_kind); + } + + std::string EndStdStreamCapture() { + assert(GetRedirectionStack().size()); + StreamCaptureInfo& SCI = GetRedirectionStack().top(); + std::string result = SCI.GetCapturedString(); + GetRedirectionStack().pop(); + return result; + } + + void CodeComplete(std::vector& Results, const char* code, + unsigned complete_line /* = 1U */, + unsigned complete_column /* = 1U */) { + compat::codeComplete(Results, getInterp(), code, complete_line, + complete_column); + } + + } // end namespace Cpp diff --git a/interpreter/CppInterOp/lib/Interpreter/CppInterOpInterpreter.h b/interpreter/CppInterOp/lib/Interpreter/CppInterOpInterpreter.h new file mode 100644 index 0000000000000..e358406335449 --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/CppInterOpInterpreter.h @@ -0,0 +1,435 @@ +//--------------------------------------------------------------------*- C++ -*- +// CppInterOp Interpreter (clang-repl) +// author: Alexander Penev +//------------------------------------------------------------------------------ + +#ifndef CPPINTEROP_INTERPRETER_H +#define CPPINTEROP_INTERPRETER_H + +#include "Compatibility.h" +#include "DynamicLibraryManager.h" +#include "Paths.h" + +#include "clang/Interpreter/Interpreter.h" +#include "clang/Interpreter/PartialTranslationUnit.h" + +#include "clang/AST/Decl.h" +#include "clang/AST/DeclarationName.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendOptions.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Sema/Lookup.h" +#include "clang/Sema/Sema.h" +#if CLANG_VERSION_MAJOR >= 19 +#include "clang/Sema/Redeclaration.h" +#endif + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ExecutionEngine/Orc/LLJIT.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/TargetSelect.h" + +namespace clang { +class CompilerInstance; +} + +namespace { +template static D* LookupResult2Decl(clang::LookupResult& R) { + if (R.empty()) + return nullptr; + + R.resolveKind(); + + if (R.isSingleResult()) + return llvm::dyn_cast(R.getFoundDecl()); + return (D*)-1; +} +} // namespace + +namespace Cpp { +namespace utils { +namespace Lookup { + +inline clang::NamespaceDecl* Namespace(clang::Sema* S, const char* Name, + const clang::DeclContext* Within) { + clang::DeclarationName DName = &(S->Context.Idents.get(Name)); + clang::LookupResult R(*S, DName, clang::SourceLocation(), + clang::Sema::LookupNestedNameSpecifierName); + R.suppressDiagnostics(); + if (!Within) + S->LookupName(R, S->TUScope); + else { + if (const clang::TagDecl* TD = llvm::dyn_cast(Within)) { + if (!TD->getDefinition()) { + // No definition, no lookup result. + return nullptr; + } + } + S->LookupQualifiedName(R, const_cast(Within)); + } + + if (R.empty()) + return nullptr; + + R.resolveKind(); + + return llvm::dyn_cast(R.getFoundDecl()); +} + +inline void Named(clang::Sema* S, clang::LookupResult& R, + const clang::DeclContext* Within = nullptr) { + R.suppressDiagnostics(); + if (!Within) + S->LookupName(R, S->TUScope); + else { + const clang::DeclContext* primaryWithin = nullptr; + if (const clang::TagDecl* TD = llvm::dyn_cast(Within)) { + primaryWithin = + llvm::dyn_cast_or_null(TD->getDefinition()); + } else { + primaryWithin = Within->getPrimaryContext(); + } + if (!primaryWithin) { + // No definition, no lookup result. + return; + } + S->LookupQualifiedName(R, const_cast(primaryWithin)); + } +} + +inline clang::NamedDecl* Named(clang::Sema* S, + const clang::DeclarationName& Name, + const clang::DeclContext* Within = nullptr) { + clang::LookupResult R(*S, Name, clang::SourceLocation(), + clang::Sema::LookupOrdinaryName, + Clang_For_Visible_Redeclaration); + Named(S, R, Within); + return LookupResult2Decl(R); +} + +inline clang::NamedDecl* Named(clang::Sema* S, llvm::StringRef Name, + const clang::DeclContext* Within = nullptr) { + clang::DeclarationName DName = &S->Context.Idents.get(Name); + return Named(S, DName, Within); +} + +inline clang::NamedDecl* Named(clang::Sema* S, const char* Name, + const clang::DeclContext* Within = nullptr) { + return Named(S, llvm::StringRef(Name), Within); +} + +} // namespace Lookup +} // namespace utils +} // namespace Cpp + +namespace Cpp { + +/// CppInterOp Interpreter +/// +class Interpreter { +private: + std::unique_ptr inner; + +public: + Interpreter(int argc, const char* const* argv, const char* llvmdir = 0, + const std::vector>& + moduleExtensions = {}, + void* extraLibHandle = 0, bool noRuntime = true) { + // Initialize all targets (required for device offloading) + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargets(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmPrinters(); + + std::vector vargs(argv + 1, argv + argc); + vargs.push_back("-include"); + vargs.push_back("new"); + inner = compat::createClangInterpreter(vargs); + } + + ~Interpreter() {} + + operator const clang::Interpreter&() const { return *inner; } + operator clang::Interpreter&() { return *inner; } + + ///\brief Describes the return result of the different routines that do the + /// incremental compilation. + /// + enum CompilationResult { kSuccess, kFailure, kMoreInputExpected }; + + const clang::CompilerInstance* getCompilerInstance() const { + return inner->getCompilerInstance(); + } + + const llvm::orc::LLJIT* getExecutionEngine() const { + return compat::getExecutionEngine(*inner); + } + + llvm::Expected Parse(llvm::StringRef Code) { + return inner->Parse(Code); + } + + llvm::Error Execute(clang::PartialTranslationUnit& T) { + return inner->Execute(T); + } + + llvm::Error ParseAndExecute(llvm::StringRef Code, clang::Value* V = nullptr) { + return inner->ParseAndExecute(Code, V); + } + + llvm::Error Undo(unsigned N = 1) { return compat::Undo(*inner, N); } + + void makeEngineOnce() const { + static bool make_engine_once = true; + if (make_engine_once) { + if (auto Err = inner->ParseAndExecute("")) + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "Error:"); + make_engine_once = false; + } + } + + /// \returns the \c ExecutorAddr of a \c GlobalDecl. This interface uses + /// the CodeGenModule's internal mangling cache to avoid recomputing the + /// mangled name. + llvm::Expected + getSymbolAddress(clang::GlobalDecl GD) const { + makeEngineOnce(); + auto AddrOrErr = compat::getSymbolAddress(*inner, GD); + if (llvm::Error Err = AddrOrErr.takeError()) + return std::move(Err); + return llvm::orc::ExecutorAddr(*AddrOrErr); + } + + /// \returns the \c ExecutorAddr of a given name as written in the IR. + llvm::Expected + getSymbolAddress(llvm::StringRef IRName) const { + makeEngineOnce(); + auto AddrOrErr = compat::getSymbolAddress(*inner, IRName); + if (llvm::Error Err = AddrOrErr.takeError()) + return std::move(Err); + return llvm::orc::ExecutorAddr(*AddrOrErr); + } + + /// \returns the \c ExecutorAddr of a given name as written in the object + /// file. + llvm::Expected + getSymbolAddressFromLinkerName(llvm::StringRef LinkerName) const { + auto AddrOrErr = compat::getSymbolAddressFromLinkerName(*inner, LinkerName); + if (llvm::Error Err = AddrOrErr.takeError()) + return std::move(Err); + return llvm::orc::ExecutorAddr(*AddrOrErr); + } + + bool isInSyntaxOnlyMode() const { + return getCompilerInstance()->getFrontendOpts().ProgramAction == + clang::frontend::ParseSyntaxOnly; + } + + // FIXME: Mangle GD and call the other overload. + void* getAddressOfGlobal(const clang::GlobalDecl& GD) const { + auto addressOrErr = getSymbolAddress(GD); + if (addressOrErr) + return addressOrErr->toPtr(); + + llvm::consumeError(addressOrErr.takeError()); // okay to be missing + return nullptr; + } + + void* getAddressOfGlobal(llvm::StringRef SymName) const { + if (isInSyntaxOnlyMode()) + return nullptr; + + auto addressOrErr = + getSymbolAddressFromLinkerName(SymName); // TODO: Or getSymbolAddress + if (addressOrErr) + return addressOrErr->toPtr(); + + llvm::consumeError(addressOrErr.takeError()); // okay to be missing + return nullptr; + } + + CompilationResult declare(const std::string& input, + clang::PartialTranslationUnit** PTU = nullptr) { + return process(input, /*Value=*/nullptr, PTU); + } + + ///\brief Maybe transform the input line to implement cint command line + /// semantics (declarations are global) and compile to produce a module. + /// + CompilationResult process(const std::string& input, clang::Value* V = 0, + clang::PartialTranslationUnit** PTU = nullptr, + bool disableValuePrinting = false) { + auto PTUOrErr = Parse(input); + if (!PTUOrErr) { + llvm::logAllUnhandledErrors(PTUOrErr.takeError(), llvm::errs(), + "Failed to parse via ::process:"); + return Interpreter::kFailure; + } + + if (PTU) + *PTU = &*PTUOrErr; + + if (auto Err = Execute(*PTUOrErr)) { + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "Failed to execute via ::process:"); + return Interpreter::kFailure; + } + return Interpreter::kSuccess; + } + + CompilationResult evaluate(const std::string& input, clang::Value& V) { + if (auto Err = ParseAndExecute(input, &V)) { + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "Failed to execute via ::evaluate:"); + return Interpreter::kFailure; + } + return Interpreter::kSuccess; + } + + void* compileFunction(llvm::StringRef name, llvm::StringRef code, + bool ifUnique, bool withAccessControl) { + // + // Compile the wrapper code. + // + + if (isInSyntaxOnlyMode()) + return nullptr; + + if (ifUnique) { + if (void* Addr = (void*)getAddressOfGlobal(name)) { + return Addr; + } + } + + clang::LangOptions& LO = + const_cast(getCompilerInstance()->getLangOpts()); + bool SavedAccessControl = LO.AccessControl; + LO.AccessControl = withAccessControl; + + if (auto Err = ParseAndExecute(code)) { + LO.AccessControl = SavedAccessControl; + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "Failed to compileFunction: "); + return nullptr; + } + + LO.AccessControl = SavedAccessControl; + + return getAddressOfGlobal(name); + } + + const clang::CompilerInstance* getCI() const { return getCompilerInstance(); } + + clang::Sema& getSema() const { return getCI()->getSema(); } + + 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(); + } + + DynamicLibraryManager* getDynamicLibraryManager() { + return const_cast( + const_cast(this)->getDynamicLibraryManager()); + } + + ///\brief Adds multiple include paths separated by a delimter. + /// + ///\param[in] PathsStr - Path(s) + ///\param[in] Delim - Delimiter to separate paths or NULL if a single path + /// + void AddIncludePaths(llvm::StringRef PathsStr, const char* Delim = ":") { + const clang::CompilerInstance* CI = getCompilerInstance(); + clang::HeaderSearchOptions& HOpts = + const_cast(CI->getHeaderSearchOpts()); + + // Save the current number of entries + size_t Idx = HOpts.UserEntries.size(); + Cpp::utils::AddIncludePaths(PathsStr, HOpts, Delim); + + clang::Preprocessor& PP = CI->getPreprocessor(); + clang::SourceManager& SM = PP.getSourceManager(); + clang::FileManager& FM = SM.getFileManager(); + clang::HeaderSearch& HSearch = PP.getHeaderSearchInfo(); + const bool isFramework = false; + + // Add all the new entries into Preprocessor + for (const size_t N = HOpts.UserEntries.size(); Idx < N; ++Idx) { + const clang::HeaderSearchOptions::Entry& E = HOpts.UserEntries[Idx]; + if (auto DE = FM.getOptionalDirectoryRef(E.Path)) + HSearch.AddSearchPath( + clang::DirectoryLookup(*DE, clang::SrcMgr::C_User, isFramework), + E.Group == clang::frontend::Angled); + } + } + + ///\brief Adds a single include path (-I). + /// + void AddIncludePath(llvm::StringRef PathsStr) { + return AddIncludePaths(PathsStr, nullptr); + } + + ///\brief Get the current include paths that are used. + /// + ///\param[out] incpaths - Pass in a llvm::SmallVector with + /// sufficiently sized N, to hold the result of the call. + ///\param[in] withSystem - if true, incpaths will also contain system + /// include paths (framework, STL etc). + ///\param[in] withFlags - if true, each element in incpaths will be prefixed + /// with a "-I" or similar, and some entries of incpaths will signal + /// a new include path region (e.g. "-cxx-isystem"). Also, flags + /// defining header search behavior will be included in incpaths, e.g. + /// "-nostdinc". + /// + void GetIncludePaths(llvm::SmallVectorImpl& incpaths, + bool withSystem, bool withFlags) const { + utils::CopyIncludePaths(getCI()->getHeaderSearchOpts(), incpaths, + withSystem, withFlags); + } + + CompilationResult loadLibrary(const std::string& filename, bool lookup) { + DynamicLibraryManager* DLM = getDynamicLibraryManager(); + std::string canonicalLib; + if (lookup) + canonicalLib = DLM->lookupLibrary(filename); + + const std::string& library = lookup ? canonicalLib : filename; + if (!library.empty()) { + switch ( + DLM->loadLibrary(library, /*permanent*/ false, /*resolved*/ true)) { + case DynamicLibraryManager::kLoadLibSuccess: // Intentional fall through + case DynamicLibraryManager::kLoadLibAlreadyLoaded: + return kSuccess; + case DynamicLibraryManager::kLoadLibNotFound: + assert(0 && "Cannot find library with existing canonical name!"); + return kFailure; + default: + // Not a source file (canonical name is non-empty) but can't load. + return kFailure; + } + } + return kMoreInputExpected; + } + + std::string toString(const char* type, void* obj) { + assert(0 && "toString is not implemented!"); + std::string ret; + return ret; // TODO: Implement + } + +}; // Interpreter +} // namespace Cpp + +#endif // CPPINTEROP_INTERPRETER_H diff --git a/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.cpp b/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.cpp new file mode 100644 index 0000000000000..630ad23a6e5f1 --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.cpp @@ -0,0 +1,509 @@ +//------------------------------------------------------------------------------ +// CLING - the C++ LLVM-based InterpreterG :) +// author: Vassil Vassilev +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#include "DynamicLibraryManager.h" +#include "Compatibility.h" +#include "Paths.h" + +#include "llvm/ADT/StringSet.h" +#include "llvm/BinaryFormat/Magic.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +#if defined(_WIN32) +#include "llvm/BinaryFormat/COFF.h" +#include "llvm/Support/Endian.h" +#endif + +#include +#include +#include + +namespace Cpp { + + using namespace Cpp::utils::platform; + using namespace Cpp::utils; + using namespace llvm; + + DynamicLibraryManager::DynamicLibraryManager() { + const SmallVector kSysLibraryEnv = { + "LD_LIBRARY_PATH", + #if __APPLE__ + "DYLD_LIBRARY_PATH", + "DYLD_FALLBACK_LIBRARY_PATH", + /* + "DYLD_VERSIONED_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", + "DYLD_FALLBACK_FRAMEWORK_PATH", + "DYLD_VERSIONED_FRAMEWORK_PATH", + */ + #elif defined(_WIN32) + "PATH", + #endif + }; + + // Behaviour is to not add paths that don't exist...In an interpreted env + // does this make sense? Path could pop into existance at any time. + for (const char* Var : kSysLibraryEnv) { + if (const char* Env = GetEnv(Var)) { + SmallVector CurPaths; + SplitPaths(Env, CurPaths, utils::kPruneNonExistant, Cpp::utils::platform::kEnvDelim); + for (const auto& Path : CurPaths) + addSearchPath(Path); + } + } + + // $CWD is the last user path searched. + addSearchPath("."); + + SmallVector SysPaths; + Cpp::utils::platform::GetSystemLibraryPaths(SysPaths); + + for (const std::string& P : SysPaths) + addSearchPath(P, /*IsUser*/ false); + } + ///\returns substitution of pattern in the front of original with replacement + /// Example: substFront("@rpath/abc", "@rpath/", "/tmp") -> "/tmp/abc" + static std::string substFront(StringRef original, StringRef pattern, + StringRef replacement) { + if (!original.starts_with_insensitive(pattern)) + return original.str(); + SmallString<512> result(replacement); + result.append(original.drop_front(pattern.size())); + return result.str().str(); + } + + ///\returns substitution of all known linker variables in \c original + static std::string substAll(StringRef original, + StringRef libLoader) { + + // Handle substitutions (MacOS): + // @rpath - This function does not substitute @rpath, becouse + // this variable is already handled by lookupLibrary where + // @rpath is replaced with all paths from RPATH one by one. + // @executable_path - Main program path. + // @loader_path - Loader library (or main program) path. + // + // Handle substitutions (Linux): + // https://man7.org/linux/man-pages/man8/ld.so.8.html + // $origin - Loader library (or main program) path. + // $lib - lib lib64 + // $platform - x86_64 AT_PLATFORM + + std::string result; +#ifdef __APPLE__ + SmallString<512> mainExecutablePath(llvm::sys::fs::getMainExecutable(nullptr, nullptr)); + llvm::sys::path::remove_filename(mainExecutablePath); + SmallString<512> loaderPath; + if (libLoader.empty()) { + loaderPath = mainExecutablePath; + } else { + loaderPath = libLoader.str(); + llvm::sys::path::remove_filename(loaderPath); + } + + result = substFront(original, "@executable_path", mainExecutablePath); + result = substFront(result, "@loader_path", loaderPath); + return result; +#else + SmallString<512> loaderPath; + if (libLoader.empty()) { + loaderPath = llvm::sys::fs::getMainExecutable(nullptr, nullptr); + } else { + loaderPath = libLoader.str(); + } + llvm::sys::path::remove_filename(loaderPath); + + result = substFront(original, "$origin", loaderPath); + //result = substFront(result, "$lib", true?"lib":"lib64"); + //result = substFront(result, "$platform", "x86_64"); + return result; +#endif + } + + std::string + DynamicLibraryManager::lookupLibInPaths(StringRef libStem, + SmallVector RPath /*={}*/, + SmallVector RunPath /*={}*/, + StringRef libLoader /*=""*/) const { +#define DEBUG_TYPE "Dyld::lookupLibInPaths" + + LLVM_DEBUG(dbgs() << "Dyld::lookupLibInPaths" << libStem.str() + << ", ..., libLoader=" << libLoader << "\n"); + + // Lookup priority is: RPATH, LD_LIBRARY_PATH/m_SearchPaths, RUNPATH + // See: https://en.wikipedia.org/wiki/Rpath + // See: https://amir.rachum.com/blog/2016/09/17/shared-libraries/ + + LLVM_DEBUG(dbgs() << "Dyld::lookupLibInPaths: \n"); + LLVM_DEBUG(dbgs() << ":: RPATH\n"); + for (auto Info : RPath) { + LLVM_DEBUG(dbgs() << ":::: " << Info.str() << "\n"); + } + LLVM_DEBUG(dbgs() << ":: SearchPaths (LD_LIBRARY_PATH, etc...)\n"); + for (auto Info : getSearchPaths()) { + LLVM_DEBUG(dbgs() << ":::: " << Info.Path << ", user=" << (Info.IsUser?"true":"false") << "\n"); + } + LLVM_DEBUG(dbgs() << ":: RUNPATH\n"); + for (auto Info : RunPath) { + LLVM_DEBUG(dbgs() << ":::: " << Info.str() << "\n"); + } + + SmallString<512> ThisPath; + // RPATH + for (auto Info : RPath) { + ThisPath = substAll(Info, libLoader); + llvm::sys::path::append(ThisPath, libStem); + // to absolute path? + LLVM_DEBUG(dbgs() << "## Try: " << ThisPath); + if (isSharedLibrary(ThisPath.str())) { + LLVM_DEBUG(dbgs() << " ... Found (in RPATH)!\n"); + return ThisPath.str().str(); + } + } + // m_SearchPaths + for (const SearchPathInfo& Info : m_SearchPaths) { + ThisPath = Info.Path; + llvm::sys::path::append(ThisPath, libStem); + // to absolute path? + LLVM_DEBUG(dbgs() << "## Try: " << ThisPath); + if (isSharedLibrary(ThisPath.str())) { + LLVM_DEBUG(dbgs() << " ... Found (in SearchPaths)!\n"); + return ThisPath.str().str(); + } + } + // RUNPATH + for (auto Info : RunPath) { + ThisPath = substAll(Info, libLoader); + llvm::sys::path::append(ThisPath, libStem); + // to absolute path? + LLVM_DEBUG(dbgs() << "## Try: " << ThisPath); + if (isSharedLibrary(ThisPath.str())) { + LLVM_DEBUG(dbgs() << " ... Found (in RUNPATH)!\n"); + return ThisPath.str().str(); + } + } + + LLVM_DEBUG(dbgs() << "## NotFound!!!\n"); + + return ""; + +#undef DEBUG_TYPE + } + + std::string + DynamicLibraryManager::lookupLibMaybeAddExt(StringRef libStem, + SmallVector RPath /*={}*/, + SmallVector RunPath /*={}*/, + StringRef libLoader /*=""*/) const { +#define DEBUG_TYPE "Dyld::lookupLibMaybeAddExt:" + + using namespace llvm::sys; + + LLVM_DEBUG(dbgs() << "Dyld::lookupLibMaybeAddExt: " << libStem.str() + << ", ..., libLoader=" << libLoader << "\n"); + + std::string foundDyLib = lookupLibInPaths(libStem, RPath, RunPath, libLoader); + + if (foundDyLib.empty()) { + // Add DyLib extension: + SmallString<512> filenameWithExt(libStem); +#if defined(LLVM_ON_UNIX) +#ifdef __APPLE__ + SmallString<512>::iterator IStemEnd = filenameWithExt.end() - 1; +#endif + static const char* DyLibExt = ".so"; +#elif defined(_WIN32) + static const char* DyLibExt = ".dll"; +#else +# error "Unsupported platform." +#endif + filenameWithExt += DyLibExt; + foundDyLib = lookupLibInPaths(filenameWithExt, RPath, RunPath, libLoader); +#ifdef __APPLE__ + if (foundDyLib.empty()) { + filenameWithExt.erase(IStemEnd + 1, filenameWithExt.end()); + filenameWithExt += ".dylib"; + foundDyLib = lookupLibInPaths(filenameWithExt, RPath, RunPath, libLoader); + } +#endif + } + + if (foundDyLib.empty()) + return std::string(); + + // get canonical path name and check if already loaded + const std::string Path = platform::NormalizePath(foundDyLib); + if (Path.empty()) { + LLVM_DEBUG(dbgs() << "cling::DynamicLibraryManager::lookupLibMaybeAddExt(): " + << "error getting real (canonical) path of library " << foundDyLib << '\n'); + return foundDyLib; + } + return Path; + +#undef DEBUG_TYPE + } + + std::string DynamicLibraryManager::normalizePath(StringRef path) { +#define DEBUG_TYPE "Dyld::normalizePath:" + // Make the path canonical if the file exists. + const std::string Path = path.str(); + struct stat buffer; + if (::stat(Path.c_str(), &buffer) != 0) + return std::string(); + + const std::string NPath = platform::NormalizePath(Path); + if (NPath.empty()) + LLVM_DEBUG(dbgs() << "Could not normalize: '" << Path << "'"); + return NPath; +#undef DEBUG_TYPE + } + + std::string RPathToStr2(SmallVector V) { + std::string result; + for (auto item : V) + result += item.str() + ","; + if (!result.empty()) + result.pop_back(); + return result; + } + + std::string + DynamicLibraryManager::lookupLibrary(StringRef libStem, + SmallVector RPath /*={}*/, + SmallVector RunPath /*={}*/, + StringRef libLoader /*=""*/, + bool variateLibStem /*=true*/) const { +#define DEBUG_TYPE "Dyld::lookupLibrary:" + LLVM_DEBUG(dbgs() << "Dyld::lookupLibrary: " << libStem.str() << ", " + << RPathToStr2(RPath) << ", " << RPathToStr2(RunPath) + << ", " << libLoader.str() << "\n"); + + // If it is an absolute path, don't try iterate over the paths. + if (llvm::sys::path::is_absolute(libStem)) { + if (isSharedLibrary(libStem)) + return normalizePath(libStem); + + LLVM_DEBUG(dbgs() << "Dyld::lookupLibrary: '" << libStem.str() << "'" + << "is not a shared library\n"); + return std::string(); + } + + // Subst all known linker variables ($origin, @rpath, etc.) +#ifdef __APPLE__ + // On MacOS @rpath is preplaced by all paths in RPATH one by one. + if (libStem.starts_with_insensitive("@rpath")) { + for (auto& P : RPath) { + std::string result = substFront(libStem, "@rpath", P); + if (isSharedLibrary(result)) + return normalizePath(result); + } + } else { +#endif + std::string result = substAll(libStem, libLoader); + if (isSharedLibrary(result)) + return normalizePath(result); +#ifdef __APPLE__ + } +#endif + + // Expand libStem with paths, extensions, etc. + std::string foundName; + if (variateLibStem) { + foundName = lookupLibMaybeAddExt(libStem, RPath, RunPath, libLoader); + if (foundName.empty()) { + StringRef libStemName = llvm::sys::path::filename(libStem); + if (!libStemName.starts_with("lib")) { + // try with "lib" prefix: + foundName = lookupLibMaybeAddExt( + libStem.str().insert(libStem.size()-libStemName.size(), "lib"), + RPath, + RunPath, + libLoader + ); + } + } + } else { + foundName = lookupLibInPaths(libStem, RPath, RunPath, libLoader); + } + + if (!foundName.empty()) + return platform::NormalizePath(foundName); + + return std::string(); +#undef DEBUG_TYPE + } + + DynamicLibraryManager::LoadLibResult + DynamicLibraryManager::loadLibrary(StringRef libStem, + bool permanent, bool resolved) { +#define DEBUG_TYPE "Dyld::loadLibrary:" + LLVM_DEBUG(dbgs() << "Dyld::loadLibrary: " << libStem.str() << ", " + << (permanent ? "permanent" : "not-permanent") << ", " + << (resolved ? "resolved" : "not-resolved") << "\n"); + + std::string canonicalLoadedLib; + if (resolved) { + canonicalLoadedLib = libStem.str(); + } else { + canonicalLoadedLib = lookupLibrary(libStem); + if (canonicalLoadedLib.empty()) + return kLoadLibNotFound; + } + + if (m_LoadedLibraries.find(canonicalLoadedLib) != m_LoadedLibraries.end()) + return kLoadLibAlreadyLoaded; + + // TODO: !permanent case + + std::string errMsg; + DyLibHandle dyLibHandle = platform::DLOpen(canonicalLoadedLib, &errMsg); + if (!dyLibHandle) { + // We emit callback to LibraryLoadingFailed when we get error with error message. + //TODO: Implement callbacks + + LLVM_DEBUG(dbgs() << "DynamicLibraryManager::loadLibrary(): " << errMsg); + + return kLoadLibLoadError; + } + + std::pair insRes + = m_DyLibs.insert(std::pair(dyLibHandle, + canonicalLoadedLib)); + if (!insRes.second) + return kLoadLibAlreadyLoaded; + m_LoadedLibraries.insert(canonicalLoadedLib); + return kLoadLibSuccess; +#undef DEBUG_TYPE + } + + void DynamicLibraryManager::unloadLibrary(StringRef libStem) { +#define DEBUG_TYPE "Dyld::unloadLibrary:" + std::string canonicalLoadedLib = lookupLibrary(libStem); + if (!isLibraryLoaded(canonicalLoadedLib)) + return; + + DyLibHandle dyLibHandle = nullptr; + for (DyLibs::const_iterator I = m_DyLibs.begin(), E = m_DyLibs.end(); + I != E; ++I) { + if (I->second == canonicalLoadedLib) { + dyLibHandle = I->first; + break; + } + } + + // TODO: !permanent case + + std::string errMsg; + platform::DLClose(dyLibHandle, &errMsg); + if (!errMsg.empty()) { + LLVM_DEBUG(dbgs() << "cling::DynamicLibraryManager::unloadLibrary(): " + << errMsg << '\n'); + } + + //TODO: Implement callbacks + + m_DyLibs.erase(dyLibHandle); + m_LoadedLibraries.erase(canonicalLoadedLib); +#undef DEBUG_TYPE + } + + bool DynamicLibraryManager::isLibraryLoaded(StringRef fullPath) const { + std::string canonPath = normalizePath(fullPath); + if (m_LoadedLibraries.find(canonPath) != m_LoadedLibraries.end()) + return true; + return false; + } + + void DynamicLibraryManager::dump(llvm::raw_ostream* S /*= nullptr*/) const { + llvm::raw_ostream &OS = S ? *S : llvm::outs(); + + // FIXME: print in a stable order the contents of m_SearchPaths + for (const auto& Info : getSearchPaths()) { + if (!Info.IsUser) + OS << "[system] "; + OS << Info.Path.c_str() << "\n"; + } + } + +#if defined(_WIN32) + static bool IsDLL(llvm::StringRef headers) { + using namespace llvm::support::endian; + + uint32_t headeroffset = read32le(headers.data() + 0x3c); + auto peheader = headers.substr(headeroffset, 24); + if (peheader.size() != 24) { + return false; + } + // Read Characteristics from the coff header + uint32_t characteristics = read16le(peheader.data() + 22); + return (characteristics & llvm::COFF::IMAGE_FILE_DLL) != 0; + } +#endif + + bool DynamicLibraryManager::isSharedLibrary(StringRef libFullPath, + bool* exists /*=0*/) { + using namespace llvm; + + auto filetype = sys::fs::get_file_type(libFullPath, /*Follow*/ true); + if (filetype != sys::fs::file_type::regular_file) { + if (exists) { + // get_file_type returns status_error also in case of file_not_found. + *exists = filetype != sys::fs::file_type::status_error; + } + return false; + } + + // Do not use the identify_magic overload taking a path: It will open the + // file and then mmap its contents, possibly causing bus errors when another + // process truncates the file while we are trying to read it. Instead just + // read the first 1024 bytes, which should be enough for identify_magic to + // do its work. + // TODO: Fix the code upstream and consider going back to calling the + // convenience function after a future LLVM upgrade. + std::string path = libFullPath.str(); + std::ifstream in(path, std::ios::binary); + char header[1024] = {0}; + in.read(header, sizeof(header)); + if (in.fail()) { + if (exists) + *exists = false; + return false; + } + + StringRef headerStr(header, in.gcount()); + file_magic Magic = identify_magic(headerStr); + + bool result = +#ifdef __APPLE__ + (Magic == file_magic::macho_fixed_virtual_memory_shared_lib + || Magic == file_magic::macho_dynamically_linked_shared_lib + || Magic == file_magic::macho_dynamically_linked_shared_lib_stub + || Magic == file_magic::macho_universal_binary) +#elif defined(LLVM_ON_UNIX) +#ifdef __CYGWIN__ + (Magic == file_magic::pecoff_executable) +#else + (Magic == file_magic::elf_shared_object) +#endif +#elif defined(_WIN32) + // We should only include dll libraries without including executables, + // object code and others... + (Magic == file_magic::pecoff_executable && IsDLL(headerStr)) +#else +# error "Unsupported platform." +#endif + ; + + return result; + } + +} // end namespace Cpp diff --git a/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.h b/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.h new file mode 100644 index 0000000000000..5ed6a7e7407e4 --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.h @@ -0,0 +1,224 @@ +//--------------------------------------------------------------------*- C++ -*- +// CLING - the C++ LLVM-based InterpreterG :) +// author: Vassil Vassilev +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#ifndef CPPINTEROP_DYNAMIC_LIBRARY_MANAGER_H +#define CPPINTEROP_DYNAMIC_LIBRARY_MANAGER_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Path.h" + +namespace Cpp { +class Dyld; +class InterpreterCallbacks; + +///\brief A helper class managing dynamic shared objects. +/// +class DynamicLibraryManager { +public: + ///\brief Describes the result of loading a library. + /// + enum LoadLibResult { + kLoadLibSuccess, ///< library loaded successfully + kLoadLibAlreadyLoaded, ///< library was already loaded + kLoadLibNotFound, ///< library was not found + kLoadLibLoadError, ///< loading the library failed + kLoadLibNumResults + }; + + /// Describes the library search paths. + struct SearchPathInfo { + /// The search path. + /// + std::string Path; + + /// True if the Path is on the LD_LIBRARY_PATH. + /// + bool IsUser; + + bool operator==(const SearchPathInfo& Other) const { + return IsUser == Other.IsUser && Path == Other.Path; + } + }; + using SearchPathInfos = llvm::SmallVector; + +private: + typedef void* DyLibHandle; + typedef llvm::DenseMap DyLibs; + ///\brief DynamicLibraries loaded by this Interpreter. + /// + DyLibs m_DyLibs; + llvm::StringSet<> m_LoadedLibraries; + + ///\brief System's include path, get initialized at construction time. + /// + SearchPathInfos m_SearchPaths; + + InterpreterCallbacks* m_Callbacks = nullptr; + + Dyld* m_Dyld = nullptr; + + ///\brief Concatenates current include paths and the system include paths + /// and performs a lookup for the filename. + /// See more information for RPATH and RUNPATH: + /// https://en.wikipedia.org/wiki/Rpath + ///\param[in] libStem - The filename being looked up + ///\param[in] RPath - RPATH as provided by loader library, searching for + /// libStem \param[in] RunPath - RUNPATH as provided by loader library, + /// searching for libStem \param[in] libLoader - The library that loads + /// libStem. Use "" for main program. + /// + ///\returns the canonical path to the file or empty string if not found + /// + std::string + lookupLibInPaths(llvm::StringRef libStem, + llvm::SmallVector RPath = {}, + llvm::SmallVector RunPath = {}, + llvm::StringRef libLoader = "") const; + + ///\brief Concatenates current include paths and the system include paths + /// and performs a lookup for the filename. If still not found it tries to + /// add the platform-specific extensions (such as so, dll, dylib) and + /// retries the lookup (from lookupLibInPaths) + /// See more information for RPATH and RUNPATH: + /// https://en.wikipedia.org/wiki/Rpath + ///\param[in] filename - The filename being looked up + ///\param[in] RPath - RPATH as provided by loader library, searching for + /// libStem \param[in] RunPath - RUNPATH as provided by loader library, + /// searching for libStem \param[in] libLoader - The library that loads + /// libStem. Use "" for main program. + /// + ///\returns the canonical path to the file or empty string if not found + /// + std::string + lookupLibMaybeAddExt(llvm::StringRef filename, + llvm::SmallVector RPath = {}, + llvm::SmallVector RunPath = {}, + llvm::StringRef libLoader = "") const; + + /// On a success returns to full path to a shared object that holds the + /// symbol pointed by func. + /// + static std::string getSymbolLocation(void* func); + +public: + DynamicLibraryManager(); + ~DynamicLibraryManager(); + DynamicLibraryManager(const DynamicLibraryManager&) = delete; + DynamicLibraryManager& operator=(const DynamicLibraryManager&) = delete; + + InterpreterCallbacks* getCallbacks() { return m_Callbacks; } + const InterpreterCallbacks* getCallbacks() const { return m_Callbacks; } + void setCallbacks(InterpreterCallbacks* C) { m_Callbacks = C; } + + ///\brief Returns the system include paths. + /// + ///\returns System include paths. + /// + const SearchPathInfos& getSearchPaths() const { return m_SearchPaths; } + + void addSearchPath(llvm::StringRef dir, bool isUser = true, + bool prepend = false) { + if (!dir.empty()) { + for (auto& item : m_SearchPaths) + if (dir == item.Path) + return; + auto pos = prepend ? m_SearchPaths.begin() : m_SearchPaths.end(); + m_SearchPaths.insert(pos, SearchPathInfo{dir.str(), isUser}); + } + } + + ///\brief Looks up a library taking into account the current include paths + /// and the system include paths. + /// See more information for RPATH and RUNPATH: + /// https://en.wikipedia.org/wiki/Rpath + ///\param[in] libStem - The filename being looked up + ///\param[in] RPath - RPATH as provided by loader library, searching for + /// libStem \param[in] RunPath - RUNPATH as provided by loader library, + /// searching for libStem \param[in] libLoader - The library that loads + /// libStem. Use "" for main program. \param[in] variateLibStem - If this + /// param is true, and libStem is "L", then + /// we search for "L", "libL", "L.so", "libL.so"", etc. + /// + ///\returns the canonical path to the file or empty string if not found + /// + std::string lookupLibrary(llvm::StringRef libStem, + llvm::SmallVector RPath = {}, + llvm::SmallVector RunPath = {}, + llvm::StringRef libLoader = "", + bool variateLibStem = true) const; + + ///\brief Loads a shared library. + /// + ///\param [in] libStem - The file to load. + ///\param [in] permanent - If false, the file can be unloaded later. + ///\param [in] resolved - Whether libStem is an absolute path or resolved + /// from a previous call to DynamicLibraryManager::lookupLibrary + /// + ///\returns kLoadLibSuccess on success, kLoadLibAlreadyLoaded if the library + /// was already loaded, kLoadLibError if the library cannot be found or any + /// other error was encountered. + /// + LoadLibResult loadLibrary(llvm::StringRef, bool permanent, + bool resolved = false); + + void unloadLibrary(llvm::StringRef libStem); + + ///\brief Returns true if the file was a dynamic library and it was already + /// loaded. + /// + bool isLibraryLoaded(llvm::StringRef fullPath) const; + + /// Initialize the dyld. + /// + ///\param [in] shouldPermanentlyIgnore - a callback deciding if a library + /// should be ignored from the result set. Useful for ignoring + /// dangerous libraries such as the ones overriding malloc. + /// + void + initializeDyld(std::function shouldPermanentlyIgnore); + + /// Find the first not-yet-loaded shared object that contains the symbol + /// + ///\param[in] mangledName - the mangled name to look for. + ///\param[in] searchSystem - whether to decend into system libraries. + /// + ///\returns the library name if found, and empty string otherwise. + /// + std::string searchLibrariesForSymbol(llvm::StringRef mangledName, + bool searchSystem = true) const; + + void dump(llvm::raw_ostream* S = nullptr) const; + + /// On a success returns to full path to a shared object that holds the + /// symbol pointed by func. + /// + template static std::string getSymbolLocation(T func) { + static_assert(std::is_pointer::value, "Must be a function pointer!"); + return getSymbolLocation(reinterpret_cast(func)); + } + + static std::string normalizePath(llvm::StringRef path); + + /// Returns true if file is a shared library. + /// + ///\param[in] libFullPath - the full path to file. + /// + ///\param[out] exists - sets if the file exists. Useful to distinguish if it + /// is a library but of incompatible file format. + /// + static bool isSharedLibrary(llvm::StringRef libFullPath, + bool* exists = nullptr); +}; +} // end namespace Cpp + +#endif // CPPINTEROP_DYNAMIC_LIBRARY_MANAGER_H diff --git a/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManagerSymbol.cpp b/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManagerSymbol.cpp new file mode 100644 index 0000000000000..ac1857fa5124b --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManagerSymbol.cpp @@ -0,0 +1,1340 @@ +//------------------------------------------------------------------------------ +// CLING - the C++ LLVM-based InterpreterG :) +// author: Vassil Vassilev +// author: Alexander Penev +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#include "DynamicLibraryManager.h" +#include "Paths.h" + +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/BinaryFormat/MachO.h" +#include "llvm/Object/COFF.h" +#include "llvm/Object/ELF.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Object/MachO.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/WithColor.h" + +#include +#include +#include +#include +#include + + +#ifdef LLVM_ON_UNIX +#include +#include +#include +#endif // LLVM_ON_UNIX + +#ifdef __APPLE__ +#include +#include +#undef LC_LOAD_DYLIB +#undef LC_RPATH +#endif // __APPLE__ + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include // For GetModuleFileNameA +#include // For VirtualQuery +#endif + +namespace { + +using BasePath = std::string; +using namespace llvm; + +// This is a GNU implementation of hash used in bloom filter! +static uint32_t GNUHash(StringRef S) { + uint32_t H = 5381; + for (uint8_t C : S) + H = (H << 5) + H + C; + return H; +} + +constexpr uint32_t log2u(std::uint32_t n) { + return (n > 1) ? 1 + log2u(n >> 1) : 0; +} + +struct BloomFilter { + + // https://hur.st/bloomfilter + // + // n = ceil(m / (-k / log(1 - exp(log(p) / k)))) + // p = pow(1 - exp(-k / (m / n)), k) + // m = ceil((n * log(p)) / log(1 / pow(2, log(2)))); + // k = round((m / n) * log(2)); + // + // n = symbolsCount + // p = 0.02 + // k = 2 (k1=GNUHash and k2=GNUHash >> bloomShift) + // m = ceil((symbolsCount * log(p)) / log(1 / pow(2, log(2)))); + // bloomShift = min(5 for bits=32 or 6 for bits=64, log2(symbolsCount)) + // bloomSize = ceil((-1.44 * n * log2f(p)) / bits) + + const int m_Bits = 8 * sizeof(uint64_t); + const float m_P = 0.02f; + + bool m_IsInitialized = false; + uint32_t m_SymbolsCount = 0; + uint32_t m_BloomSize = 0; + uint32_t m_BloomShift = 0; + std::vector m_BloomTable; + + bool TestHash(uint32_t hash) const { + // This function is superhot. No branches here, breaks inlining and makes + // overall performance around 4x slower. + assert(m_IsInitialized && "Not yet initialized!"); + uint32_t hash2 = hash >> m_BloomShift; + uint32_t n = (hash >> log2u(m_Bits)) % m_BloomSize; + uint64_t mask = ((1ULL << (hash % m_Bits)) | (1ULL << (hash2 % m_Bits))); + return (mask & m_BloomTable[n]) == mask; + } + + void AddHash(uint32_t hash) { + assert(m_IsInitialized && "Not yet initialized!"); + uint32_t hash2 = hash >> m_BloomShift; + uint32_t n = (hash >> log2u(m_Bits)) % m_BloomSize; + uint64_t mask = ((1ULL << (hash % m_Bits)) | (1ULL << (hash2 % m_Bits))); + m_BloomTable[n] |= mask; + } + + void ResizeTable(uint32_t newSymbolsCount) { + assert(m_SymbolsCount == 0 && "Not supported yet!"); + m_SymbolsCount = newSymbolsCount; + m_BloomSize = ceil((-1.44f * m_SymbolsCount * log2f(m_P)) / m_Bits); + m_BloomShift = std::min(6u, log2u(m_SymbolsCount)); + m_BloomTable.resize(m_BloomSize); + } + +}; + +/// An efficient representation of a full path to a library which does not +/// duplicate common path patterns reducing the overall memory footprint. +/// +/// For example, `/home/.../lib/libA.so`, m_Path will contain a pointer +/// to `/home/.../lib/` +/// will be stored and .second `libA.so`. +/// This approach reduces the duplicate paths as at one location there may be +/// plenty of libraries. +struct LibraryPath { + const BasePath& m_Path; + std::string m_LibName; + BloomFilter m_Filter; + StringSet<> m_Symbols; + //std::vector m_LibDeps; + + LibraryPath(const BasePath& Path, const std::string& LibName) + : m_Path(Path), m_LibName(LibName) { + } + + bool operator==(const LibraryPath &other) const { + return (&m_Path == &other.m_Path || m_Path == other.m_Path) && + m_LibName == other.m_LibName; + } + + const std::string GetFullName() const { + SmallString<512> Vec(m_Path); + llvm::sys::path::append(Vec, StringRef(m_LibName)); + return Vec.str().str(); + } + + void AddBloom(StringRef symbol) { + m_Filter.AddHash(GNUHash(symbol)); + } + + StringRef AddSymbol(const std::string& symbol) { + auto it = m_Symbols.insert(symbol); + return it.first->getKey(); + } + + bool hasBloomFilter() const { + return m_Filter.m_IsInitialized; + } + + bool isBloomFilterEmpty() const { + assert(m_Filter.m_IsInitialized && "Bloom filter not initialized!"); + return m_Filter.m_SymbolsCount == 0; + } + + void InitializeBloomFilter(uint32_t newSymbolsCount) { + assert(!m_Filter.m_IsInitialized && + "Cannot re-initialize non-empty filter!"); + m_Filter.m_IsInitialized = true; + m_Filter.ResizeTable(newSymbolsCount); + } + + bool MayExistSymbol(uint32_t hash) const { + // The library had no symbols and the bloom filter is empty. + if (isBloomFilterEmpty()) + return false; + + return m_Filter.TestHash(hash); + } + + bool ExistSymbol(StringRef symbol) const { + return m_Symbols.find(symbol) != m_Symbols.end(); + } +}; + + +/// A helper class keeping track of loaded libraries. It implements a fast +/// search O(1) while keeping deterministic iterability in a memory efficient +/// way. The underlying set uses a custom hasher for better efficiency given the +/// specific problem where the library names (m_LibName) are relatively short +/// strings and the base paths (m_Path) are repetitive long strings. +class LibraryPaths { + struct LibraryPathHashFn { + size_t operator()(const LibraryPath& item) const { + return std::hash()(item.m_Path.length()) ^ + std::hash()(item.m_LibName); + } + }; + + std::vector m_Libs; + std::unordered_set m_LibsH; +public: + bool HasRegisteredLib(const LibraryPath& Lib) const { + return m_LibsH.count(Lib); + } + + const LibraryPath* GetRegisteredLib(const LibraryPath& Lib) const { + auto search = m_LibsH.find(Lib); + if (search != m_LibsH.end()) + return &(*search); + return nullptr; + } + + const LibraryPath* RegisterLib(const LibraryPath& Lib) { + auto it = m_LibsH.insert(Lib); + assert(it.second && "Already registered!"); + m_Libs.push_back(&*it.first); + return &*it.first; + } + + void UnregisterLib(const LibraryPath& Lib) { + auto found = m_LibsH.find(Lib); + if (found == m_LibsH.end()) + return; + + m_Libs.erase(std::find(m_Libs.begin(), m_Libs.end(), &*found)); + m_LibsH.erase(found); + } + + size_t size() const { + assert(m_Libs.size() == m_LibsH.size()); + return m_Libs.size(); + } + + const std::vector& GetLibraries() const { + return m_Libs; + } +}; + +#ifndef _WIN32 +// Cached version of system function lstat +static inline mode_t cached_lstat(const char *path) { + static StringMap lstat_cache; + + // If already cached - retun cached result + auto it = lstat_cache.find(path); + if (it != lstat_cache.end()) + return it->second; + + // If result not in cache - call system function and cache result + struct stat buf; + mode_t st_mode = (lstat(path, &buf) == -1) ? 0 : buf.st_mode; + lstat_cache.insert(std::pair(path, st_mode)); + return st_mode; +} + +// Cached version of system function readlink +static inline StringRef cached_readlink(const char* pathname) { + static StringMap readlink_cache; + + // If already cached - retun cached result + auto it = readlink_cache.find(pathname); + if (it != readlink_cache.end()) + return StringRef(it->second); + + // If result not in cache - call system function and cache result + char buf[PATH_MAX]; + ssize_t len; + if ((len = readlink(pathname, buf, sizeof(buf))) != -1) { + buf[len] = '\0'; + std::string s(buf); + readlink_cache.insert(std::pair(pathname, s)); + return readlink_cache[pathname]; + } + return ""; +} +#endif + +// Cached version of system function realpath +std::string cached_realpath(StringRef path, StringRef base_path = "", + bool is_base_path_real = false, + long symlooplevel = 40) { + if (path.empty()) { + errno = ENOENT; + return ""; + } + + if (!symlooplevel) { + errno = ELOOP; + return ""; + } + + // If already cached - retun cached result + static StringMap> cache; + bool relative_path = llvm::sys::path::is_relative(path); + if (!relative_path) { + auto it = cache.find(path); + if (it != cache.end()) { + errno = it->second.second; + return it->second.first; + } + } + + // If result not in cache - call system function and cache result + + StringRef sep(llvm::sys::path::get_separator()); + SmallString<256> result(sep); +#ifndef _WIN32 + SmallVector p; + + // Relative or absolute path + if (relative_path) { + if (is_base_path_real) { + result.assign(base_path); + } else { + if (path[0] == '~' && (path.size() == 1 || llvm::sys::path::is_separator(path[1]))) { + static SmallString<128> home; + if (home.str().empty()) + llvm::sys::path::home_directory(home); + StringRef(home).split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); + } else if (base_path.empty()) { + static SmallString<256> current_path; + if (current_path.str().empty()) + llvm::sys::fs::current_path(current_path); + StringRef(current_path).split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); + } else { + base_path.split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); + } + } + } + path.split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); + + // Handle path list items + for (auto item : p) { + if (item == ".") + continue; // skip "." element in "abc/./def" + if (item == "..") { + // collapse "a/b/../c" to "a/c" + size_t s = result.rfind(sep); + if (s != llvm::StringRef::npos) + result.resize(s); + if (result.empty()) + result = sep; + continue; + } + + size_t old_size = result.size(); + llvm::sys::path::append(result, item); + mode_t st_mode = cached_lstat(result.c_str()); + if (S_ISLNK(st_mode)) { + StringRef symlink = cached_readlink(result.c_str()); + if (llvm::sys::path::is_relative(symlink)) { + result.resize(old_size); + result = cached_realpath(symlink, result, true, symlooplevel - 1); + } else { + result = cached_realpath(symlink, "", true, symlooplevel - 1); + } + } else if (st_mode == 0) { + cache.insert(std::pair>( + path, + std::pair("",ENOENT)) + ); + errno = ENOENT; + return ""; + } + } +#else + llvm::sys::fs::real_path(path, result); +#endif + cache.insert(std::pair>( + path, + std::pair(result.str().str(),errno)) + ); + return result.str().str(); +} + +using namespace llvm; +using namespace llvm::object; + +template +static Expected getDynamicStrTab(const ELFFile* Elf) { + auto DynamicEntriesOrError = Elf->dynamicEntries(); + if (!DynamicEntriesOrError) + return DynamicEntriesOrError.takeError(); + + for (const typename ELFT::Dyn& Dyn : *DynamicEntriesOrError) { + if (Dyn.d_tag == ELF::DT_STRTAB) { + auto MappedAddrOrError = Elf->toMappedAddr(Dyn.getPtr()); + if (!MappedAddrOrError) + return MappedAddrOrError.takeError(); + return StringRef(reinterpret_cast(*MappedAddrOrError)); + } + } + + // If the dynamic segment is not present, we fall back on the sections. + auto SectionsOrError = Elf->sections(); + if (!SectionsOrError) + return SectionsOrError.takeError(); + + for (const typename ELFT::Shdr &Sec : *SectionsOrError) { + if (Sec.sh_type == ELF::SHT_DYNSYM) + return Elf->getStringTableForSymtab(Sec); + } + + return createError("dynamic string table not found"); +} + +static StringRef GetGnuHashSection(llvm::object::ObjectFile *file) { + for (auto S : file->sections()) { + StringRef name = llvm::cantFail(S.getName()); + if (name == ".gnu.hash") { + return llvm::cantFail(S.getContents()); + } + } + return ""; +} + +/// Bloom filter is a stochastic data structure which can tell us if a symbol +/// name does not exist in a library with 100% certainty. If it tells us it +/// exists this may not be true: +/// https://blogs.oracle.com/solaris/gnu-hash-elf-sections-v2 +/// +/// ELF has this optimization in the new linkers by default, It is stored in the +/// gnu.hash section of the object file. +/// +///\returns true if the symbol may be in the library. +static bool MayExistInElfObjectFile(llvm::object::ObjectFile *soFile, + uint32_t hash) { + assert(soFile->isELF() && "Not ELF"); + + // Compute the platform bitness -- either 64 or 32. + const unsigned bits = 8 * soFile->getBytesInAddress(); + + StringRef contents = GetGnuHashSection(soFile); + if (contents.size() < 16) + // We need to search if the library doesn't have .gnu.hash section! + return true; + const char* hashContent = contents.data(); + + // See https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/ for .gnu.hash + // table layout. + uint32_t maskWords = *reinterpret_cast(hashContent + 8); + uint32_t shift2 = *reinterpret_cast(hashContent + 12); + uint32_t hash2 = hash >> shift2; + uint32_t n = (hash / bits) % maskWords; + + const char *bloomfilter = hashContent + 16; + const char *hash_pos = bloomfilter + n*(bits/8); // * (Bits / 8) + uint64_t word = *reinterpret_cast(hash_pos); + uint64_t bitmask = ( (1ULL << (hash % bits)) | (1ULL << (hash2 % bits))); + return (bitmask & word) == bitmask; +} + +} // anon namespace + +// This function isn't referenced outside its translation unit, but it +// can't use the "static" keyword because its address is used for +// GetMainExecutable (since some platforms don't support taking the +// address of main, and some platforms can't implement GetMainExecutable +// without being given the address of a function in the main executable). +std::string GetExecutablePath() { + // This just needs to be some symbol in the binary; C++ doesn't + // allow taking the address of ::main however. + return Cpp::DynamicLibraryManager::getSymbolLocation(&GetExecutablePath); +} + +namespace Cpp { + class Dyld { + struct BasePathHashFunction { + size_t operator()(const BasePath& item) const { + return std::hash()(item); + } + }; + + struct BasePathEqFunction { + size_t operator()(const BasePath& l, const BasePath& r) const { + return &l == &r || l == r; + } + }; + /// A memory efficient llvm::VectorSet. The class provides O(1) search + /// complexity. It is tuned to compare BasePaths first by checking the + /// address and then the representation which models the base path reuse. + class BasePaths { + public: + std::unordered_set m_Paths; + public: + const BasePath& RegisterBasePath(const std::string& Path, + bool* WasInserted = nullptr) { + auto it = m_Paths.insert(Path); + if (WasInserted) + *WasInserted = it.second; + + return *it.first; + } + + bool Contains(StringRef Path) { + return m_Paths.count(Path.str()); + } + }; + + bool m_FirstRun = true; + bool m_FirstRunSysLib = true; + bool m_UseBloomFilter = true; + bool m_UseHashTable = true; + + const Cpp::DynamicLibraryManager& m_DynamicLibraryManager; + + /// The basename of `/home/.../lib/libA.so`, + /// m_BasePaths will contain `/home/.../lib/` + BasePaths m_BasePaths; + + LibraryPaths m_Libraries; + LibraryPaths m_SysLibraries; + /// Contains a set of libraries which we gave to the user via ResolveSymbol + /// call and next time we should check if the user loaded them to avoid + /// useless iterations. + LibraryPaths m_QueriedLibraries; + + using PermanentlyIgnoreCallbackProto = std::function; + const PermanentlyIgnoreCallbackProto m_ShouldPermanentlyIgnoreCallback; + const StringRef m_ExecutableFormat; + + /// Scan for shared objects which are not yet loaded. They are a our symbol + /// resolution candidate sources. + /// NOTE: We only scan not loaded shared objects. + /// \param[in] searchSystemLibraries - whether to decent to standard system + /// locations for shared objects. + void ScanForLibraries(bool searchSystemLibraries = false); + + /// Builds a bloom filter lookup optimization. + void BuildBloomFilter(LibraryPath* Lib, llvm::object::ObjectFile *BinObjFile, + unsigned IgnoreSymbolFlags = 0) const; + + + /// Looks up symbols from a an object file, representing the library. + ///\param[in] Lib - full path to the library. + ///\param[in] mangledName - the mangled name to look for. + ///\param[in] IgnoreSymbolFlags - The symbols to ignore upon a match. + ///\returns true on success. + bool ContainsSymbol(const LibraryPath* Lib, StringRef mangledName, + unsigned IgnoreSymbolFlags = 0) const; + + bool ShouldPermanentlyIgnore(StringRef FileName) const; + void dumpDebugInfo() const; + public: + Dyld(const Cpp::DynamicLibraryManager &DLM, + PermanentlyIgnoreCallbackProto shouldIgnore, + StringRef execFormat) + : m_DynamicLibraryManager(DLM), + m_ShouldPermanentlyIgnoreCallback(shouldIgnore), + m_ExecutableFormat(execFormat) { } + + ~Dyld(){}; + + std::string searchLibrariesForSymbol(StringRef mangledName, + bool searchSystem); + }; + + std::string RPathToStr(SmallVector V) { + std::string result; + for (auto item : V) + result += item.str() + ","; + if (!result.empty()) + result.pop_back(); + return result; + } + + template + void HandleDynTab(const ELFFile* Elf, StringRef FileName, + SmallVector& RPath, + SmallVector& RunPath, + std::vector& Deps, + bool& isPIEExecutable) { +#define DEBUG_TYPE "Dyld:" + const char *Data = ""; + if (Expected StrTabOrErr = getDynamicStrTab(Elf)) + Data = StrTabOrErr.get().data(); + + isPIEExecutable = false; + + auto DynamicEntriesOrError = Elf->dynamicEntries(); + if (!DynamicEntriesOrError) { + LLVM_DEBUG(dbgs() << "Dyld: failed to read dynamic entries in" + << "'" << FileName.str() << "'\n"); + return; + } + + for (const typename ELFT::Dyn& Dyn : *DynamicEntriesOrError) { + switch (Dyn.d_tag) { + case ELF::DT_NEEDED: + Deps.push_back(Data + Dyn.d_un.d_val); + break; + case ELF::DT_RPATH: + SplitPaths(Data + Dyn.d_un.d_val, RPath, utils::kAllowNonExistant, Cpp::utils::platform::kEnvDelim, false); + break; + case ELF::DT_RUNPATH: + SplitPaths(Data + Dyn.d_un.d_val, RunPath, utils::kAllowNonExistant, Cpp::utils::platform::kEnvDelim, false); + break; + case ELF::DT_FLAGS_1: + // Check if this is not a pie executable. + if (Dyn.d_un.d_val & llvm::ELF::DF_1_PIE) + isPIEExecutable = true; + break; + // (Dyn.d_tag == ELF::DT_NULL) continue; + // (Dyn.d_tag == ELF::DT_AUXILIARY || Dyn.d_tag == ELF::DT_FILTER) + } + } +#undef DEBUG_TYPE + } + + void Dyld::ScanForLibraries(bool searchSystemLibraries/* = false*/) { +#define DEBUG_TYPE "Dyld:ScanForLibraries:" + + const auto &searchPaths = m_DynamicLibraryManager.getSearchPaths(); + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: system=" << (searchSystemLibraries?"true":"false") << "\n"); + for (const DynamicLibraryManager::SearchPathInfo &Info : searchPaths) + LLVM_DEBUG(dbgs() << ">>>" << Info.Path << ", " << (Info.IsUser?"user\n":"system\n")); + + llvm::SmallSet ScannedPaths; + + for (const DynamicLibraryManager::SearchPathInfo &Info : searchPaths) { + if (Info.IsUser != searchSystemLibraries) { + // Examples which we should handle. + // File Real + // /lib/1/1.so /lib/1/1.so // file + // /lib/1/2.so->/lib/1/1.so /lib/1/1.so // file local link + // /lib/1/3.so->/lib/3/1.so /lib/3/1.so // file external link + // /lib/2->/lib/1 // path link + // /lib/2/1.so /lib/1/1.so // path link, file + // /lib/2/2.so->/lib/1/1.so /lib/1/1.so // path link, file local link + // /lib/2/3.so->/lib/3/1.so /lib/3/1.so // path link, file external link + // + // /lib/3/1.so + // /lib/3/2.so->/system/lib/s.so + // /lib/3/3.so + // /system/lib/1.so + // + // libL.so NEEDED/RPATH libR.so /lib/some-rpath/libR.so // needed/dependedt library in libL.so RPATH/RUNPATH or other (in)direct dep + // + // Paths = /lib/1 : /lib/2 : /lib/3 + + // m_BasePaths = ["/lib/1", "/lib/3", "/system/lib"] + // m_*Libraries = [<0,"1.so">, <1,"1.so">, <2,"s.so">, <1,"3.so">] + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries Iter:" << Info.Path << " -> "); + std::string RealPath = cached_realpath(Info.Path); + + llvm::StringRef DirPath(RealPath); + LLVM_DEBUG(dbgs() << RealPath << "\n"); + + if (!llvm::sys::fs::is_directory(DirPath) || DirPath.empty()) + continue; + + // Already searched? + const BasePath &ScannedBPath = m_BasePaths.RegisterBasePath(RealPath); + if (ScannedPaths.count(&ScannedBPath)) { + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries Already scanned: " << RealPath << "\n"); + continue; + } + + // FileName must be always full/absolute/resolved file name. + std::function HandleLib = + [&](llvm::StringRef FileName, unsigned level) { + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries HandleLib:" << FileName.str() + << ", level=" << level << " -> "); + + llvm::StringRef FileRealPath = llvm::sys::path::parent_path(FileName); + llvm::StringRef FileRealName = llvm::sys::path::filename(FileName); + const BasePath& BaseP = + m_BasePaths.RegisterBasePath(FileRealPath.str()); + LibraryPath LibPath(BaseP, FileRealName.str()); //bp, str + + if (m_SysLibraries.GetRegisteredLib(LibPath) || + m_Libraries.GetRegisteredLib(LibPath)) { + LLVM_DEBUG(dbgs() << "Already handled!!!\n"); + return; + } + + if (ShouldPermanentlyIgnore(FileName)) { + LLVM_DEBUG(dbgs() << "PermanentlyIgnored!!!\n"); + return; + } + + if (searchSystemLibraries) + m_SysLibraries.RegisterLib(LibPath); + else + m_Libraries.RegisterLib(LibPath); + + // Handle lib dependencies + llvm::SmallVector RPath; + llvm::SmallVector RunPath; + std::vector Deps; + auto ObjFileOrErr = + llvm::object::ObjectFile::createObjectFile(FileName); + if (llvm::Error Err = ObjFileOrErr.takeError()) { + std::string Message; + handleAllErrors(std::move(Err), [&](llvm::ErrorInfoBase &EIB) { + Message += EIB.message() + "; "; + }); + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Failed to read object file " + << FileName.str() << " Errors: " << Message << "\n"); + return; + } + llvm::object::ObjectFile *BinObjF = ObjFileOrErr.get().getBinary(); + if (BinObjF->isELF()) { + bool isPIEExecutable = false; + + if (const auto* ELF = dyn_cast(BinObjF)) + HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, + isPIEExecutable); + else if (const auto* ELF = dyn_cast(BinObjF)) + HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, + isPIEExecutable); + else if (const auto* ELF = dyn_cast(BinObjF)) + HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, + isPIEExecutable); + else if (const auto* ELF = dyn_cast(BinObjF)) + HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, + isPIEExecutable); + + if ((level == 0) && isPIEExecutable) { + if (searchSystemLibraries) + m_SysLibraries.UnregisterLib(LibPath); + else + m_Libraries.UnregisterLib(LibPath); + return; + } + } else if (BinObjF->isMachO()) { + MachOObjectFile *Obj = (MachOObjectFile*)BinObjF; + for (const auto &Command : Obj->load_commands()) { + if (Command.C.cmd == MachO::LC_LOAD_DYLIB) { + //Command.C.cmd == MachO::LC_ID_DYLIB || + //Command.C.cmd == MachO::LC_LOAD_WEAK_DYLIB || + //Command.C.cmd == MachO::LC_REEXPORT_DYLIB || + //Command.C.cmd == MachO::LC_LAZY_LOAD_DYLIB || + //Command.C.cmd == MachO::LC_LOAD_UPWARD_DYLIB || + MachO::dylib_command dylibCmd = + Obj->getDylibIDLoadCommand(Command); + Deps.push_back(StringRef(Command.Ptr + dylibCmd.dylib.name)); + } + else if (Command.C.cmd == MachO::LC_RPATH) { + MachO::rpath_command rpathCmd = Obj->getRpathCommand(Command); + SplitPaths(Command.Ptr + rpathCmd.path, RPath, utils::kAllowNonExistant, Cpp::utils::platform::kEnvDelim, false); + } + } + } else if (BinObjF->isCOFF()) { + // TODO: COFF support + } + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Deps Info:\n"); + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: RPATH=" << RPathToStr(RPath) << "\n"); + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: RUNPATH=" << RPathToStr(RunPath) << "\n"); + int x = 0; + for (StringRef dep : Deps) + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Deps[" << x++ << "]=" << dep.str() << "\n"); + + // Heuristics for workaround performance problems: + // (H1) If RPATH and RUNPATH == "" -> skip handling Deps + if (RPath.empty() && RunPath.empty()) { + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Skip all deps by Heuristic1: " << FileName.str() << "\n"); + return; + }; + // (H2) If RPATH subset of LD_LIBRARY_PATH && + // RUNPATH subset of LD_LIBRARY_PATH -> skip handling Deps + if (std::all_of(RPath.begin(), RPath.end(), [&](StringRef item){ return std::any_of(searchPaths.begin(), searchPaths.end(), [&](DynamicLibraryManager::SearchPathInfo item1){ return item==item1.Path; }); }) && + std::all_of(RunPath.begin(), RunPath.end(), [&](StringRef item){ return std::any_of(searchPaths.begin(), searchPaths.end(), [&](DynamicLibraryManager::SearchPathInfo item1){ return item==item1.Path; }); }) ) { + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Skip all deps by Heuristic2: " << FileName.str() << "\n"); + return; + } + + // Handle dependencies + for (StringRef dep : Deps) { + std::string dep_full = + m_DynamicLibraryManager.lookupLibrary(dep, RPath, RunPath, FileName, false); + HandleLib(dep_full, level + 1); + } + + }; + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Iterator: " << DirPath << "\n"); + std::error_code EC; + for (llvm::sys::fs::directory_iterator DirIt(DirPath, EC), DirEnd; + DirIt != DirEnd && !EC; DirIt.increment(EC)) { + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Iterator >>> " + << DirIt->path() << ", type=" + << (short)(DirIt->type()) << "\n"); + + const llvm::sys::fs::file_type ft = DirIt->type(); + if (ft == llvm::sys::fs::file_type::regular_file) { + HandleLib(DirIt->path(), 0); + } else if (ft == llvm::sys::fs::file_type::symlink_file) { + std::string DepFileName_str = cached_realpath(DirIt->path()); + llvm::StringRef DepFileName = DepFileName_str; + assert(!llvm::sys::fs::is_symlink_file(DepFileName)); + if (!llvm::sys::fs::is_directory(DepFileName)) + HandleLib(DepFileName, 0); + } + } + + // Register the DirPath as fully scanned. + ScannedPaths.insert(&ScannedBPath); + } + } +#undef DEBUG_TYPE + } + + void Dyld::BuildBloomFilter(LibraryPath* Lib, + llvm::object::ObjectFile *BinObjFile, + unsigned IgnoreSymbolFlags /*= 0*/) const { +#define DEBUG_TYPE "Dyld::BuildBloomFilter:" + assert(m_UseBloomFilter && "Bloom filter is disabled"); + assert(!Lib->hasBloomFilter() && "Already built!"); + + using namespace llvm; + using namespace llvm::object; + + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Start building Bloom filter for: " + << Lib->GetFullName() << "\n"); + + // If BloomFilter is empty then build it. + // Count Symbols and generate BloomFilter + uint32_t SymbolsCount = 0; + std::list symbols; + for (const llvm::object::SymbolRef &S : BinObjFile->symbols()) { + uint32_t Flags = llvm::cantFail(S.getFlags()); + // Do not insert in the table symbols flagged to ignore. + if (Flags & IgnoreSymbolFlags) + continue; + + // Note, we are at last resort and loading library based on a weak + // symbol is allowed. Otherwise, the JIT will issue an unresolved + // symbol error. + // + // There are other weak symbol kinds (marked as 'V') to denote + // typeinfo and vtables. It is unclear whether we should load such + // libraries or from which library we should resolve the symbol. + // We seem to not have a way to differentiate it from the symbol API. + + llvm::Expected SymNameErr = S.getName(); + if (!SymNameErr) { + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Failed to read symbol " + << SymNameErr.get() << "\n"); + continue; + } + + if (SymNameErr.get().empty()) + continue; + + ++SymbolsCount; + symbols.push_back(SymNameErr.get()); + } + + if (BinObjFile->isELF()) { + // ELF file format has .dynstr section for the dynamic symbol table. + const auto *ElfObj = cast(BinObjFile); + + for (const object::SymbolRef &S : ElfObj->getDynamicSymbolIterators()) { + uint32_t Flags = llvm::cantFail(S.getFlags()); + // DO NOT insert to table if symbol was undefined + if (Flags & llvm::object::SymbolRef::SF_Undefined) + continue; + + // Note, we are at last resort and loading library based on a weak + // symbol is allowed. Otherwise, the JIT will issue an unresolved + // symbol error. + // + // There are other weak symbol kinds (marked as 'V') to denote + // typeinfo and vtables. It is unclear whether we should load such + // libraries or from which library we should resolve the symbol. + // We seem to not have a way to differentiate it from the symbol API. + + llvm::Expected SymNameErr = S.getName(); + if (!SymNameErr) { + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Failed to read symbol " + << SymNameErr.get() << "\n"); + continue; + } + + if (SymNameErr.get().empty()) + continue; + + ++SymbolsCount; + symbols.push_back(SymNameErr.get()); + } + } + else if (BinObjFile->isCOFF()) { // On Windows, the symbols are present in COFF format. + llvm::object::COFFObjectFile* CoffObj = cast(BinObjFile); + + // In COFF, the symbols are not present in the SymbolTable section + // of the Object file. They are present in the ExportDirectory section. + for (auto I=CoffObj->export_directory_begin(), + E=CoffObj->export_directory_end(); I != E; I = ++I) { + // All the symbols are already flagged as exported. + // We cannot really ignore symbols based on flags as we do on unix. + StringRef Name; + auto Err = I->getSymbolName(Name); + + if (Err) { + std::string Message; + handleAllErrors(std::move(Err), [&](llvm::ErrorInfoBase& EIB) { + Message += EIB.message() + "; "; + }); + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Failed to read symbol " + << Message << "\n"); + continue; + } + if (Name.empty()) + continue; + + ++SymbolsCount; + symbols.push_back(Name); + } + } + + Lib->InitializeBloomFilter(SymbolsCount); + + if (!SymbolsCount) { + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: No symbols!\n"); + return; + } + + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Symbols:\n"); + for (auto it : symbols) + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter" << "- " << it << "\n"); + + // Generate BloomFilter + for (const auto &S : symbols) { + if (m_UseHashTable) + Lib->AddBloom(Lib->AddSymbol(S.str())); + else + Lib->AddBloom(S); + } +#undef DEBUG_TYPE + } + + bool Dyld::ContainsSymbol(const LibraryPath* Lib, + StringRef mangledName, + unsigned IgnoreSymbolFlags /*= 0*/) const { +#define DEBUG_TYPE "Dyld::ContainsSymbol:" + const std::string library_filename = Lib->GetFullName(); + + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Find symbol: lib=" + << library_filename << ", mangled=" + << mangledName.str() << "\n"); + + auto ObjF = llvm::object::ObjectFile::createObjectFile(library_filename); + if (llvm::Error Err = ObjF.takeError()) { + std::string Message; + handleAllErrors(std::move(Err), [&](llvm::ErrorInfoBase &EIB) { + Message += EIB.message() + "; "; + }); + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Failed to read object file " + << library_filename << " Errors: " << Message << "\n"); + return false; + } + + llvm::object::ObjectFile *BinObjFile = ObjF.get().getBinary(); + + uint32_t hashedMangle = GNUHash(mangledName); + // Check for the gnu.hash section if ELF. + // If the symbol doesn't exist, exit early. + if (BinObjFile->isELF() && + !MayExistInElfObjectFile(BinObjFile, hashedMangle)) { + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: ELF BloomFilter: Skip symbol <" << mangledName.str() << ">.\n"); + return false; + } + + if (m_UseBloomFilter) { + // Use our bloom filters and create them if necessary. + if (!Lib->hasBloomFilter()) + BuildBloomFilter(const_cast(Lib), BinObjFile, + IgnoreSymbolFlags); + + // If the symbol does not exist, exit early. In case it may exist, iterate. + if (!Lib->MayExistSymbol(hashedMangle)) { + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: BloomFilter: Skip symbol <" << mangledName.str() << ">.\n"); + return false; + } + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: BloomFilter: Symbol <" << mangledName.str() << "> May exist." + << " Search for it. "); + } + + if (m_UseHashTable) { + bool result = Lib->ExistSymbol(mangledName); + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: HashTable: Symbol " + << (result ? "Exist" : "Not exist") << "\n"); + return result; + } + + auto ForeachSymbol = + [&library_filename](llvm::iterator_range range, + unsigned IgnoreSymbolFlags, llvm::StringRef mangledName) -> bool { + for (const llvm::object::SymbolRef &S : range) { + uint32_t Flags = llvm::cantFail(S.getFlags()); + // Do not insert in the table symbols flagged to ignore. + if (Flags & IgnoreSymbolFlags) + continue; + + // Note, we are at last resort and loading library based on a weak + // symbol is allowed. Otherwise, the JIT will issue an unresolved + // symbol error. + // + // There are other weak symbol kinds (marked as 'V') to denote + // typeinfo and vtables. It is unclear whether we should load such + // libraries or from which library we should resolve the symbol. + // We seem to not have a way to differentiate it from the symbol API. + + llvm::Expected SymNameErr = S.getName(); + if (!SymNameErr) { + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Failed to read symbol " + << mangledName.str() << "\n"); + continue; + } + + if (SymNameErr.get().empty()) + continue; + + if (SymNameErr.get() == mangledName) { + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Symbol " + << mangledName.str() << " found in " + << library_filename << "\n"); + return true; + } + } + return false; + }; + + // If no hash symbol then iterate to detect symbol + // We Iterate only if BloomFilter and/or SymbolHashTable are not supported. + + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Iterate all for <" + << mangledName.str() << ">"); + + // Symbol may exist. Iterate. + if (ForeachSymbol(BinObjFile->symbols(), IgnoreSymbolFlags, mangledName)) { + LLVM_DEBUG(dbgs() << " -> found.\n"); + return true; + } + + + if (!BinObjFile->isELF()) { + LLVM_DEBUG(dbgs() << " -> not found.\n"); + return false; + } + + // ELF file format has .dynstr section for the dynamic symbol table. + const auto *ElfObj = + llvm::cast(BinObjFile); + + bool result = ForeachSymbol(ElfObj->getDynamicSymbolIterators(), + IgnoreSymbolFlags, mangledName); + LLVM_DEBUG(dbgs() << (result ? " -> found.\n" : " -> not found.\n")); + return result; +#undef DEBUG_TYPE + } + + bool Dyld::ShouldPermanentlyIgnore(StringRef FileName) const { +#define DEBUG_TYPE "Dyld:" + assert(!m_ExecutableFormat.empty() && "Failed to find the object format!"); + + if (!Cpp::DynamicLibraryManager::isSharedLibrary(FileName)) + return true; + + // No need to check linked libraries, as this function is only invoked + // for symbols that cannot be found (neither by dlsym nor in the JIT). + if (m_DynamicLibraryManager.isLibraryLoaded(FileName)) + return true; + + + auto ObjF = llvm::object::ObjectFile::createObjectFile(FileName); + if (!ObjF) { + LLVM_DEBUG(dbgs() << "[DyLD] Failed to read object file " + << FileName << "\n"); + return true; + } + + llvm::object::ObjectFile *file = ObjF.get().getBinary(); + + LLVM_DEBUG(dbgs() << "Current executable format: " << m_ExecutableFormat + << ". Executable format of " << FileName << " : " + << file->getFileFormatName() << "\n"); + + // Ignore libraries with different format than the executing one. + if (m_ExecutableFormat != file->getFileFormatName()) + return true; + + if (llvm::isa(*file)) { + for (auto S : file->sections()) { + llvm::StringRef name = llvm::cantFail(S.getName()); + if (name == ".text") { + // Check if the library has only debug symbols, usually when + // stripped with objcopy --only-keep-debug. This check is done by + // reading the manual of objcopy and inspection of stripped with + // objcopy libraries. + auto SecRef = static_cast(S); + if (SecRef.getType() == llvm::ELF::SHT_NOBITS) + return true; + + return (SecRef.getFlags() & llvm::ELF::SHF_ALLOC) == 0; + } + } + return true; + } + + //FIXME: Handle osx using isStripped after upgrading to llvm9. + + return m_ShouldPermanentlyIgnoreCallback(FileName); +#undef DEBUG_TYPE + } + + void Dyld::dumpDebugInfo() const { +#define DEBUG_TYPE "Dyld:" + LLVM_DEBUG(dbgs() << "---\n"); + size_t x = 0; + for (auto const &item : m_BasePaths.m_Paths) { + LLVM_DEBUG(dbgs() << "Dyld: - m_BasePaths[" << x++ << "]:" + << &item << ": " << item << "\n"); + } + LLVM_DEBUG(dbgs() << "---\n"); + x = 0; + for (auto const &item : m_Libraries.GetLibraries()) { + LLVM_DEBUG(dbgs() << "Dyld: - m_Libraries[" << x++ << "]:" + << &item << ": " << item->m_Path << ", " + << item->m_LibName << "\n"); + } + x = 0; + for (auto const &item : m_SysLibraries.GetLibraries()) { + LLVM_DEBUG(dbgs() << "Dyld: - m_SysLibraries[" << x++ << "]:" + << &item << ": " << item->m_Path << ", " + << item->m_LibName << "\n"); + } +#undef DEBUG_TYPE + } + + std::string Dyld::searchLibrariesForSymbol(StringRef mangledName, + bool searchSystem/* = true*/) { +#define DEBUG_TYPE "Dyld:searchLibrariesForSymbol:" + assert(!llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(mangledName.str()) && + "Library already loaded, please use dlsym!"); + assert(!mangledName.empty()); + + using namespace llvm::sys::path; + using namespace llvm::sys::fs; + + if (m_FirstRun) { + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol:" << mangledName.str() + << ", searchSystem=" << (searchSystem ? "true" : "false") + << ", FirstRun(user)... scanning\n"); + + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: Before first ScanForLibraries\n"); + dumpDebugInfo(); + + ScanForLibraries(/* SearchSystemLibraries= */ false); + m_FirstRun = false; + + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: After first ScanForLibraries\n"); + dumpDebugInfo(); + } + + if (m_QueriedLibraries.size() > 0) { + // Last call we were asked if a library contains a symbol. Usually, the + // caller wants to load this library. Check if was loaded and remove it + // from our lists of not-yet-loaded libs. + + LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: m_QueriedLibraries:\n"); + size_t x = 0; + for (auto item : m_QueriedLibraries.GetLibraries()) { + LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol - [" << x++ << "]:" + << &item << ": " << item->GetFullName() << "\n"); + } + + for (const LibraryPath* P : m_QueriedLibraries.GetLibraries()) { + const std::string LibName = P->GetFullName(); + if (!m_DynamicLibraryManager.isLibraryLoaded(LibName)) + continue; + + m_Libraries.UnregisterLib(*P); + m_SysLibraries.UnregisterLib(*P); + } + // TODO: m_QueriedLibraries.clear ? + } + + // Iterate over files under this path. We want to get each ".so" files + for (const LibraryPath* P : m_Libraries.GetLibraries()) { + if (ContainsSymbol(P, mangledName, /*ignore*/ + llvm::object::SymbolRef::SF_Undefined)) { + if (!m_QueriedLibraries.HasRegisteredLib(*P)) + m_QueriedLibraries.RegisterLib(*P); + + LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: Search found match in [user lib]: " + << P->GetFullName() << "!\n"); + + return P->GetFullName(); + } + } + + if (!searchSystem) + return ""; + + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: SearchSystem!!!\n"); + + // Lookup in non-system libraries failed. Expand the search to the system. + if (m_FirstRunSysLib) { + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol:" << mangledName.str() + << ", searchSystem=" << (searchSystem ? "true" : "false") + << ", FirstRun(system)... scanning\n"); + + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: Before first system ScanForLibraries\n"); + dumpDebugInfo(); + + ScanForLibraries(/* SearchSystemLibraries= */ true); + m_FirstRunSysLib = false; + + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: After first system ScanForLibraries\n"); + dumpDebugInfo(); + } + + for (const LibraryPath* P : m_SysLibraries.GetLibraries()) { + if (ContainsSymbol(P, mangledName, /*ignore*/ + llvm::object::SymbolRef::SF_Undefined | + llvm::object::SymbolRef::SF_Weak)) { + if (!m_QueriedLibraries.HasRegisteredLib(*P)) + m_QueriedLibraries.RegisterLib(*P); + + LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: Search found match in [system lib]: " + << P->GetFullName() << "!\n"); + + return P->GetFullName(); + } + } + + LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: Search found no match!\n"); + + return ""; // Search found no match. +#undef DEBUG_TYPE + } + + DynamicLibraryManager::~DynamicLibraryManager() { + static_assert(sizeof(Dyld) > 0, "Incomplete type"); + delete m_Dyld; + } + + void DynamicLibraryManager::initializeDyld( + std::function shouldPermanentlyIgnore) { + //assert(!m_Dyld && "Already initialized!"); + if (m_Dyld) + delete m_Dyld; + + std::string exeP = GetExecutablePath(); + auto ObjF = + cantFail(llvm::object::ObjectFile::createObjectFile(exeP)); + + m_Dyld = new Dyld(*this, shouldPermanentlyIgnore, + ObjF.getBinary()->getFileFormatName()); + } + + std::string + DynamicLibraryManager::searchLibrariesForSymbol(StringRef mangledName, + bool searchSystem/* = true*/) const { + assert(m_Dyld && "Must call initialize dyld before!"); + return m_Dyld->searchLibrariesForSymbol(mangledName, searchSystem); + } + + std::string DynamicLibraryManager::getSymbolLocation(void *func) { +#if defined(__CYGWIN__) && defined(__GNUC__) + return {}; +#elif defined(_WIN32) + MEMORY_BASIC_INFORMATION mbi; + if (!VirtualQuery (func, &mbi, sizeof (mbi))) + return {}; + + HMODULE hMod = (HMODULE) mbi.AllocationBase; + char moduleName[MAX_PATH]; + + if (!GetModuleFileNameA (hMod, moduleName, sizeof (moduleName))) + return {}; + + return cached_realpath(moduleName); + +#else + // assume we have defined HAVE_DLFCN_H and HAVE_DLADDR + Dl_info info; + if (dladdr((void*)func, &info) == 0) { + // Not in a known shared library, let's give up + return {}; + } else { + std::string result = cached_realpath(info.dli_fname); + if (!result.empty()) + return result; + + // Else absolute path. For all we know that's a binary. + // Some people have dictionaries in binaries, this is how we find their + // path: (see also https://stackoverflow.com/a/1024937/6182509) +# if defined(__APPLE__) + char buf[PATH_MAX] = { 0 }; + uint32_t bufsize = sizeof(buf); + if (_NSGetExecutablePath(buf, &bufsize) >= 0) + return cached_realpath(buf); + return cached_realpath(info.dli_fname); +# elif defined(LLVM_ON_UNIX) + char buf[PATH_MAX] = { 0 }; + // Cross our fingers that /proc/self/exe exists. + if (readlink("/proc/self/exe", buf, sizeof(buf)) > 0) + return cached_realpath(buf); + std::string pipeCmd = std::string("which \"") + info.dli_fname + "\""; + FILE* pipe = popen(pipeCmd.c_str(), "r"); + if (!pipe) + return cached_realpath(info.dli_fname); + while (fgets(buf, sizeof(buf), pipe)) + result += buf; + + pclose(pipe); + return cached_realpath(result); +# else +# error "Unsupported platform." +# endif + return {}; + } +#endif + } + +} // namespace Cpp diff --git a/interpreter/CppInterOp/lib/Interpreter/Paths.cpp b/interpreter/CppInterOp/lib/Interpreter/Paths.cpp new file mode 100644 index 0000000000000..a9a4cf9fa02f4 --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/Paths.cpp @@ -0,0 +1,365 @@ +//--------------------------------------------------------------------*- C++ -*- +// CLING - the C++ LLVM-based InterpreterG :) +// author: +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#include "Paths.h" +#include "Compatibility.h" + +#include "clang/Basic/FileManager.h" +#include "clang/Lex/HeaderSearchOptions.h" + +#include "llvm/Support/Debug.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +#if defined(LLVM_ON_UNIX) +#include +#endif + +namespace Cpp { +namespace utils { + +namespace platform { +#if defined(LLVM_ON_UNIX) + const char* const kEnvDelim = ":"; +#elif defined(_WIN32) + const char* const kEnvDelim = ";"; +#else + #error "Unknown platform (environmental delimiter)" +#endif + +#if defined(LLVM_ON_UNIX) + bool Popen(const std::string& Cmd, llvm::SmallVectorImpl& Buf, + bool RdE) { + if (FILE* PF = ::popen(RdE ? (Cmd + " 2>&1").c_str() : Cmd.c_str(), "r")) { + Buf.resize(0); + const size_t Chunk = Buf.capacity_in_bytes(); + while (true) { + const size_t Len = Buf.size(); + Buf.resize(Len + Chunk); + const size_t R = ::fread(&Buf[Len], sizeof(char), Chunk, PF); + if (R < Chunk) { + Buf.resize(Len + R); + break; + } + } + ::pclose(PF); + return !Buf.empty(); + } + return false; + } +#endif + + bool GetSystemLibraryPaths(llvm::SmallVectorImpl& Paths) { +#if defined(__APPLE__) || defined(__CYGWIN__) + Paths.push_back("/usr/local/lib/"); + Paths.push_back("/usr/X11R6/lib/"); + Paths.push_back("/usr/lib/"); + Paths.push_back("/lib/"); + +#ifndef __APPLE__ + Paths.push_back("/lib/x86_64-linux-gnu/"); + Paths.push_back("/usr/local/lib64/"); + Paths.push_back("/usr/lib64/"); + Paths.push_back("/lib64/"); +#endif +#elif defined(LLVM_ON_UNIX) + llvm::SmallString<1024> Buf; + platform::Popen("LD_DEBUG=libs LD_PRELOAD=DOESNOTEXIST ls", Buf, true); + const llvm::StringRef Result = Buf.str(); + + const std::size_t NPos = std::string::npos; + const std::size_t LD = Result.find("(LD_LIBRARY_PATH)"); + std::size_t From = Result.find("search path=", LD == NPos ? 0 : LD); + if (From != NPos) { + std::size_t To = Result.find("(system search path)", From); + if (To != NPos) { + From += 12; + while (To > From && isspace(Result[To - 1])) + --To; + std::string SysPath = Result.substr(From, To - From).str(); + SysPath.erase(std::remove_if(SysPath.begin(), SysPath.end(), ::isspace), + SysPath.end()); + + llvm::SmallVector CurPaths; + SplitPaths(SysPath, CurPaths); + for (const auto& Path : CurPaths) + Paths.push_back(Path.str()); + } + } +#endif + return true; + } + + std::string NormalizePath(const std::string& Path) { + + llvm::SmallString<256> Buffer; + std::error_code EC = llvm::sys::fs::real_path(Path, Buffer, true); + if (EC) + return std::string(); + return std::string(Buffer.str()); + } + +#if defined(LLVM_ON_UNIX) + static void DLErr(std::string* Err) { + if (!Err) + return; + if (const char* DyLibError = ::dlerror()) + *Err = DyLibError; + } + + void* DLOpen(const std::string& Path, std::string* Err /* = nullptr */) { + void* Lib = ::dlopen(Path.c_str(), RTLD_LAZY | RTLD_GLOBAL); + DLErr(Err); + return Lib; + } + + void DLClose(void* Lib, std::string* Err /* = nullptr*/) { + ::dlclose(Lib); + DLErr(Err); + } +#elif defined(_WIN32) + + void* DLOpen(const std::string& Path, std::string* Err /* = nullptr */) { + auto lib = llvm::sys::DynamicLibrary::getLibrary(Path.c_str(), Err); + return lib.getOSSpecificHandle(); + } + + void DLClose(void* Lib, std::string* Err /* = nullptr*/) { + auto dl = llvm::sys::DynamicLibrary(Lib); + llvm::sys::DynamicLibrary::closeLibrary(dl); + if (Err) { + *Err = std::string(); + } + } +#endif + +} // namespace platform + +using namespace llvm; +using namespace clang; + +// Adapted from clang/lib/Frontend/CompilerInvocation.cpp + +void CopyIncludePaths(const clang::HeaderSearchOptions& Opts, + llvm::SmallVectorImpl& incpaths, + bool withSystem, bool withFlags) { + if (withFlags && Opts.Sysroot != "/") { + incpaths.push_back("-isysroot"); + incpaths.push_back(Opts.Sysroot); + } + + /// User specified include entries. + for (unsigned i = 0, e = Opts.UserEntries.size(); i != e; ++i) { + const HeaderSearchOptions::Entry &E = Opts.UserEntries[i]; + if (E.IsFramework && E.Group != frontend::Angled) + llvm::report_fatal_error("Invalid option set!"); + switch (E.Group) { + case frontend::After: + if (withFlags) incpaths.push_back("-idirafter"); + break; + + case frontend::Quoted: + if (withFlags) incpaths.push_back("-iquote"); + break; + + case frontend::System: + if (!withSystem) continue; + if (withFlags) incpaths.push_back("-isystem"); + break; + + case frontend::IndexHeaderMap: + if (!withSystem) continue; + if (withFlags) incpaths.push_back("-index-header-map"); + if (withFlags) incpaths.push_back(E.IsFramework? "-F" : "-I"); + break; + + case frontend::CSystem: + if (!withSystem) continue; + if (withFlags) incpaths.push_back("-c-isystem"); + break; + + case frontend::ExternCSystem: + if (!withSystem) continue; + if (withFlags) incpaths.push_back("-extern-c-isystem"); + break; + + case frontend::CXXSystem: + if (!withSystem) continue; + if (withFlags) incpaths.push_back("-cxx-isystem"); + break; + + case frontend::ObjCSystem: + if (!withSystem) continue; + if (withFlags) incpaths.push_back("-objc-isystem"); + break; + + case frontend::ObjCXXSystem: + if (!withSystem) continue; + if (withFlags) incpaths.push_back("-objcxx-isystem"); + break; + + case frontend::Angled: + if (withFlags) incpaths.push_back(E.IsFramework ? "-F" : "-I"); + break; + } + incpaths.push_back(E.Path); + } + + if (withSystem && !Opts.ResourceDir.empty()) { + if (withFlags) incpaths.push_back("-resource-dir"); + incpaths.push_back(Opts.ResourceDir); + } + if (withSystem && withFlags && !Opts.ModuleCachePath.empty()) { + incpaths.push_back("-fmodule-cache-path"); + incpaths.push_back(Opts.ModuleCachePath); + } + if (withSystem && withFlags && !Opts.UseStandardSystemIncludes) + incpaths.push_back("-nostdinc"); + if (withSystem && withFlags && !Opts.UseStandardCXXIncludes) + incpaths.push_back("-nostdinc++"); + if (withSystem && withFlags && Opts.UseLibcxx) + incpaths.push_back("-stdlib=libc++"); + if (withSystem && withFlags && Opts.Verbose) + incpaths.push_back("-v"); +} + +void LogNonExistantDirectory(llvm::StringRef Path) { +#define DEBUG_TYPE "LogNonExistantDirectory" + LLVM_DEBUG(dbgs() << " ignoring nonexistent directory \"" << Path << "\"\n"); +#undef DEBUG_TYPE +} + +bool SplitPaths(llvm::StringRef PathStr, + llvm::SmallVectorImpl& Paths, + SplitMode Mode, llvm::StringRef Delim, bool Verbose) { +#define DEBUG_TYPE "SplitPths" + + assert(Delim.size() && "Splitting without a delimiter"); + +#if defined(_WIN32) + // Support using a ':' delimiter on Windows. + const bool WindowsColon = (Delim == ":"); +#endif + + bool AllExisted = true; + for (std::pair Split = PathStr.split(Delim); + !Split.second.empty(); Split = PathStr.split(Delim)) { + + if (!Split.first.empty()) { + bool Exists = llvm::sys::fs::is_directory(Split.first); + +#if defined(_WIN32) + // Because drive letters will have a colon we have to make sure the split + // occurs at a colon not followed by a path separator. + if (!Exists && WindowsColon && Split.first.size()==1) { + // Both clang and cl.exe support '\' and '/' path separators. + if (Split.second.front() == '\\' || Split.second.front() == '/') { + const std::pair Tmp = + Split.second.split(Delim); + // Split.first = 'C', but we want 'C:', so Tmp.first.size()+2 + Split.first = + llvm::StringRef(Split.first.data(), Tmp.first.size() + 2); + Split.second = Tmp.second; + Exists = llvm::sys::fs::is_directory(Split.first); + } + } +#endif + + AllExisted = AllExisted && Exists; + + if (!Exists) { + if (Mode == kFailNonExistant) { + if (Verbose) { + // Exiting early, but still log all non-existant paths that we have + LogNonExistantDirectory(Split.first); + while (!Split.second.empty()) { + Split = PathStr.split(Delim); + if (llvm::sys::fs::is_directory(Split.first)) { + LLVM_DEBUG(dbgs() << " ignoring directory that exists \"" + << Split.first << "\"\n"); + } else + LogNonExistantDirectory(Split.first); + Split = Split.second.split(Delim); + } + if (!llvm::sys::fs::is_directory(Split.first)) + LogNonExistantDirectory(Split.first); + } + return false; + } else if (Mode == kAllowNonExistant) + Paths.push_back(Split.first); + else if (Verbose) + LogNonExistantDirectory(Split.first); + } else + Paths.push_back(Split.first); + } + + PathStr = Split.second; + } + + // Trim trailing sep in case of A:B:C:D: + if (!PathStr.empty() && PathStr.ends_with(Delim)) + PathStr = PathStr.substr(0, PathStr.size()-Delim.size()); + + if (!PathStr.empty()) { + if (!llvm::sys::fs::is_directory(PathStr)) { + AllExisted = false; + if (Mode == kAllowNonExistant) + Paths.push_back(PathStr); + else if (Verbose) + LogNonExistantDirectory(PathStr); + } else + Paths.push_back(PathStr); + } + + return AllExisted; + +#undef DEBUG_TYPE +} + +void AddIncludePaths(llvm::StringRef PathStr, + clang::HeaderSearchOptions& HOpts, + const char* Delim /* = Cpp::utils::platform::kEnvDelim */) { +#define DEBUG_TYPE "AddIncludePaths" + + llvm::SmallVector Paths; + if (Delim && *Delim) + SplitPaths(PathStr, Paths, kAllowNonExistant, Delim, HOpts.Verbose); + else + Paths.push_back(PathStr); + + // Avoid duplicates + llvm::SmallVector PathsChecked; + for (llvm::StringRef Path : Paths) { + bool Exists = false; + for (const clang::HeaderSearchOptions::Entry& E : HOpts.UserEntries) { + if ((Exists = E.Path == Path)) + break; + } + if (!Exists) + PathsChecked.push_back(Path); + } + + const bool IsFramework = false; + const bool IsSysRootRelative = true; + for (llvm::StringRef Path : PathsChecked) + HOpts.AddPath(Path, clang::frontend::Angled, IsFramework, IsSysRootRelative); + + if (HOpts.Verbose) { + LLVM_DEBUG(dbgs() << "Added include paths:\n"); + for (llvm::StringRef Path : PathsChecked) + LLVM_DEBUG(dbgs() << " " << Path << "\n"); + } + +#undef DEBUG_TYPE +} + +} // namespace utils +} // namespace Cpp diff --git a/interpreter/CppInterOp/lib/Interpreter/Paths.h b/interpreter/CppInterOp/lib/Interpreter/Paths.h new file mode 100644 index 0000000000000..e8833d4fd13e5 --- /dev/null +++ b/interpreter/CppInterOp/lib/Interpreter/Paths.h @@ -0,0 +1,122 @@ +//--------------------------------------------------------------------*- C++ -*- +// CLING - the C++ LLVM-based InterpreterG :) +// author: +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#ifndef CPPINTEROP_UTILS_PATHS_H +#define CPPINTEROP_UTILS_PATHS_H + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace llvm { +class raw_ostream; +} + +namespace clang { +class HeaderSearchOptions; +class FileManager; +} // namespace clang + +namespace Cpp { +namespace utils { + +namespace platform { +///\brief Platform specific delimiter for splitting environment variables. +/// ':' on Unix, and ';' on Windows +extern const char* const kEnvDelim; + +/// +bool GetSystemLibraryPaths(llvm::SmallVectorImpl& Paths); + +///\brief Returns a normalized version of the given Path +/// +std::string NormalizePath(const std::string& Path); + +///\brief Open a handle to a shared library. On Unix the lib is opened with +/// RTLD_LAZY|RTLD_GLOBAL flags. +/// +/// \param [in] Path - Library to open +/// \param [out] Err - Write errors to this string when given +/// +/// \returns the library handle +/// +void* DLOpen(const std::string& Path, std::string* Err = nullptr); + +///\brief Close a handle to a shared library. +/// +/// \param [in] Lib - Handle to library from previous call to DLOpen +/// \param [out] Err - Write errors to this string when given +/// +/// \returns the library handle +/// +void DLClose(void* Lib, std::string* Err = nullptr); +} // namespace platform + +enum SplitMode { + kPruneNonExistant, ///< Don't add non-existant paths into output + kFailNonExistant, ///< Fail on any non-existant paths + kAllowNonExistant ///< Add all paths whether they exist or not +}; + +///\brief Collect the constituant paths from a PATH string. +/// /bin:/usr/bin:/usr/local/bin -> {/bin, /usr/bin, /usr/local/bin} +/// +/// All paths returned existed at the time of the call +/// \param [in] PathStr - The PATH string to be split +/// \param [out] Paths - All the paths in the string that exist +/// \param [in] Mode - If any path doesn't exist stop and return false +/// \param [in] Delim - The delimeter to use +/// \param [in] Verbose - Whether to print out details as 'clang -v' would +/// +/// \return true if all paths existed, otherwise false +/// +bool SplitPaths(llvm::StringRef PathStr, + llvm::SmallVectorImpl& Paths, + SplitMode Mode = kPruneNonExistant, + llvm::StringRef Delim = Cpp::utils::platform::kEnvDelim, + bool Verbose = false); + +///\brief Adds multiple include paths separated by a delimter into the +/// given HeaderSearchOptions. This adds the paths but does no further +/// processing. See Interpreter::AddIncludePaths or CIFactory::createCI +/// for examples of what needs to be done once the paths have been added. +/// +///\param[in] PathStr - Path(s) +///\param[in] Opts - HeaderSearchOptions to add paths into +///\param[in] Delim - Delimiter to separate paths or NULL if a single path +/// +void AddIncludePaths(llvm::StringRef PathStr, clang::HeaderSearchOptions& HOpts, + const char* Delim = Cpp::utils::platform::kEnvDelim); + +///\brief Write to cling::errs that directory does not exist in a format +/// matching what 'clang -v' would do +/// +void LogNonExistantDirectory(llvm::StringRef Path); + +///\brief Copies the current include paths into the HeaderSearchOptions. +/// +///\param[in] Opts - HeaderSearchOptions to read from +///\param[out] Paths - Vector to output elements into +///\param[in] WithSystem - if true, incpaths will also contain system +/// include paths (framework, STL etc). +///\param[in] WithFlags - if true, each element in incpaths will be prefixed +/// with a "-I" or similar, and some entries of incpaths will signal +/// a new include path region (e.g. "-cxx-isystem"). Also, flags +/// defining header search behavior will be included in incpaths, e.g. +/// "-nostdinc". +/// +void CopyIncludePaths(const clang::HeaderSearchOptions& Opts, + llvm::SmallVectorImpl& Paths, + bool WithSystem, bool WithFlags); + +} // namespace utils +} // namespace Cpp + +#endif // CPPINTEROP_UTILS_PATHS_H diff --git a/interpreter/CppInterOp/patches/llvm/README.md b/interpreter/CppInterOp/patches/llvm/README.md new file mode 100644 index 0000000000000..4a70de66405fb --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/README.md @@ -0,0 +1 @@ +LLVM/Clang patches diff --git a/interpreter/CppInterOp/patches/llvm/clang16-1-Value.patch b/interpreter/CppInterOp/patches/llvm/clang16-1-Value.patch new file mode 100644 index 0000000000000..854077c7a5da4 --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/clang16-1-Value.patch @@ -0,0 +1,2084 @@ +diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h +index 863f6ac57..feb6db113 100644 +--- a/clang/include/clang/AST/Decl.h ++++ b/clang/include/clang/AST/Decl.h +@@ -4308,6 +4308,7 @@ class TopLevelStmtDecl : public Decl { + friend class ASTDeclWriter; + + Stmt *Statement = nullptr; ++ bool IsSemiMissing = false; + + TopLevelStmtDecl(DeclContext *DC, SourceLocation L, Stmt *S) + : Decl(TopLevelStmt, DC, L), Statement(S) {} +@@ -4321,6 +4322,12 @@ public: + SourceRange getSourceRange() const override LLVM_READONLY; + Stmt *getStmt() { return Statement; } + const Stmt *getStmt() const { return Statement; } ++ void setStmt(Stmt *S) { ++ assert(IsSemiMissing && "Operation supported for printing values only!"); ++ Statement = S; ++ } ++ bool isSemiMissing() const { return IsSemiMissing; } ++ void setSemiMissing(bool Missing = true) { IsSemiMissing = Missing; } + + static bool classof(const Decl *D) { return classofKind(D->getKind()); } + static bool classofKind(Kind K) { return K == TopLevelStmt; } +diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def +index 96feae991..752629855 100644 +--- a/clang/include/clang/Basic/TokenKinds.def ++++ b/clang/include/clang/Basic/TokenKinds.def +@@ -936,6 +936,9 @@ ANNOTATION(module_end) + // into the name of a header unit. + ANNOTATION(header_unit) + ++// Annotation for end of input in clang-repl. ++ANNOTATION(repl_input_end) ++ + #undef PRAGMA_ANNOTATION + #undef ANNOTATION + #undef TESTING_KEYWORD +diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h +index fd22af976..e68021845 100644 +--- a/clang/include/clang/Interpreter/Interpreter.h ++++ b/clang/include/clang/Interpreter/Interpreter.h +@@ -14,13 +14,15 @@ + #ifndef LLVM_CLANG_INTERPRETER_INTERPRETER_H + #define LLVM_CLANG_INTERPRETER_INTERPRETER_H + +-#include "clang/Interpreter/PartialTranslationUnit.h" +- ++#include "clang/AST/Decl.h" + #include "clang/AST/GlobalDecl.h" ++#include "clang/Interpreter/PartialTranslationUnit.h" ++#include "clang/Interpreter/Value.h" + ++#include "llvm/ADT/DenseMap.h" + #include "llvm/ExecutionEngine/JITSymbol.h" ++#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" + #include "llvm/Support/Error.h" +- + #include + #include + +@@ -28,7 +30,7 @@ namespace llvm { + namespace orc { + class LLJIT; + class ThreadSafeContext; +-} ++} // namespace orc + } // namespace llvm + + namespace clang { +@@ -52,39 +54,64 @@ class Interpreter { + + Interpreter(std::unique_ptr CI, llvm::Error &Err); + ++ llvm::Error CreateExecutor(); ++ unsigned InitPTUSize = 0; ++ ++ // This member holds the last result of the value printing. It's a class ++ // member because we might want to access it after more inputs. If no value ++ // printing happens, it's in an invalid state. ++ Value LastValue; ++ + public: + ~Interpreter(); + static llvm::Expected> + create(std::unique_ptr CI); ++ const ASTContext &getASTContext() const; ++ ASTContext &getASTContext(); + const CompilerInstance *getCompilerInstance() const; +- const llvm::orc::LLJIT *getExecutionEngine() const; ++ llvm::Expected getExecutionEngine(); ++ + llvm::Expected Parse(llvm::StringRef Code); + llvm::Error Execute(PartialTranslationUnit &T); +- llvm::Error ParseAndExecute(llvm::StringRef Code) { +- auto PTU = Parse(Code); +- if (!PTU) +- return PTU.takeError(); +- if (PTU->TheModule) +- return Execute(*PTU); +- return llvm::Error::success(); +- } ++ llvm::Error ParseAndExecute(llvm::StringRef Code, Value *V = nullptr); ++ llvm::Expected CompileDtorCall(CXXRecordDecl *CXXRD); + + /// Undo N previous incremental inputs. + llvm::Error Undo(unsigned N = 1); + +- /// \returns the \c JITTargetAddress of a \c GlobalDecl. This interface uses ++ /// Link a dynamic library ++ llvm::Error LoadDynamicLibrary(const char *name); ++ ++ /// \returns the \c ExecutorAddr of a \c GlobalDecl. This interface uses + /// the CodeGenModule's internal mangling cache to avoid recomputing the + /// mangled name. +- llvm::Expected getSymbolAddress(GlobalDecl GD) const; ++ llvm::Expected getSymbolAddress(GlobalDecl GD) const; + +- /// \returns the \c JITTargetAddress of a given name as written in the IR. +- llvm::Expected ++ /// \returns the \c ExecutorAddr of a given name as written in the IR. ++ llvm::Expected + getSymbolAddress(llvm::StringRef IRName) const; + +- /// \returns the \c JITTargetAddress of a given name as written in the object ++ /// \returns the \c ExecutorAddr of a given name as written in the object + /// file. +- llvm::Expected ++ llvm::Expected + getSymbolAddressFromLinkerName(llvm::StringRef LinkerName) const; ++ ++ enum InterfaceKind { NoAlloc, WithAlloc, CopyArray }; ++ ++ const llvm::SmallVectorImpl &getValuePrintingInfo() const { ++ return ValuePrintingInfo; ++ } ++ ++ Expr *SynthesizeExpr(Expr *E); ++ ++private: ++ size_t getEffectivePTUSize() const; ++ ++ bool FindRuntimeInterface(); ++ ++ llvm::DenseMap Dtors; ++ ++ llvm::SmallVector ValuePrintingInfo; + }; + } // namespace clang + +diff --git a/clang/include/clang/Interpreter/Value.h b/clang/include/clang/Interpreter/Value.h +new file mode 100644 +index 000000000..4df436703 +--- /dev/null ++++ b/clang/include/clang/Interpreter/Value.h +@@ -0,0 +1,202 @@ ++//===--- Value.h - Definition of interpreter value --------------*- C++ -*-===// ++// ++// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. ++// See https://llvm.org/LICENSE.txt for license information. ++// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ++// ++//===----------------------------------------------------------------------===// ++// ++// Value is a lightweight struct that is used for carrying execution results in ++// clang-repl. It's a special runtime that acts like a messager between compiled ++// code and interpreted code. This makes it possible to exchange interesting ++// information between the compiled & interpreted world. ++// ++// A typical usage is like the below: ++// ++// Value V; ++// Interp.ParseAndExecute("int x = 42;"); ++// Interp.ParseAndExecute("x", &V); ++// V.getType(); // <-- Yields a clang::QualType. ++// V.getInt(); // <-- Yields 42. ++// ++// The current design is still highly experimental and nobody should rely on the ++// API being stable because we're hopefully going to make significant changes to ++// it in the relatively near future. For example, Value also intends to be used ++// as an exchange token for JIT support enabling remote execution on the embed ++// devices where the JIT infrastructure cannot fit. To support that we will need ++// to split the memory storage in a different place and perhaps add a resource ++// header is similar to intrinsics headers which have stricter performance ++// constraints. ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLVM_CLANG_INTERPRETER_VALUE_H ++#define LLVM_CLANG_INTERPRETER_VALUE_H ++ ++#include "llvm/Support/Compiler.h" ++#include ++ ++// NOTE: Since the REPL itself could also include this runtime, extreme caution ++// should be taken when MAKING CHANGES to this file, especially when INCLUDE NEW ++// HEADERS, like , and etc. (That pulls a large number of ++// tokens and will impact the runtime performance of the REPL) ++ ++namespace llvm { ++class raw_ostream; ++ ++} // namespace llvm ++ ++namespace clang { ++ ++class ASTContext; ++class Interpreter; ++class QualType; ++ ++#if __has_attribute(visibility) && \ ++ (!(defined(_WIN32) || defined(__CYGWIN__)) || \ ++ (defined(__MINGW32__) && defined(__clang__))) ++#if defined(LLVM_BUILD_LLVM_DYLIB) || defined(LLVM_BUILD_SHARED_LIBS) ++#define REPL_EXTERNAL_VISIBILITY __attribute__((visibility("default"))) ++#else ++#define REPL_EXTERNAL_VISIBILITY ++#endif ++#else ++#if defined(_WIN32) ++#define REPL_EXTERNAL_VISIBILITY __declspec(dllexport) ++#endif ++#endif ++ ++#define REPL_BUILTIN_TYPES \ ++ X(bool, Bool) \ ++ X(char, Char_S) \ ++ X(signed char, SChar) \ ++ X(unsigned char, UChar) \ ++ X(short, Short) \ ++ X(unsigned short, UShort) \ ++ X(int, Int) \ ++ X(unsigned int, UInt) \ ++ X(long, Long) \ ++ X(unsigned long, ULong) \ ++ X(long long, LongLong) \ ++ X(unsigned long long, ULongLong) \ ++ X(float, Float) \ ++ X(double, Double) \ ++ X(long double, LongDouble) ++ ++class REPL_EXTERNAL_VISIBILITY Value { ++ union Storage { ++#define X(type, name) type m_##name; ++ REPL_BUILTIN_TYPES ++#undef X ++ void *m_Ptr; ++ }; ++ ++public: ++ enum Kind { ++#define X(type, name) K_##name, ++ REPL_BUILTIN_TYPES ++#undef X ++ ++ K_Void, ++ K_PtrOrObj, ++ K_Unspecified ++ }; ++ ++ Value() = default; ++ Value(Interpreter *In, void *Ty); ++ Value(const Value &RHS); ++ Value(Value &&RHS) noexcept; ++ Value &operator=(const Value &RHS); ++ Value &operator=(Value &&RHS) noexcept; ++ ~Value(); ++ ++ void printType(llvm::raw_ostream &Out) const; ++ void printData(llvm::raw_ostream &Out) const; ++ void print(llvm::raw_ostream &Out) const; ++ void dump() const; ++ void clear(); ++ ++ ASTContext &getASTContext(); ++ const ASTContext &getASTContext() const; ++ Interpreter &getInterpreter(); ++ const Interpreter &getInterpreter() const; ++ QualType getType() const; ++ ++ bool isValid() const { return ValueKind != K_Unspecified; } ++ bool isVoid() const { return ValueKind == K_Void; } ++ bool hasValue() const { return isValid() && !isVoid(); } ++ bool isManuallyAlloc() const { return IsManuallyAlloc; } ++ Kind getKind() const { return ValueKind; } ++ void setKind(Kind K) { ValueKind = K; } ++ void setOpaqueType(void *Ty) { OpaqueType = Ty; } ++ ++ void *getPtr() const; ++ void setPtr(void *Ptr) { Data.m_Ptr = Ptr; } ++ ++#define X(type, name) \ ++ void set##name(type Val) { Data.m_##name = Val; } \ ++ type get##name() const { return Data.m_##name; } ++ REPL_BUILTIN_TYPES ++#undef X ++ ++ /// \brief Get the value with cast. ++ // ++ /// Get the value cast to T. This is similar to reinterpret_cast(value), ++ /// casting the value of builtins (except void), enums and pointers. ++ /// Values referencing an object are treated as pointers to the object. ++ template T convertTo() const { ++ return convertFwd::cast(*this); ++ } ++ ++protected: ++ bool isPointerOrObjectType() const { return ValueKind == K_PtrOrObj; } ++ ++ /// \brief Get to the value with type checking casting the underlying ++ /// stored value to T. ++ template T as() const { ++ switch (ValueKind) { ++ default: ++ return T(); ++#define X(type, name) \ ++ case Value::K_##name: \ ++ return (T)Data.m_##name; ++ REPL_BUILTIN_TYPES ++#undef X ++ } ++ } ++ ++ // Allow convertTo to be partially specialized. ++ template struct convertFwd { ++ static T cast(const Value &V) { ++ if (V.isPointerOrObjectType()) ++ return (T)(uintptr_t)V.as(); ++ if (!V.isValid() || V.isVoid()) { ++ return T(); ++ } ++ return V.as(); ++ } ++ }; ++ ++ template struct convertFwd { ++ static T *cast(const Value &V) { ++ if (V.isPointerOrObjectType()) ++ return (T *)(uintptr_t)V.as(); ++ return nullptr; ++ } ++ }; ++ ++ Interpreter *Interp = nullptr; ++ void *OpaqueType = nullptr; ++ Storage Data; ++ Kind ValueKind = K_Unspecified; ++ bool IsManuallyAlloc = false; ++}; ++ ++template <> inline void *Value::as() const { ++ if (isPointerOrObjectType()) ++ return Data.m_Ptr; ++ return (void *)as(); ++} ++ ++} // namespace clang ++#endif +diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h +index 6f9581b9e..6b73f43a1 100644 +--- a/clang/include/clang/Parse/Parser.h ++++ b/clang/include/clang/Parse/Parser.h +@@ -18,6 +18,7 @@ + #include "clang/Basic/OpenMPKinds.h" + #include "clang/Basic/OperatorPrecedence.h" + #include "clang/Basic/Specifiers.h" ++#include "clang/Basic/TokenKinds.h" + #include "clang/Lex/CodeCompletionHandler.h" + #include "clang/Lex/Preprocessor.h" + #include "clang/Sema/DeclSpec.h" +@@ -692,7 +693,8 @@ private: + bool isEofOrEom() { + tok::TokenKind Kind = Tok.getKind(); + return Kind == tok::eof || Kind == tok::annot_module_begin || +- Kind == tok::annot_module_end || Kind == tok::annot_module_include; ++ Kind == tok::annot_module_end || Kind == tok::annot_module_include || ++ Kind == tok::annot_repl_input_end; + } + + /// Checks if the \p Level is valid for use in a fold expression. +diff --git a/clang/lib/Frontend/PrintPreprocessedOutput.cpp b/clang/lib/Frontend/PrintPreprocessedOutput.cpp +index ffa85e523..1b262d9e6 100644 +--- a/clang/lib/Frontend/PrintPreprocessedOutput.cpp ++++ b/clang/lib/Frontend/PrintPreprocessedOutput.cpp +@@ -663,7 +663,8 @@ void PrintPPOutputPPCallbacks::HandleWhitespaceBeforeTok(const Token &Tok, + // them. + if (Tok.is(tok::eof) || + (Tok.isAnnotation() && !Tok.is(tok::annot_header_unit) && +- !Tok.is(tok::annot_module_begin) && !Tok.is(tok::annot_module_end))) ++ !Tok.is(tok::annot_module_begin) && !Tok.is(tok::annot_module_end) && ++ !Tok.is(tok::annot_repl_input_end))) + return; + + // EmittedDirectiveOnThisLine takes priority over RequireSameLine. +@@ -819,6 +820,9 @@ static void PrintPreprocessedTokens(Preprocessor &PP, Token &Tok, + // -traditional-cpp the lexer keeps /all/ whitespace, including comments. + PP.Lex(Tok); + continue; ++ } else if (Tok.is(tok::annot_repl_input_end)) { ++ PP.Lex(Tok); ++ continue; + } else if (Tok.is(tok::eod)) { + // Don't print end of directive tokens, since they are typically newlines + // that mess up our line tracking. These come from unknown pre-processor +diff --git a/clang/lib/Interpreter/CMakeLists.txt b/clang/lib/Interpreter/CMakeLists.txt +index c49f22fdd..565e824bf 100644 +--- a/clang/lib/Interpreter/CMakeLists.txt ++++ b/clang/lib/Interpreter/CMakeLists.txt +@@ -12,6 +12,8 @@ add_clang_library(clangInterpreter + IncrementalExecutor.cpp + IncrementalParser.cpp + Interpreter.cpp ++ InterpreterUtils.cpp ++ Value.cpp + + DEPENDS + intrinsics_gen +diff --git a/clang/lib/Interpreter/IncrementalExecutor.cpp b/clang/lib/Interpreter/IncrementalExecutor.cpp +index 37d230b61..489ea48e0 100644 +--- a/clang/lib/Interpreter/IncrementalExecutor.cpp ++++ b/clang/lib/Interpreter/IncrementalExecutor.cpp +@@ -86,7 +86,7 @@ llvm::Error IncrementalExecutor::runCtors() const { + return Jit->initialize(Jit->getMainJITDylib()); + } + +-llvm::Expected ++llvm::Expected + IncrementalExecutor::getSymbolAddress(llvm::StringRef Name, + SymbolNameKind NameKind) const { + auto Sym = (NameKind == LinkerName) ? Jit->lookupLinkerMangled(Name) +@@ -94,7 +94,7 @@ IncrementalExecutor::getSymbolAddress(llvm::StringRef Name, + + if (!Sym) + return Sym.takeError(); +- return Sym->getValue(); ++ return Sym; + } + + } // end namespace clang +diff --git a/clang/lib/Interpreter/IncrementalExecutor.h b/clang/lib/Interpreter/IncrementalExecutor.h +index 54d37c763..dd0a210a0 100644 +--- a/clang/lib/Interpreter/IncrementalExecutor.h ++++ b/clang/lib/Interpreter/IncrementalExecutor.h +@@ -16,6 +16,7 @@ + #include "llvm/ADT/DenseMap.h" + #include "llvm/ADT/StringRef.h" + #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" ++#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" + + #include + +@@ -51,9 +52,10 @@ public: + llvm::Error removeModule(PartialTranslationUnit &PTU); + llvm::Error runCtors() const; + llvm::Error cleanUp(); +- llvm::Expected ++ llvm::Expected + getSymbolAddress(llvm::StringRef Name, SymbolNameKind NameKind) const; +- llvm::orc::LLJIT *getExecutionEngine() const { return Jit.get(); } ++ ++ llvm::orc::LLJIT &GetExecutionEngine() { return *Jit; } + }; + + } // end namespace clang +diff --git a/clang/lib/Interpreter/IncrementalParser.cpp b/clang/lib/Interpreter/IncrementalParser.cpp +index 373e2844b..e43189071 100644 +--- a/clang/lib/Interpreter/IncrementalParser.cpp ++++ b/clang/lib/Interpreter/IncrementalParser.cpp +@@ -11,7 +11,6 @@ + //===----------------------------------------------------------------------===// + + #include "IncrementalParser.h" +- + #include "clang/AST/DeclContextInternals.h" + #include "clang/CodeGen/BackendUtil.h" + #include "clang/CodeGen/CodeGenAction.h" +@@ -19,9 +18,9 @@ + #include "clang/Frontend/CompilerInstance.h" + #include "clang/Frontend/FrontendAction.h" + #include "clang/FrontendTool/Utils.h" ++#include "clang/Interpreter/Interpreter.h" + #include "clang/Parse/Parser.h" + #include "clang/Sema/Sema.h" +- + #include "llvm/Option/ArgList.h" + #include "llvm/Support/CrashRecoveryContext.h" + #include "llvm/Support/Error.h" +@@ -31,6 +30,79 @@ + + namespace clang { + ++class IncrementalASTConsumer final : public ASTConsumer { ++ Interpreter &Interp; ++ std::unique_ptr Consumer; ++ ++public: ++ IncrementalASTConsumer(Interpreter &InterpRef, std::unique_ptr C) ++ : Interp(InterpRef), Consumer(std::move(C)) {} ++ ++ bool HandleTopLevelDecl(DeclGroupRef DGR) override final { ++ if (DGR.isNull()) ++ return true; ++ if (!Consumer) ++ return true; ++ ++ for (Decl *D : DGR) ++ if (auto *TSD = llvm::dyn_cast(D); ++ TSD && TSD->isSemiMissing()) ++ TSD->setStmt(Interp.SynthesizeExpr(cast(TSD->getStmt()))); ++ ++ return Consumer->HandleTopLevelDecl(DGR); ++ } ++ void HandleTranslationUnit(ASTContext &Ctx) override final { ++ Consumer->HandleTranslationUnit(Ctx); ++ } ++ void HandleInlineFunctionDefinition(FunctionDecl *D) override final { ++ Consumer->HandleInlineFunctionDefinition(D); ++ } ++ void HandleInterestingDecl(DeclGroupRef D) override final { ++ Consumer->HandleInterestingDecl(D); ++ } ++ void HandleTagDeclDefinition(TagDecl *D) override final { ++ Consumer->HandleTagDeclDefinition(D); ++ } ++ void HandleTagDeclRequiredDefinition(const TagDecl *D) override final { ++ Consumer->HandleTagDeclRequiredDefinition(D); ++ } ++ void HandleCXXImplicitFunctionInstantiation(FunctionDecl *D) override final { ++ Consumer->HandleCXXImplicitFunctionInstantiation(D); ++ } ++ void HandleTopLevelDeclInObjCContainer(DeclGroupRef D) override final { ++ Consumer->HandleTopLevelDeclInObjCContainer(D); ++ } ++ void HandleImplicitImportDecl(ImportDecl *D) override final { ++ Consumer->HandleImplicitImportDecl(D); ++ } ++ void CompleteTentativeDefinition(VarDecl *D) override final { ++ Consumer->CompleteTentativeDefinition(D); ++ } ++ void CompleteExternalDeclaration(VarDecl *D) override final { ++ Consumer->CompleteExternalDeclaration(D); ++ } ++ void AssignInheritanceModel(CXXRecordDecl *RD) override final { ++ Consumer->AssignInheritanceModel(RD); ++ } ++ void HandleCXXStaticMemberVarInstantiation(VarDecl *D) override final { ++ Consumer->HandleCXXStaticMemberVarInstantiation(D); ++ } ++ void HandleVTable(CXXRecordDecl *RD) override final { ++ Consumer->HandleVTable(RD); ++ } ++ ASTMutationListener *GetASTMutationListener() override final { ++ return Consumer->GetASTMutationListener(); ++ } ++ ASTDeserializationListener *GetASTDeserializationListener() override final { ++ return Consumer->GetASTDeserializationListener(); ++ } ++ void PrintStats() override final { Consumer->PrintStats(); } ++ bool shouldSkipFunctionBody(Decl *D) override final { ++ return Consumer->shouldSkipFunctionBody(D); ++ } ++ static bool classof(const clang::ASTConsumer *) { return true; } ++}; ++ + /// A custom action enabling the incremental processing functionality. + /// + /// The usual \p FrontendAction expects one call to ExecuteAction and once it +@@ -122,7 +194,8 @@ public: + } + }; + +-IncrementalParser::IncrementalParser(std::unique_ptr Instance, ++IncrementalParser::IncrementalParser(Interpreter &Interp, ++ std::unique_ptr Instance, + llvm::LLVMContext &LLVMCtx, + llvm::Error &Err) + : CI(std::move(Instance)) { +@@ -131,6 +204,9 @@ IncrementalParser::IncrementalParser(std::unique_ptr Instance, + if (Err) + return; + CI->ExecuteAction(*Act); ++ std::unique_ptr IncrConsumer = ++ std::make_unique(Interp, CI->takeASTConsumer()); ++ CI->setASTConsumer(std::move(IncrConsumer)); + Consumer = &CI->getASTConsumer(); + P.reset( + new Parser(CI->getPreprocessor(), CI->getSema(), /*SkipBodies=*/false)); +@@ -158,8 +234,8 @@ IncrementalParser::ParseOrWrapTopLevelDecl() { + LastPTU.TUPart = C.getTranslationUnitDecl(); + + // Skip previous eof due to last incremental input. +- if (P->getCurToken().is(tok::eof)) { +- P->ConsumeToken(); ++ if (P->getCurToken().is(tok::annot_repl_input_end)) { ++ P->ConsumeAnyToken(); + // FIXME: Clang does not call ExitScope on finalizing the regular TU, we + // might want to do that around HandleEndOfTranslationUnit. + P->ExitScope(); +@@ -259,23 +335,28 @@ IncrementalParser::Parse(llvm::StringRef input) { + Token Tok; + do { + PP.Lex(Tok); +- } while (Tok.isNot(tok::eof)); ++ } while (Tok.isNot(tok::annot_repl_input_end)); ++ } else { ++ Token AssertTok; ++ PP.Lex(AssertTok); ++ assert(AssertTok.is(tok::annot_repl_input_end) && ++ "Lexer must be EOF when starting incremental parse!"); + } + +- Token AssertTok; +- PP.Lex(AssertTok); +- assert(AssertTok.is(tok::eof) && +- "Lexer must be EOF when starting incremental parse!"); ++ if (std::unique_ptr M = GenModule()) ++ PTU->TheModule = std::move(M); ++ ++ return PTU; ++} + ++std::unique_ptr IncrementalParser::GenModule() { ++ static unsigned ID = 0; + if (CodeGenerator *CG = getCodeGen(Act.get())) { + std::unique_ptr M(CG->ReleaseModule()); +- CG->StartModule("incr_module_" + std::to_string(PTUs.size()), +- M->getContext()); +- +- PTU->TheModule = std::move(M); ++ CG->StartModule("incr_module_" + std::to_string(ID++), M->getContext()); ++ return M; + } +- +- return PTU; ++ return nullptr; + } + + void IncrementalParser::CleanUpPTU(PartialTranslationUnit &PTU) { +diff --git a/clang/lib/Interpreter/IncrementalParser.h b/clang/lib/Interpreter/IncrementalParser.h +index 8e45d6b59..99e37588d 100644 +--- a/clang/lib/Interpreter/IncrementalParser.h ++++ b/clang/lib/Interpreter/IncrementalParser.h +@@ -16,7 +16,6 @@ + #include "clang/Interpreter/PartialTranslationUnit.h" + + #include "clang/AST/GlobalDecl.h" +- + #include "llvm/ADT/ArrayRef.h" + #include "llvm/ADT/StringRef.h" + #include "llvm/Support/Error.h" +@@ -31,8 +30,8 @@ namespace clang { + class ASTConsumer; + class CompilerInstance; + class IncrementalAction; ++class Interpreter; + class Parser; +- + /// Provides support for incremental compilation. Keeps track of the state + /// changes between the subsequent incremental input. + /// +@@ -57,7 +56,8 @@ class IncrementalParser { + std::list PTUs; + + public: +- IncrementalParser(std::unique_ptr Instance, ++ IncrementalParser(Interpreter &Interp, ++ std::unique_ptr Instance, + llvm::LLVMContext &LLVMCtx, llvm::Error &Err); + ~IncrementalParser(); + +@@ -76,6 +76,8 @@ public: + + std::list &getPTUs() { return PTUs; } + ++ std::unique_ptr GenModule(); ++ + private: + llvm::Expected ParseOrWrapTopLevelDecl(); + }; +diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp +index a6f5fdc6e..4391bd008 100644 +--- a/clang/lib/Interpreter/Interpreter.cpp ++++ b/clang/lib/Interpreter/Interpreter.cpp +@@ -16,7 +16,11 @@ + #include "IncrementalExecutor.h" + #include "IncrementalParser.h" + ++#include "InterpreterUtils.h" + #include "clang/AST/ASTContext.h" ++#include "clang/AST/Mangle.h" ++#include "clang/AST/TypeVisitor.h" ++#include "clang/Basic/DiagnosticSema.h" + #include "clang/Basic/TargetInfo.h" + #include "clang/CodeGen/ModuleBuilder.h" + #include "clang/CodeGen/ObjectFilePCHContainerOperations.h" +@@ -27,12 +31,16 @@ + #include "clang/Driver/Tool.h" + #include "clang/Frontend/CompilerInstance.h" + #include "clang/Frontend/TextDiagnosticBuffer.h" ++#include "clang/Interpreter/Value.h" + #include "clang/Lex/PreprocessorOptions.h" +- ++#include "clang/Sema/Lookup.h" ++#include "llvm/ExecutionEngine/JITSymbol.h" ++#include "llvm/ExecutionEngine/Orc/LLJIT.h" + #include "llvm/IR/Module.h" + #include "llvm/Support/Errc.h" ++#include "llvm/Support/ErrorHandling.h" ++#include "llvm/Support/raw_ostream.h" + #include "llvm/Support/Host.h" +- + using namespace clang; + + // FIXME: Figure out how to unify with namespace init_convenience from +@@ -176,7 +184,7 @@ Interpreter::Interpreter(std::unique_ptr CI, + llvm::ErrorAsOutParameter EAO(&Err); + auto LLVMCtx = std::make_unique(); + TSCtx = std::make_unique(std::move(LLVMCtx)); +- IncrParser = std::make_unique(std::move(CI), ++ IncrParser = std::make_unique(*this, std::move(CI), + *TSCtx->getContext(), Err); + } + +@@ -189,6 +197,29 @@ Interpreter::~Interpreter() { + } + } + ++// These better to put in a runtime header but we can't. This is because we ++// can't find the precise resource directory in unittests so we have to hard ++// code them. ++const char *const Runtimes = R"( ++ void* operator new(__SIZE_TYPE__, void* __p) noexcept; ++ void *__clang_Interpreter_SetValueWithAlloc(void*, void*, void*); ++ void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*); ++ void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, void*); ++ void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, float); ++ void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, double); ++ void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, long double); ++ void __clang_Interpreter_SetValueNoAlloc(void*,void*,void*,unsigned long long); ++ template ++ void __clang_Interpreter_SetValueCopyArr(T* Src, void* Placement, unsigned long Size) { ++ for (auto Idx = 0; Idx < Size; ++Idx) ++ new ((void*)(((T*)Placement) + Idx)) T(Src[Idx]); ++ } ++ template ++ void __clang_Interpreter_SetValueCopyArr(const T (*Src)[N], void* Placement, unsigned long Size) { ++ __clang_Interpreter_SetValueCopyArr(Src[0], Placement, Size); ++ } ++)"; ++ + llvm::Expected> + Interpreter::create(std::unique_ptr CI) { + llvm::Error Err = llvm::Error::success(); +@@ -196,6 +227,15 @@ Interpreter::create(std::unique_ptr CI) { + std::unique_ptr(new Interpreter(std::move(CI), Err)); + if (Err) + return std::move(Err); ++ auto PTU = Interp->Parse(Runtimes); ++ if (!PTU) ++ return PTU.takeError(); ++ ++ Interp->ValuePrintingInfo.resize(3); ++ // FIXME: This is a ugly hack. Undo command checks its availability by looking ++ // at the size of the PTU list. However we have parsed something in the ++ // beginning of the REPL so we have to mark them as 'Irrevocable'. ++ Interp->InitPTUSize = Interp->IncrParser->getPTUs().size(); + return std::move(Interp); + } + +@@ -203,25 +243,53 @@ const CompilerInstance *Interpreter::getCompilerInstance() const { + return IncrParser->getCI(); + } + +-const llvm::orc::LLJIT *Interpreter::getExecutionEngine() const { +- if (IncrExecutor) +- return IncrExecutor->getExecutionEngine(); +- return nullptr; ++llvm::Expected Interpreter::getExecutionEngine() { ++ if (!IncrExecutor) { ++ if (auto Err = CreateExecutor()) ++ return std::move(Err); ++ } ++ ++ return IncrExecutor->GetExecutionEngine(); ++} ++ ++ASTContext &Interpreter::getASTContext() { ++ return getCompilerInstance()->getASTContext(); ++} ++ ++const ASTContext &Interpreter::getASTContext() const { ++ return getCompilerInstance()->getASTContext(); ++} ++ ++size_t Interpreter::getEffectivePTUSize() const { ++ std::list &PTUs = IncrParser->getPTUs(); ++ assert(PTUs.size() >= InitPTUSize && "empty PTU list?"); ++ return PTUs.size() - InitPTUSize; + } + + llvm::Expected + Interpreter::Parse(llvm::StringRef Code) { ++ // Tell the interpreter sliently ignore unused expressions since value ++ // printing could cause it. ++ getCompilerInstance()->getDiagnostics().setSeverity( ++ clang::diag::warn_unused_expr, diag::Severity::Ignored, SourceLocation()); + return IncrParser->Parse(Code); + } + ++llvm::Error Interpreter::CreateExecutor() { ++ const clang::TargetInfo &TI = ++ getCompilerInstance()->getASTContext().getTargetInfo(); ++ llvm::Error Err = llvm::Error::success(); ++ auto Executor = std::make_unique(*TSCtx, Err, TI); ++ if (!Err) ++ IncrExecutor = std::move(Executor); ++ ++ return Err; ++} ++ + llvm::Error Interpreter::Execute(PartialTranslationUnit &T) { + assert(T.TheModule); + if (!IncrExecutor) { +- const clang::TargetInfo &TI = +- getCompilerInstance()->getASTContext().getTargetInfo(); +- llvm::Error Err = llvm::Error::success(); +- IncrExecutor = std::make_unique(*TSCtx, Err, TI); +- ++ auto Err = CreateExecutor(); + if (Err) + return Err; + } +@@ -235,7 +303,26 @@ llvm::Error Interpreter::Execute(PartialTranslationUnit &T) { + return llvm::Error::success(); + } + +-llvm::Expected ++llvm::Error Interpreter::ParseAndExecute(llvm::StringRef Code, Value *V) { ++ ++ auto PTU = Parse(Code); ++ if (!PTU) ++ return PTU.takeError(); ++ if (PTU->TheModule) ++ if (llvm::Error Err = Execute(*PTU)) ++ return Err; ++ ++ if (LastValue.isValid()) { ++ if (!V) { ++ LastValue.dump(); ++ LastValue.clear(); ++ } else ++ *V = std::move(LastValue); ++ } ++ return llvm::Error::success(); ++} ++ ++llvm::Expected + Interpreter::getSymbolAddress(GlobalDecl GD) const { + if (!IncrExecutor) + return llvm::make_error("Operation failed. " +@@ -245,7 +332,7 @@ Interpreter::getSymbolAddress(GlobalDecl GD) const { + return getSymbolAddress(MangledName); + } + +-llvm::Expected ++llvm::Expected + Interpreter::getSymbolAddress(llvm::StringRef IRName) const { + if (!IncrExecutor) + return llvm::make_error("Operation failed. " +@@ -255,7 +342,7 @@ Interpreter::getSymbolAddress(llvm::StringRef IRName) const { + return IncrExecutor->getSymbolAddress(IRName, IncrementalExecutor::IRName); + } + +-llvm::Expected ++llvm::Expected + Interpreter::getSymbolAddressFromLinkerName(llvm::StringRef Name) const { + if (!IncrExecutor) + return llvm::make_error("Operation failed. " +@@ -268,7 +355,7 @@ Interpreter::getSymbolAddressFromLinkerName(llvm::StringRef Name) const { + llvm::Error Interpreter::Undo(unsigned N) { + + std::list &PTUs = IncrParser->getPTUs(); +- if (N > PTUs.size()) ++ if (N > getEffectivePTUSize()) + return llvm::make_error("Operation failed. " + "Too many undos", + std::error_code()); +@@ -283,3 +370,359 @@ llvm::Error Interpreter::Undo(unsigned N) { + } + return llvm::Error::success(); + } ++ ++llvm::Error Interpreter::LoadDynamicLibrary(const char *name) { ++ auto EE = getExecutionEngine(); ++ if (!EE) ++ return EE.takeError(); ++ ++ auto &DL = EE->getDataLayout(); ++ ++ if (auto DLSG = llvm::orc::DynamicLibrarySearchGenerator::Load( ++ name, DL.getGlobalPrefix())) ++ EE->getMainJITDylib().addGenerator(std::move(*DLSG)); ++ else ++ return DLSG.takeError(); ++ ++ return llvm::Error::success(); ++} ++ ++llvm::Expected ++Interpreter::CompileDtorCall(CXXRecordDecl *CXXRD) { ++ assert(CXXRD && "Cannot compile a destructor for a nullptr"); ++ if (auto Dtor = Dtors.find(CXXRD); Dtor != Dtors.end()) ++ return Dtor->getSecond(); ++ ++ if (CXXRD->hasIrrelevantDestructor()) ++ return llvm::orc::ExecutorAddr{}; ++ ++ CXXDestructorDecl *DtorRD = ++ getCompilerInstance()->getSema().LookupDestructor(CXXRD); ++ ++ llvm::StringRef Name = ++ IncrParser->GetMangledName(GlobalDecl(DtorRD, Dtor_Base)); ++ auto AddrOrErr = getSymbolAddress(Name); ++ if (!AddrOrErr) ++ return AddrOrErr.takeError(); ++ ++ Dtors[CXXRD] = *AddrOrErr; ++ return AddrOrErr; ++} ++ ++static constexpr llvm::StringRef MagicRuntimeInterface[] = { ++ "__clang_Interpreter_SetValueNoAlloc", ++ "__clang_Interpreter_SetValueWithAlloc", ++ "__clang_Interpreter_SetValueCopyArr"}; ++ ++bool Interpreter::FindRuntimeInterface() { ++ if (llvm::all_of(ValuePrintingInfo, [](Expr *E) { return E != nullptr; })) ++ return true; ++ ++ Sema &S = getCompilerInstance()->getSema(); ++ ASTContext &Ctx = S.getASTContext(); ++ ++ auto LookupInterface = [&](Expr *&Interface, llvm::StringRef Name) { ++ LookupResult R(S, &Ctx.Idents.get(Name), SourceLocation(), ++ Sema::LookupOrdinaryName, Sema::ForVisibleRedeclaration); ++ S.LookupQualifiedName(R, Ctx.getTranslationUnitDecl()); ++ if (R.empty()) ++ return false; ++ ++ CXXScopeSpec CSS; ++ Interface = S.BuildDeclarationNameExpr(CSS, R, /*ADL=*/false).get(); ++ return true; ++ }; ++ ++ if (!LookupInterface(ValuePrintingInfo[NoAlloc], ++ MagicRuntimeInterface[NoAlloc])) ++ return false; ++ if (!LookupInterface(ValuePrintingInfo[WithAlloc], ++ MagicRuntimeInterface[WithAlloc])) ++ return false; ++ if (!LookupInterface(ValuePrintingInfo[CopyArray], ++ MagicRuntimeInterface[CopyArray])) ++ return false; ++ return true; ++} ++ ++namespace { ++ ++class RuntimeInterfaceBuilder ++ : public TypeVisitor { ++ clang::Interpreter &Interp; ++ ASTContext &Ctx; ++ Sema &S; ++ Expr *E; ++ llvm::SmallVector Args; ++ ++public: ++ RuntimeInterfaceBuilder(clang::Interpreter &In, ASTContext &C, Sema &SemaRef, ++ Expr *VE, ArrayRef FixedArgs) ++ : Interp(In), Ctx(C), S(SemaRef), E(VE) { ++ // The Interpreter* parameter and the out parameter `OutVal`. ++ for (Expr *E : FixedArgs) ++ Args.push_back(E); ++ ++ // Get rid of ExprWithCleanups. ++ if (auto *EWC = llvm::dyn_cast_if_present(E)) ++ E = EWC->getSubExpr(); ++ } ++ ++ ExprResult getCall() { ++ QualType Ty = E->getType(); ++ QualType DesugaredTy = Ty.getDesugaredType(Ctx); ++ ++ // For lvalue struct, we treat it as a reference. ++ if (DesugaredTy->isRecordType() && E->isLValue()) { ++ DesugaredTy = Ctx.getLValueReferenceType(DesugaredTy); ++ Ty = Ctx.getLValueReferenceType(Ty); ++ } ++ ++ Expr *TypeArg = ++ CStyleCastPtrExpr(S, Ctx.VoidPtrTy, (uintptr_t)Ty.getAsOpaquePtr()); ++ // The QualType parameter `OpaqueType`, represented as `void*`. ++ Args.push_back(TypeArg); ++ ++ // We push the last parameter based on the type of the Expr. Note we need ++ // special care for rvalue struct. ++ Interpreter::InterfaceKind Kind = Visit(&*DesugaredTy); ++ switch (Kind) { ++ case Interpreter::InterfaceKind::WithAlloc: ++ case Interpreter::InterfaceKind::CopyArray: { ++ // __clang_Interpreter_SetValueWithAlloc. ++ ExprResult AllocCall = S.ActOnCallExpr( ++ /*Scope=*/nullptr, ++ Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::WithAlloc], ++ E->getBeginLoc(), Args, E->getEndLoc()); ++ assert(!AllocCall.isInvalid() && "Can't create runtime interface call!"); ++ ++ TypeSourceInfo *TSI = Ctx.getTrivialTypeSourceInfo(Ty, SourceLocation()); ++ ++ // Force CodeGen to emit destructor. ++ if (auto *RD = Ty->getAsCXXRecordDecl()) { ++ auto *Dtor = S.LookupDestructor(RD); ++ Dtor->addAttr(UsedAttr::CreateImplicit(Ctx)); ++ Interp.getCompilerInstance()->getASTConsumer().HandleTopLevelDecl( ++ DeclGroupRef(Dtor)); ++ } ++ ++ // __clang_Interpreter_SetValueCopyArr. ++ if (Kind == Interpreter::InterfaceKind::CopyArray) { ++ const auto *ConstantArrTy = ++ cast(DesugaredTy.getTypePtr()); ++ size_t ArrSize = Ctx.getConstantArrayElementCount(ConstantArrTy); ++ Expr *ArrSizeExpr = IntegerLiteralExpr(Ctx, ArrSize); ++ Expr *Args[] = {E, AllocCall.get(), ArrSizeExpr}; ++ return S.ActOnCallExpr( ++ /*Scope *=*/nullptr, ++ Interp ++ .getValuePrintingInfo()[Interpreter::InterfaceKind::CopyArray], ++ SourceLocation(), Args, SourceLocation()); ++ } ++ Expr *Args[] = {AllocCall.get()}; ++ ExprResult CXXNewCall = S.BuildCXXNew( ++ E->getSourceRange(), ++ /*UseGlobal=*/true, /*PlacementLParen=*/SourceLocation(), Args, ++ /*PlacementRParen=*/SourceLocation(), ++ /*TypeIdParens=*/SourceRange(), TSI->getType(), TSI, std::nullopt, ++ E->getSourceRange(), E); ++ ++ assert(!CXXNewCall.isInvalid() && ++ "Can't create runtime placement new call!"); ++ ++ return S.ActOnFinishFullExpr(CXXNewCall.get(), ++ /*DiscardedValue=*/false); ++ } ++ // __clang_Interpreter_SetValueNoAlloc. ++ case Interpreter::InterfaceKind::NoAlloc: { ++ return S.ActOnCallExpr( ++ /*Scope=*/nullptr, ++ Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NoAlloc], ++ E->getBeginLoc(), Args, E->getEndLoc()); ++ } ++ } ++ llvm_unreachable("Unhandled Interpreter::InterfaceKind"); ++ } ++ ++ Interpreter::InterfaceKind VisitRecordType(const RecordType *Ty) { ++ return Interpreter::InterfaceKind::WithAlloc; ++ } ++ ++ Interpreter::InterfaceKind ++ VisitMemberPointerType(const MemberPointerType *Ty) { ++ return Interpreter::InterfaceKind::WithAlloc; ++ } ++ ++ Interpreter::InterfaceKind ++ VisitConstantArrayType(const ConstantArrayType *Ty) { ++ return Interpreter::InterfaceKind::CopyArray; ++ } ++ ++ Interpreter::InterfaceKind ++ VisitFunctionProtoType(const FunctionProtoType *Ty) { ++ HandlePtrType(Ty); ++ return Interpreter::InterfaceKind::NoAlloc; ++ } ++ ++ Interpreter::InterfaceKind VisitPointerType(const PointerType *Ty) { ++ HandlePtrType(Ty); ++ return Interpreter::InterfaceKind::NoAlloc; ++ } ++ ++ Interpreter::InterfaceKind VisitReferenceType(const ReferenceType *Ty) { ++ ExprResult AddrOfE = S.CreateBuiltinUnaryOp(SourceLocation(), UO_AddrOf, E); ++ assert(!AddrOfE.isInvalid() && "Can not create unary expression"); ++ Args.push_back(AddrOfE.get()); ++ return Interpreter::InterfaceKind::NoAlloc; ++ } ++ ++ Interpreter::InterfaceKind VisitBuiltinType(const BuiltinType *Ty) { ++ if (Ty->isNullPtrType()) ++ Args.push_back(E); ++ else if (Ty->isFloatingType()) ++ Args.push_back(E); ++ else if (Ty->isIntegralOrEnumerationType()) ++ HandleIntegralOrEnumType(Ty); ++ else if (Ty->isVoidType()) { ++ // Do we need to still run `E`? ++ } ++ ++ return Interpreter::InterfaceKind::NoAlloc; ++ } ++ ++ Interpreter::InterfaceKind VisitEnumType(const EnumType *Ty) { ++ HandleIntegralOrEnumType(Ty); ++ return Interpreter::InterfaceKind::NoAlloc; ++ } ++ ++private: ++ // Force cast these types to uint64 to reduce the number of overloads of ++ // `__clang_Interpreter_SetValueNoAlloc`. ++ void HandleIntegralOrEnumType(const Type *Ty) { ++ TypeSourceInfo *TSI = Ctx.getTrivialTypeSourceInfo(Ctx.UnsignedLongLongTy); ++ ExprResult CastedExpr = ++ S.BuildCStyleCastExpr(SourceLocation(), TSI, SourceLocation(), E); ++ assert(!CastedExpr.isInvalid() && "Cannot create cstyle cast expr"); ++ Args.push_back(CastedExpr.get()); ++ } ++ ++ void HandlePtrType(const Type *Ty) { ++ TypeSourceInfo *TSI = Ctx.getTrivialTypeSourceInfo(Ctx.VoidPtrTy); ++ ExprResult CastedExpr = ++ S.BuildCStyleCastExpr(SourceLocation(), TSI, SourceLocation(), E); ++ assert(!CastedExpr.isInvalid() && "Can not create cstyle cast expression"); ++ Args.push_back(CastedExpr.get()); ++ } ++}; ++} // namespace ++ ++// This synthesizes a call expression to a speciall ++// function that is responsible for generating the Value. ++// In general, we transform: ++// clang-repl> x ++// To: ++// // 1. If x is a built-in type like int, float. ++// __clang_Interpreter_SetValueNoAlloc(ThisInterp, OpaqueValue, xQualType, x); ++// // 2. If x is a struct, and a lvalue. ++// __clang_Interpreter_SetValueNoAlloc(ThisInterp, OpaqueValue, xQualType, ++// &x); ++// // 3. If x is a struct, but a rvalue. ++// new (__clang_Interpreter_SetValueWithAlloc(ThisInterp, OpaqueValue, ++// xQualType)) (x); ++ ++Expr *Interpreter::SynthesizeExpr(Expr *E) { ++ Sema &S = getCompilerInstance()->getSema(); ++ ASTContext &Ctx = S.getASTContext(); ++ ++ if (!FindRuntimeInterface()) ++ llvm_unreachable("We can't find the runtime iterface for pretty print!"); ++ ++ // Create parameter `ThisInterp`. ++ auto *ThisInterp = CStyleCastPtrExpr(S, Ctx.VoidPtrTy, (uintptr_t)this); ++ ++ // Create parameter `OutVal`. ++ auto *OutValue = CStyleCastPtrExpr(S, Ctx.VoidPtrTy, (uintptr_t)&LastValue); ++ ++ // Build `__clang_Interpreter_SetValue*` call. ++ RuntimeInterfaceBuilder Builder(*this, Ctx, S, E, {ThisInterp, OutValue}); ++ ++ ExprResult Result = Builder.getCall(); ++ // It could fail, like printing an array type in C. (not supported) ++ if (Result.isInvalid()) ++ return E; ++ return Result.get(); ++} ++ ++// Temporary rvalue struct that need special care. ++REPL_EXTERNAL_VISIBILITY void * ++__clang_Interpreter_SetValueWithAlloc(void *This, void *OutVal, ++ void *OpaqueType) { ++ Value &VRef = *(Value *)OutVal; ++ VRef = Value(static_cast(This), OpaqueType); ++ return VRef.getPtr(); ++} ++ ++// Pointers, lvalue struct that can take as a reference. ++REPL_EXTERNAL_VISIBILITY void ++__clang_Interpreter_SetValueNoAlloc(void *This, void *OutVal, void *OpaqueType, ++ void *Val) { ++ Value &VRef = *(Value *)OutVal; ++ VRef = Value(static_cast(This), OpaqueType); ++ VRef.setPtr(Val); ++} ++ ++REPL_EXTERNAL_VISIBILITY void ++__clang_Interpreter_SetValueNoAlloc(void *This, void *OutVal, ++ void *OpaqueType) { ++ Value &VRef = *(Value *)OutVal; ++ VRef = Value(static_cast(This), OpaqueType); ++} ++ ++static void SetValueDataBasedOnQualType(Value &V, unsigned long long Data) { ++ QualType QT = V.getType(); ++ if (const auto *ET = QT->getAs()) ++ QT = ET->getDecl()->getIntegerType(); ++ ++ switch (QT->getAs()->getKind()) { ++ default: ++ llvm_unreachable("unknown type kind!"); ++#define X(type, name) \ ++ case BuiltinType::name: \ ++ V.set##name(Data); \ ++ break; ++ REPL_BUILTIN_TYPES ++#undef X ++ } ++} ++ ++REPL_EXTERNAL_VISIBILITY void ++__clang_Interpreter_SetValueNoAlloc(void *This, void *OutVal, void *OpaqueType, ++ unsigned long long Val) { ++ Value &VRef = *(Value *)OutVal; ++ VRef = Value(static_cast(This), OpaqueType); ++ SetValueDataBasedOnQualType(VRef, Val); ++} ++ ++REPL_EXTERNAL_VISIBILITY void ++__clang_Interpreter_SetValueNoAlloc(void *This, void *OutVal, void *OpaqueType, ++ float Val) { ++ Value &VRef = *(Value *)OutVal; ++ VRef = Value(static_cast(This), OpaqueType); ++ VRef.setFloat(Val); ++} ++ ++REPL_EXTERNAL_VISIBILITY void ++__clang_Interpreter_SetValueNoAlloc(void *This, void *OutVal, void *OpaqueType, ++ double Val) { ++ Value &VRef = *(Value *)OutVal; ++ VRef = Value(static_cast(This), OpaqueType); ++ VRef.setDouble(Val); ++} ++ ++REPL_EXTERNAL_VISIBILITY void ++__clang_Interpreter_SetValueNoAlloc(void *This, void *OutVal, void *OpaqueType, ++ long double Val) { ++ Value &VRef = *(Value *)OutVal; ++ VRef = Value(static_cast(This), OpaqueType); ++ VRef.setLongDouble(Val); ++} +diff --git a/clang/lib/Interpreter/InterpreterUtils.cpp b/clang/lib/Interpreter/InterpreterUtils.cpp +new file mode 100644 +index 000000000..c19cf6aa3 +--- /dev/null ++++ b/clang/lib/Interpreter/InterpreterUtils.cpp +@@ -0,0 +1,111 @@ ++//===--- InterpreterUtils.cpp - Incremental Utils --------*- C++ -*-===// ++// ++// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. ++// See https://llvm.org/LICENSE.txt for license information. ++// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ++// ++//===----------------------------------------------------------------------===// ++// ++// This file implements some common utils used in the incremental library. ++// ++//===----------------------------------------------------------------------===// ++ ++#include "InterpreterUtils.h" ++ ++namespace clang { ++ ++IntegerLiteral *IntegerLiteralExpr(ASTContext &C, uint64_t Val) { ++ return IntegerLiteral::Create(C, llvm::APSInt::getUnsigned(Val), ++ C.UnsignedLongLongTy, SourceLocation()); ++} ++ ++Expr *CStyleCastPtrExpr(Sema &S, QualType Ty, Expr *E) { ++ ASTContext &Ctx = S.getASTContext(); ++ if (!Ty->isPointerType()) ++ Ty = Ctx.getPointerType(Ty); ++ ++ TypeSourceInfo *TSI = Ctx.getTrivialTypeSourceInfo(Ty, SourceLocation()); ++ Expr *Result = ++ S.BuildCStyleCastExpr(SourceLocation(), TSI, SourceLocation(), E).get(); ++ assert(Result && "Cannot create CStyleCastPtrExpr"); ++ return Result; ++} ++ ++Expr *CStyleCastPtrExpr(Sema &S, QualType Ty, uintptr_t Ptr) { ++ ASTContext &Ctx = S.getASTContext(); ++ return CStyleCastPtrExpr(S, Ty, IntegerLiteralExpr(Ctx, (uint64_t)Ptr)); ++} ++ ++Sema::DeclGroupPtrTy CreateDGPtrFrom(Sema &S, Decl *D) { ++ SmallVector DeclsInGroup; ++ DeclsInGroup.push_back(D); ++ Sema::DeclGroupPtrTy DeclGroupPtr = S.BuildDeclaratorGroup(DeclsInGroup); ++ return DeclGroupPtr; ++} ++ ++NamespaceDecl *LookupNamespace(Sema &S, llvm::StringRef Name, ++ const DeclContext *Within) { ++ DeclarationName DName = &S.Context.Idents.get(Name); ++ LookupResult R(S, DName, SourceLocation(), ++ Sema::LookupNestedNameSpecifierName); ++ R.suppressDiagnostics(); ++ if (!Within) ++ S.LookupName(R, S.TUScope); ++ else { ++ if (const auto *TD = dyn_cast(Within); ++ TD && !TD->getDefinition()) ++ // No definition, no lookup result. ++ return nullptr; ++ ++ S.LookupQualifiedName(R, const_cast(Within)); ++ } ++ ++ if (R.empty()) ++ return nullptr; ++ ++ R.resolveKind(); ++ ++ return dyn_cast(R.getFoundDecl()); ++} ++ ++NamedDecl *LookupNamed(Sema &S, llvm::StringRef Name, ++ const DeclContext *Within) { ++ DeclarationName DName = &S.Context.Idents.get(Name); ++ LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, ++ Sema::ForVisibleRedeclaration); ++ ++ R.suppressDiagnostics(); ++ ++ if (!Within) ++ S.LookupName(R, S.TUScope); ++ else { ++ const DeclContext *PrimaryWithin = nullptr; ++ if (const auto *TD = dyn_cast(Within)) ++ PrimaryWithin = llvm::dyn_cast_or_null(TD->getDefinition()); ++ else ++ PrimaryWithin = Within->getPrimaryContext(); ++ ++ // No definition, no lookup result. ++ if (!PrimaryWithin) ++ return nullptr; ++ ++ S.LookupQualifiedName(R, const_cast(PrimaryWithin)); ++ } ++ ++ if (R.empty()) ++ return nullptr; ++ R.resolveKind(); ++ ++ if (R.isSingleResult()) ++ return llvm::dyn_cast(R.getFoundDecl()); ++ ++ return nullptr; ++} ++ ++std::string GetFullTypeName(ASTContext &Ctx, QualType QT) { ++ PrintingPolicy Policy(Ctx.getPrintingPolicy()); ++ Policy.SuppressScope = false; ++ Policy.AnonymousTagLocations = false; ++ return QT.getAsString(Policy); ++} ++} // namespace clang +diff --git a/clang/lib/Interpreter/InterpreterUtils.h b/clang/lib/Interpreter/InterpreterUtils.h +new file mode 100644 +index 000000000..8df158c17 +--- /dev/null ++++ b/clang/lib/Interpreter/InterpreterUtils.h +@@ -0,0 +1,54 @@ ++//===--- InterpreterUtils.h - Incremental Utils --------*- C++ -*-===// ++// ++// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. ++// See https://llvm.org/LICENSE.txt for license information. ++// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ++// ++//===----------------------------------------------------------------------===// ++// ++// This file implements some common utils used in the incremental library. ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLVM_CLANG_INTERPRETER_UTILS_H ++#define LLVM_CLANG_INTERPRETER_UTILS_H ++ ++#include "clang/AST/ASTContext.h" ++#include "clang/AST/Mangle.h" ++#include "clang/AST/TypeVisitor.h" ++#include "clang/Basic/TargetInfo.h" ++#include "clang/CodeGen/ModuleBuilder.h" ++#include "clang/CodeGen/ObjectFilePCHContainerOperations.h" ++#include "clang/Driver/Compilation.h" ++#include "clang/Driver/Driver.h" ++#include "clang/Driver/Job.h" ++#include "clang/Driver/Options.h" ++#include "clang/Driver/Tool.h" ++#include "clang/Frontend/CompilerInstance.h" ++#include "clang/Frontend/TextDiagnosticBuffer.h" ++#include "clang/Lex/PreprocessorOptions.h" ++ ++#include "clang/Sema/Lookup.h" ++#include "llvm/IR/Module.h" ++#include "llvm/Support/Errc.h" ++#include "llvm/TargetParser/Host.h" ++ ++namespace clang { ++IntegerLiteral *IntegerLiteralExpr(ASTContext &C, uint64_t Val); ++ ++Expr *CStyleCastPtrExpr(Sema &S, QualType Ty, Expr *E); ++ ++Expr *CStyleCastPtrExpr(Sema &S, QualType Ty, uintptr_t Ptr); ++ ++Sema::DeclGroupPtrTy CreateDGPtrFrom(Sema &S, Decl *D); ++ ++NamespaceDecl *LookupNamespace(Sema &S, llvm::StringRef Name, ++ const DeclContext *Within = nullptr); ++ ++NamedDecl *LookupNamed(Sema &S, llvm::StringRef Name, ++ const DeclContext *Within); ++ ++std::string GetFullTypeName(ASTContext &Ctx, QualType QT); ++} // namespace clang ++ ++#endif +diff --git a/clang/lib/Interpreter/Value.cpp b/clang/lib/Interpreter/Value.cpp +new file mode 100644 +index 000000000..fe37eebac +--- /dev/null ++++ b/clang/lib/Interpreter/Value.cpp +@@ -0,0 +1,266 @@ ++//===--- Interpreter.h - Incremental Compiation and Execution---*- C++ -*-===// ++// ++// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. ++// See https://llvm.org/LICENSE.txt for license information. ++// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ++// ++//===----------------------------------------------------------------------===// ++// ++// This file defines the class that used to represent a value in incremental ++// C++. ++// ++//===----------------------------------------------------------------------===// ++ ++#include "clang/Interpreter/Value.h" ++#include "clang/AST/ASTContext.h" ++#include "clang/AST/Type.h" ++#include "clang/Interpreter/Interpreter.h" ++#include "llvm/ADT/StringExtras.h" ++#include "llvm/Support/ErrorHandling.h" ++#include "llvm/Support/raw_os_ostream.h" ++#include ++#include ++#include ++ ++using namespace clang; ++ ++namespace { ++ ++// This is internal buffer maintained by Value, used to hold temporaries. ++class ValueStorage { ++public: ++ using DtorFunc = void (*)(void *); ++ ++ static unsigned char *CreatePayload(void *DtorF, size_t AllocSize, ++ size_t ElementsSize) { ++ if (AllocSize < sizeof(Canary)) ++ AllocSize = sizeof(Canary); ++ unsigned char *Buf = ++ new unsigned char[ValueStorage::getPayloadOffset() + AllocSize]; ++ ValueStorage *VS = new (Buf) ValueStorage(DtorF, AllocSize, ElementsSize); ++ std::memcpy(VS->getPayload(), Canary, sizeof(Canary)); ++ return VS->getPayload(); ++ } ++ ++ unsigned char *getPayload() { return Storage; } ++ const unsigned char *getPayload() const { return Storage; } ++ ++ static unsigned getPayloadOffset() { ++ static ValueStorage Dummy(nullptr, 0, 0); ++ return Dummy.getPayload() - reinterpret_cast(&Dummy); ++ } ++ ++ static ValueStorage *getFromPayload(void *Payload) { ++ ValueStorage *R = reinterpret_cast( ++ (unsigned char *)Payload - getPayloadOffset()); ++ return R; ++ } ++ ++ void Retain() { ++RefCnt; } ++ ++ void Release() { ++ assert(RefCnt > 0 && "Can't release if reference count is already zero"); ++ if (--RefCnt == 0) { ++ // We hace a non-trivial dtor. ++ if (Dtor && IsAlive()) { ++ assert(Elements && "We at least should have 1 element in Value"); ++ size_t Stride = AllocSize / Elements; ++ for (size_t Idx = 0; Idx < Elements; ++Idx) ++ (*Dtor)(getPayload() + Idx * Stride); ++ } ++ delete[] reinterpret_cast(this); ++ } ++ } ++ ++ // Check whether the storage is valid by validating the canary bits. ++ // If someone accidentally write some invalid bits in the storage, the canary ++ // will be changed first, and `IsAlive` will return false then. ++ bool IsAlive() const { ++ return std::memcmp(getPayload(), Canary, sizeof(Canary)) != 0; ++ } ++ ++private: ++ ValueStorage(void *DtorF, size_t AllocSize, size_t ElementsNum) ++ : RefCnt(1), Dtor(reinterpret_cast(DtorF)), ++ AllocSize(AllocSize), Elements(ElementsNum) {} ++ ++ mutable unsigned RefCnt; ++ DtorFunc Dtor = nullptr; ++ size_t AllocSize = 0; ++ size_t Elements = 0; ++ unsigned char Storage[1]; ++ ++ // These are some canary bits that are used for protecting the storage been ++ // damaged. ++ static constexpr unsigned char Canary[8] = {0x4c, 0x37, 0xad, 0x8f, ++ 0x2d, 0x23, 0x95, 0x91}; ++}; ++} // namespace ++ ++static Value::Kind ConvertQualTypeToKind(const ASTContext &Ctx, QualType QT) { ++ if (Ctx.hasSameType(QT, Ctx.VoidTy)) ++ return Value::K_Void; ++ ++ if (const auto *ET = QT->getAs()) ++ QT = ET->getDecl()->getIntegerType(); ++ ++ const auto *BT = QT->getAs(); ++ if (!BT || BT->isNullPtrType()) ++ return Value::K_PtrOrObj; ++ ++ switch (QT->getAs()->getKind()) { ++ default: ++ assert(false && "Type not supported"); ++ return Value::K_Unspecified; ++#define X(type, name) \ ++ case BuiltinType::name: \ ++ return Value::K_##name; ++ REPL_BUILTIN_TYPES ++#undef X ++ } ++} ++ ++Value::Value(Interpreter *In, void *Ty) : Interp(In), OpaqueType(Ty) { ++ setKind(ConvertQualTypeToKind(getASTContext(), getType())); ++ if (ValueKind == K_PtrOrObj) { ++ QualType Canon = getType().getCanonicalType(); ++ if ((Canon->isPointerType() || Canon->isObjectType() || ++ Canon->isReferenceType()) && ++ (Canon->isRecordType() || Canon->isConstantArrayType() || ++ Canon->isMemberPointerType())) { ++ IsManuallyAlloc = true; ++ // Compile dtor function. ++ Interpreter &Interp = getInterpreter(); ++ void *DtorF = nullptr; ++ size_t ElementsSize = 1; ++ QualType DtorTy = getType(); ++ ++ if (const auto *ArrTy = ++ llvm::dyn_cast(DtorTy.getTypePtr())) { ++ DtorTy = ArrTy->getElementType(); ++ llvm::APInt ArrSize(sizeof(size_t) * 8, 1); ++ do { ++ ArrSize *= ArrTy->getSize(); ++ ArrTy = llvm::dyn_cast( ++ ArrTy->getElementType().getTypePtr()); ++ } while (ArrTy); ++ ElementsSize = static_cast(ArrSize.getZExtValue()); ++ } ++ if (const auto *RT = DtorTy->getAs()) { ++ if (CXXRecordDecl *CXXRD = ++ llvm::dyn_cast(RT->getDecl())) { ++ if (llvm::Expected Addr = ++ Interp.CompileDtorCall(CXXRD)) ++ DtorF = reinterpret_cast(Addr->getValue()); ++ else ++ llvm::logAllUnhandledErrors(Addr.takeError(), llvm::errs()); ++ } ++ } ++ ++ size_t AllocSize = ++ getASTContext().getTypeSizeInChars(getType()).getQuantity(); ++ unsigned char *Payload = ++ ValueStorage::CreatePayload(DtorF, AllocSize, ElementsSize); ++ setPtr((void *)Payload); ++ } ++ } ++} ++ ++Value::Value(const Value &RHS) ++ : Interp(RHS.Interp), OpaqueType(RHS.OpaqueType), Data(RHS.Data), ++ ValueKind(RHS.ValueKind), IsManuallyAlloc(RHS.IsManuallyAlloc) { ++ if (IsManuallyAlloc) ++ ValueStorage::getFromPayload(getPtr())->Retain(); ++} ++ ++Value::Value(Value &&RHS) noexcept { ++ Interp = std::exchange(RHS.Interp, nullptr); ++ OpaqueType = std::exchange(RHS.OpaqueType, nullptr); ++ Data = RHS.Data; ++ ValueKind = std::exchange(RHS.ValueKind, K_Unspecified); ++ IsManuallyAlloc = std::exchange(RHS.IsManuallyAlloc, false); ++ ++ if (IsManuallyAlloc) ++ ValueStorage::getFromPayload(getPtr())->Release(); ++} ++ ++Value &Value::operator=(const Value &RHS) { ++ if (IsManuallyAlloc) ++ ValueStorage::getFromPayload(getPtr())->Release(); ++ ++ Interp = RHS.Interp; ++ OpaqueType = RHS.OpaqueType; ++ Data = RHS.Data; ++ ValueKind = RHS.ValueKind; ++ IsManuallyAlloc = RHS.IsManuallyAlloc; ++ ++ if (IsManuallyAlloc) ++ ValueStorage::getFromPayload(getPtr())->Retain(); ++ ++ return *this; ++} ++ ++Value &Value::operator=(Value &&RHS) noexcept { ++ if (IsManuallyAlloc) ++ ValueStorage::getFromPayload(getPtr())->Release(); ++ ++ Interp = std::exchange(RHS.Interp, nullptr); ++ OpaqueType = std::exchange(RHS.OpaqueType, nullptr); ++ ValueKind = std::exchange(RHS.ValueKind, K_Unspecified); ++ IsManuallyAlloc = std::exchange(RHS.IsManuallyAlloc, false); ++ ++ Data = RHS.Data; ++ ++ return *this; ++} ++ ++void Value::clear() { ++ if (IsManuallyAlloc) ++ ValueStorage::getFromPayload(getPtr())->Release(); ++ ValueKind = K_Unspecified; ++ OpaqueType = nullptr; ++ Interp = nullptr; ++ IsManuallyAlloc = false; ++} ++ ++Value::~Value() { clear(); } ++ ++void *Value::getPtr() const { ++ assert(ValueKind == K_PtrOrObj); ++ return Data.m_Ptr; ++} ++ ++QualType Value::getType() const { ++ return QualType::getFromOpaquePtr(OpaqueType); ++} ++ ++Interpreter &Value::getInterpreter() { ++ assert(Interp != nullptr && ++ "Can't get interpreter from a default constructed value"); ++ return *Interp; ++} ++ ++const Interpreter &Value::getInterpreter() const { ++ assert(Interp != nullptr && ++ "Can't get interpreter from a default constructed value"); ++ return *Interp; ++} ++ ++ASTContext &Value::getASTContext() { return getInterpreter().getASTContext(); } ++ ++const ASTContext &Value::getASTContext() const { ++ return getInterpreter().getASTContext(); ++} ++ ++void Value::dump() const { print(llvm::outs()); } ++ ++void Value::printType(llvm::raw_ostream &Out) const { ++ Out << "Not implement yet.\n"; ++} ++void Value::printData(llvm::raw_ostream &Out) const { ++ Out << "Not implement yet.\n"; ++} ++void Value::print(llvm::raw_ostream &Out) const { ++ assert(OpaqueType != nullptr && "Can't print default Value"); ++ Out << "Not implement yet.\n"; ++} +diff --git a/clang/lib/Lex/PPLexerChange.cpp b/clang/lib/Lex/PPLexerChange.cpp +index 66168467e..0822f83b5 100644 +--- a/clang/lib/Lex/PPLexerChange.cpp ++++ b/clang/lib/Lex/PPLexerChange.cpp +@@ -526,13 +526,19 @@ bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) { + return LeavingSubmodule; + } + } +- + // If this is the end of the main file, form an EOF token. + assert(CurLexer && "Got EOF but no current lexer set!"); + const char *EndPos = getCurLexerEndPos(); + Result.startToken(); + CurLexer->BufferPtr = EndPos; +- CurLexer->FormTokenWithChars(Result, EndPos, tok::eof); ++ ++ if (isIncrementalProcessingEnabled()) { ++ CurLexer->FormTokenWithChars(Result, EndPos, tok::annot_repl_input_end); ++ Result.setAnnotationEndLoc(Result.getLocation()); ++ Result.setAnnotationValue(nullptr); ++ } else { ++ CurLexer->FormTokenWithChars(Result, EndPos, tok::eof); ++ } + + if (isCodeCompletionEnabled()) { + // Inserting the code-completion point increases the source buffer by 1, +diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp +index 3a7f5426d..57a3dfba4 100644 +--- a/clang/lib/Parse/ParseCXXInlineMethods.cpp ++++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp +@@ -836,6 +836,7 @@ bool Parser::ConsumeAndStoreUntil(tok::TokenKind T1, tok::TokenKind T2, + case tok::annot_module_begin: + case tok::annot_module_end: + case tok::annot_module_include: ++ case tok::annot_repl_input_end: + // Ran out of tokens. + return false; + +@@ -1242,6 +1243,7 @@ bool Parser::ConsumeAndStoreInitializer(CachedTokens &Toks, + case tok::annot_module_begin: + case tok::annot_module_end: + case tok::annot_module_include: ++ case tok::annot_repl_input_end: + // Ran out of tokens. + return false; + +diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp +index e6812ac72..2f193a3b4 100644 +--- a/clang/lib/Parse/ParseDecl.cpp ++++ b/clang/lib/Parse/ParseDecl.cpp +@@ -2030,6 +2030,7 @@ void Parser::SkipMalformedDecl() { + case tok::annot_module_begin: + case tok::annot_module_end: + case tok::annot_module_include: ++ case tok::annot_repl_input_end: + return; + + default: +@@ -5394,6 +5395,13 @@ Parser::DeclGroupPtrTy Parser::ParseTopLevelStmtDecl() { + + SmallVector DeclsInGroup; + DeclsInGroup.push_back(Actions.ActOnTopLevelStmtDecl(R.get())); ++ ++ if (Tok.is(tok::annot_repl_input_end) && ++ Tok.getAnnotationValue() != nullptr) { ++ ConsumeAnnotationToken(); ++ cast(DeclsInGroup.back())->setSemiMissing(); ++ } ++ + // Currently happens for things like -fms-extensions and use `__if_exists`. + for (Stmt *S : Stmts) + DeclsInGroup.push_back(Actions.ActOnTopLevelStmtDecl(S)); +diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp +index 1c8441faf..d22e1d440 100644 +--- a/clang/lib/Parse/ParseStmt.cpp ++++ b/clang/lib/Parse/ParseStmt.cpp +@@ -543,9 +543,22 @@ StmtResult Parser::ParseExprStatement(ParsedStmtContext StmtCtx) { + return ParseCaseStatement(StmtCtx, /*MissingCase=*/true, Expr); + } + +- // Otherwise, eat the semicolon. +- ExpectAndConsumeSemi(diag::err_expected_semi_after_expr); +- return handleExprStmt(Expr, StmtCtx); ++ Token *CurTok = nullptr; ++ // If the semicolon is missing at the end of REPL input, consider if ++ // we want to do value printing. Note this is only enabled in C++ mode ++ // since part of the implementation requires C++ language features. ++ // Note we shouldn't eat the token since the callback needs it. ++ if (Tok.is(tok::annot_repl_input_end) && Actions.getLangOpts().CPlusPlus) ++ CurTok = &Tok; ++ else ++ // Otherwise, eat the semicolon. ++ ExpectAndConsumeSemi(diag::err_expected_semi_after_expr); ++ ++ StmtResult R = handleExprStmt(Expr, StmtCtx); ++ if (CurTok && !R.isInvalid()) ++ CurTok->setAnnotationValue(R.get()); ++ ++ return R; + } + + /// ParseSEHTryBlockCommon +diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp +index 6db3dc315..7fbb27057 100644 +--- a/clang/lib/Parse/Parser.cpp ++++ b/clang/lib/Parse/Parser.cpp +@@ -319,6 +319,7 @@ bool Parser::SkipUntil(ArrayRef Toks, SkipUntilFlags Flags) { + case tok::annot_module_begin: + case tok::annot_module_end: + case tok::annot_module_include: ++ case tok::annot_repl_input_end: + // Stop before we change submodules. They generally indicate a "good" + // place to pick up parsing again (except in the special case where + // we're trying to skip to EOF). +@@ -612,11 +613,6 @@ bool Parser::ParseTopLevelDecl(DeclGroupPtrTy &Result, + Sema::ModuleImportState &ImportState) { + DestroyTemplateIdAnnotationsRAIIObj CleanupRAII(*this); + +- // Skip over the EOF token, flagging end of previous input for incremental +- // processing +- if (PP.isIncrementalProcessingEnabled() && Tok.is(tok::eof)) +- ConsumeToken(); +- + Result = nullptr; + switch (Tok.getKind()) { + case tok::annot_pragma_unused: +@@ -695,6 +691,7 @@ bool Parser::ParseTopLevelDecl(DeclGroupPtrTy &Result, + return false; + + case tok::eof: ++ case tok::annot_repl_input_end: + // Check whether -fmax-tokens= was reached. + if (PP.getMaxTokens() != 0 && PP.getTokenCount() > PP.getMaxTokens()) { + PP.Diag(Tok.getLocation(), diag::warn_max_tokens_total) +diff --git a/clang/test/Interpreter/Inputs/dynamic-library-test.cpp b/clang/test/Interpreter/Inputs/dynamic-library-test.cpp +new file mode 100644 +index 000000000..1f143ba04 +--- /dev/null ++++ b/clang/test/Interpreter/Inputs/dynamic-library-test.cpp +@@ -0,0 +1,6 @@ ++int ultimate_answer = 0; ++ ++int calculate_answer() { ++ ultimate_answer = 42; ++ return 5; ++} +diff --git a/clang/test/Interpreter/dynamic-library.cpp b/clang/test/Interpreter/dynamic-library.cpp +new file mode 100644 +index 000000000..794ccccf7 +--- /dev/null ++++ b/clang/test/Interpreter/dynamic-library.cpp +@@ -0,0 +1,19 @@ ++// REQUIRES: host-supports-jit, system-linux ++ ++// RUN: %clang -xc++ -o %T/libdynamic-library-test.so -fPIC -shared -DLIBRARY %S/Inputs/dynamic-library-test.cpp ++// RUN: cat %s | env LD_LIBRARY_PATH=%T:$LD_LIBRARY_PATH clang-repl | FileCheck %s ++ ++#include ++ ++extern int ultimate_answer; ++int calculate_answer(); ++ ++%lib libdynamic-library-test.so ++ ++printf("Return value: %d\n", calculate_answer()); ++// CHECK: Return value: 5 ++ ++printf("Variable: %d\n", ultimate_answer); ++// CHECK-NEXT: Variable: 42 ++ ++%quit +diff --git a/clang/tools/clang-repl/CMakeLists.txt b/clang/tools/clang-repl/CMakeLists.txt +index b51a18c10..15d7f9439 100644 +--- a/clang/tools/clang-repl/CMakeLists.txt ++++ b/clang/tools/clang-repl/CMakeLists.txt +@@ -12,6 +12,7 @@ add_clang_tool(clang-repl + ) + + clang_target_link_libraries(clang-repl PRIVATE ++ clangAST + clangBasic + clangFrontend + clangInterpreter +diff --git a/clang/tools/clang-repl/ClangRepl.cpp b/clang/tools/clang-repl/ClangRepl.cpp +index 401a31d34..33faf3fab 100644 +--- a/clang/tools/clang-repl/ClangRepl.cpp ++++ b/clang/tools/clang-repl/ClangRepl.cpp +@@ -123,6 +123,13 @@ int main(int argc, const char **argv) { + } + continue; + } ++ if (Line->rfind("%lib ", 0) == 0) { ++ if (auto Err = Interp->LoadDynamicLibrary(Line->data() + 5)) { ++ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); ++ HasError = true; ++ } ++ continue; ++ } + + if (auto Err = Interp->ParseAndExecute(*Line)) { + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); +diff --git a/clang/unittests/Interpreter/CMakeLists.txt b/clang/unittests/Interpreter/CMakeLists.txt +index 1a099dbbf..698494b98 100644 +--- a/clang/unittests/Interpreter/CMakeLists.txt ++++ b/clang/unittests/Interpreter/CMakeLists.txt +@@ -22,3 +22,5 @@ target_link_libraries(ClangReplInterpreterTests PUBLIC + if(NOT WIN32) + add_subdirectory(ExceptionTests) + endif() ++ ++export_executable_symbols(ClangReplInterpreterTests) +diff --git a/clang/unittests/Interpreter/ExceptionTests/InterpreterExceptionTest.cpp b/clang/unittests/Interpreter/ExceptionTests/InterpreterExceptionTest.cpp +index f54c65568..6d0433a98 100644 +--- a/clang/unittests/Interpreter/ExceptionTests/InterpreterExceptionTest.cpp ++++ b/clang/unittests/Interpreter/ExceptionTests/InterpreterExceptionTest.cpp +@@ -25,7 +25,6 @@ + #include "llvm/ExecutionEngine/Orc/LLJIT.h" + #include "llvm/Support/ManagedStatic.h" + #include "llvm/Support/TargetSelect.h" +-#include "llvm-c/Error.h" + + #include "gmock/gmock.h" + #include "gtest/gtest.h" +@@ -116,7 +115,8 @@ extern "C" int throw_exception() { + llvm::cantFail(Interp->ParseAndExecute(ExceptionCode)); + testing::internal::CaptureStdout(); + auto ThrowException = +- (int (*)())llvm::cantFail(Interp->getSymbolAddress("throw_exception")); ++ llvm::cantFail(Interp->getSymbolAddress("throw_exception")) ++ .toPtr(); + EXPECT_ANY_THROW(ThrowException()); + std::string CapturedStdOut = testing::internal::GetCapturedStdout(); + EXPECT_EQ(CapturedStdOut, "Caught: 'To be caught in JIT'\n"); +diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp +index d4900a0e4..330fd18ab 100644 +--- a/clang/unittests/Interpreter/InterpreterTest.cpp ++++ b/clang/unittests/Interpreter/InterpreterTest.cpp +@@ -17,6 +17,7 @@ + #include "clang/AST/Mangle.h" + #include "clang/Frontend/CompilerInstance.h" + #include "clang/Frontend/TextDiagnosticPrinter.h" ++#include "clang/Interpreter/Value.h" + #include "clang/Sema/Lookup.h" + #include "clang/Sema/Sema.h" + +@@ -33,6 +34,11 @@ using namespace clang; + #define CLANG_INTERPRETER_NO_SUPPORT_EXEC + #endif + ++int Global = 42; ++// JIT reports symbol not found on Windows without the visibility attribute. ++REPL_EXTERNAL_VISIBILITY int getGlobal() { return Global; } ++REPL_EXTERNAL_VISIBILITY void setGlobal(int val) { Global = val; } ++ + namespace { + using Args = std::vector; + static std::unique_ptr +@@ -225,7 +231,7 @@ TEST(IncrementalProcessing, FindMangledNameSymbol) { + + std::string MangledName = MangleName(FD); + auto Addr = cantFail(Interp->getSymbolAddress(MangledName)); +- EXPECT_NE(0U, Addr); ++ EXPECT_NE(0U, Addr.getValue()); + GlobalDecl GD(FD); + EXPECT_EQ(Addr, cantFail(Interp->getSymbolAddress(GD))); + } +@@ -276,8 +282,7 @@ TEST(IncrementalProcessing, InstantiateTemplate) { + std::vector Args = {"-fno-delayed-template-parsing"}; + std::unique_ptr Interp = createInterpreter(Args); + +- llvm::cantFail(Interp->Parse("void* operator new(__SIZE_TYPE__, void* __p);" +- "extern \"C\" int printf(const char*,...);" ++ llvm::cantFail(Interp->Parse("extern \"C\" int printf(const char*,...);" + "class A {};" + "struct B {" + " template" +@@ -309,9 +314,109 @@ TEST(IncrementalProcessing, InstantiateTemplate) { + + std::string MangledName = MangleName(TmpltSpec); + typedef int (*TemplateSpecFn)(void *); +- auto fn = (TemplateSpecFn)cantFail(Interp->getSymbolAddress(MangledName)); ++ auto fn = ++ cantFail(Interp->getSymbolAddress(MangledName)).toPtr(); + EXPECT_EQ(42, fn(NewA)); + free(NewA); + } + ++#ifdef CLANG_INTERPRETER_NO_SUPPORT_EXEC ++TEST(InterpreterTest, DISABLED_Value) { ++#else ++TEST(InterpreterTest, Value) { ++#endif ++ // We cannot execute on the platform. ++ if (!HostSupportsJit()) ++ return; ++ ++ std::unique_ptr Interp = createInterpreter(); ++ ++ Value V1; ++ llvm::cantFail(Interp->ParseAndExecute("int x = 42;")); ++ llvm::cantFail(Interp->ParseAndExecute("x", &V1)); ++ EXPECT_TRUE(V1.isValid()); ++ EXPECT_TRUE(V1.hasValue()); ++ EXPECT_EQ(V1.getInt(), 42); ++ EXPECT_EQ(V1.convertTo(), 42); ++ EXPECT_TRUE(V1.getType()->isIntegerType()); ++ EXPECT_EQ(V1.getKind(), Value::K_Int); ++ EXPECT_FALSE(V1.isManuallyAlloc()); ++ ++ Value V2; ++ llvm::cantFail(Interp->ParseAndExecute("double y = 3.14;")); ++ llvm::cantFail(Interp->ParseAndExecute("y", &V2)); ++ EXPECT_TRUE(V2.isValid()); ++ EXPECT_TRUE(V2.hasValue()); ++ EXPECT_EQ(V2.getDouble(), 3.14); ++ EXPECT_EQ(V2.convertTo(), 3.14); ++ EXPECT_TRUE(V2.getType()->isFloatingType()); ++ EXPECT_EQ(V2.getKind(), Value::K_Double); ++ EXPECT_FALSE(V2.isManuallyAlloc()); ++ ++ Value V3; ++ llvm::cantFail(Interp->ParseAndExecute( ++ "struct S { int* p; S() { p = new int(42); } ~S() { delete p; }};")); ++ llvm::cantFail(Interp->ParseAndExecute("S{}", &V3)); ++ EXPECT_TRUE(V3.isValid()); ++ EXPECT_TRUE(V3.hasValue()); ++ EXPECT_TRUE(V3.getType()->isRecordType()); ++ EXPECT_EQ(V3.getKind(), Value::K_PtrOrObj); ++ EXPECT_TRUE(V3.isManuallyAlloc()); ++ ++ Value V4; ++ llvm::cantFail(Interp->ParseAndExecute("int getGlobal();")); ++ llvm::cantFail(Interp->ParseAndExecute("void setGlobal(int);")); ++ llvm::cantFail(Interp->ParseAndExecute("getGlobal()", &V4)); ++ EXPECT_EQ(V4.getInt(), 42); ++ EXPECT_TRUE(V4.getType()->isIntegerType()); ++ ++ Value V5; ++ // Change the global from the compiled code. ++ setGlobal(43); ++ llvm::cantFail(Interp->ParseAndExecute("getGlobal()", &V5)); ++ EXPECT_EQ(V5.getInt(), 43); ++ EXPECT_TRUE(V5.getType()->isIntegerType()); ++ ++ // Change the global from the interpreted code. ++ llvm::cantFail(Interp->ParseAndExecute("setGlobal(44);")); ++ EXPECT_EQ(getGlobal(), 44); ++ ++ Value V6; ++ llvm::cantFail(Interp->ParseAndExecute("void foo() {}")); ++ llvm::cantFail(Interp->ParseAndExecute("foo()", &V6)); ++ EXPECT_TRUE(V6.isValid()); ++ EXPECT_FALSE(V6.hasValue()); ++ EXPECT_TRUE(V6.getType()->isVoidType()); ++ EXPECT_EQ(V6.getKind(), Value::K_Void); ++ EXPECT_FALSE(V2.isManuallyAlloc()); ++ ++ Value V7; ++ llvm::cantFail(Interp->ParseAndExecute("foo", &V7)); ++ EXPECT_TRUE(V7.isValid()); ++ EXPECT_TRUE(V7.hasValue()); ++ EXPECT_TRUE(V7.getType()->isFunctionProtoType()); ++ EXPECT_EQ(V7.getKind(), Value::K_PtrOrObj); ++ EXPECT_FALSE(V7.isManuallyAlloc()); ++ ++ Value V8; ++ llvm::cantFail(Interp->ParseAndExecute("struct SS{ void f() {} };")); ++ llvm::cantFail(Interp->ParseAndExecute("&SS::f", &V8)); ++ EXPECT_TRUE(V8.isValid()); ++ EXPECT_TRUE(V8.hasValue()); ++ EXPECT_TRUE(V8.getType()->isMemberFunctionPointerType()); ++ EXPECT_EQ(V8.getKind(), Value::K_PtrOrObj); ++ EXPECT_TRUE(V8.isManuallyAlloc()); ++ ++ Value V9; ++ llvm::cantFail(Interp->ParseAndExecute("struct A { virtual int f(); };")); ++ llvm::cantFail( ++ Interp->ParseAndExecute("struct B : A { int f() { return 42; }};")); ++ llvm::cantFail(Interp->ParseAndExecute("int (B::*ptr)() = &B::f;")); ++ llvm::cantFail(Interp->ParseAndExecute("ptr", &V9)); ++ EXPECT_TRUE(V9.isValid()); ++ EXPECT_TRUE(V9.hasValue()); ++ EXPECT_TRUE(V9.getType()->isMemberFunctionPointerType()); ++ EXPECT_EQ(V9.getKind(), Value::K_PtrOrObj); ++ EXPECT_TRUE(V9.isManuallyAlloc()); ++} + } // end anonymous namespace diff --git a/interpreter/CppInterOp/patches/llvm/clang16-2-CUDA.patch b/interpreter/CppInterOp/patches/llvm/clang16-2-CUDA.patch new file mode 100644 index 0000000000000..ffaed5b974d7d --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/clang16-2-CUDA.patch @@ -0,0 +1,969 @@ +diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h +index e68021845..43573fb1a 100644 +--- a/clang/include/clang/Interpreter/Interpreter.h ++++ b/clang/include/clang/Interpreter/Interpreter.h +@@ -42,8 +42,34 @@ class IncrementalParser; + /// Create a pre-configured \c CompilerInstance for incremental processing. + class IncrementalCompilerBuilder { + public: ++ IncrementalCompilerBuilder() {} ++ ++ void SetCompilerArgs(const std::vector &Args) { ++ UserArgs = Args; ++ } ++ ++ // General C++ ++ llvm::Expected> CreateCpp(); ++ ++ // Offload options ++ void SetOffloadArch(llvm::StringRef Arch) { OffloadArch = Arch; }; ++ ++ // CUDA specific ++ void SetCudaSDK(llvm::StringRef path) { CudaSDKPath = path; }; ++ ++ llvm::Expected> CreateCudaHost(); ++ llvm::Expected> CreateCudaDevice(); ++ ++private: + static llvm::Expected> + create(std::vector &ClangArgv); ++ ++ llvm::Expected> createCuda(bool device); ++ ++ std::vector UserArgs; ++ ++ llvm::StringRef OffloadArch; ++ llvm::StringRef CudaSDKPath; + }; + + /// Provides top-level interfaces for incremental compilation and execution. +@@ -52,6 +78,9 @@ class Interpreter { + std::unique_ptr IncrParser; + std::unique_ptr IncrExecutor; + ++ // An optional parser for CUDA offloading ++ std::unique_ptr DeviceParser; ++ + Interpreter(std::unique_ptr CI, llvm::Error &Err); + + llvm::Error CreateExecutor(); +@@ -66,6 +95,9 @@ public: + ~Interpreter(); + static llvm::Expected> + create(std::unique_ptr CI); ++ static llvm::Expected> ++ createWithCUDA(std::unique_ptr CI, ++ std::unique_ptr DCI); + const ASTContext &getASTContext() const; + ASTContext &getASTContext(); + const CompilerInstance *getCompilerInstance() const; +diff --git a/clang/lib/CodeGen/CGCUDANV.cpp b/clang/lib/CodeGen/CGCUDANV.cpp +index bb887df3e..9cb8ae33b 100644 +--- a/clang/lib/CodeGen/CGCUDANV.cpp ++++ b/clang/lib/CodeGen/CGCUDANV.cpp +@@ -24,6 +24,7 @@ + #include "llvm/IR/DerivedTypes.h" + #include "llvm/IR/ReplaceConstant.h" + #include "llvm/Support/Format.h" ++#include "llvm/Support/VirtualFileSystem.h" + + using namespace clang; + using namespace CodeGen; +@@ -721,8 +722,9 @@ llvm::Function *CGNVCUDARuntime::makeModuleCtorFunction() { + // handle so CUDA runtime can figure out what to call on the GPU side. + std::unique_ptr CudaGpuBinary = nullptr; + if (!CudaGpuBinaryFileName.empty()) { +- llvm::ErrorOr> CudaGpuBinaryOrErr = +- llvm::MemoryBuffer::getFileOrSTDIN(CudaGpuBinaryFileName); ++ auto VFS = CGM.getFileSystem(); ++ auto CudaGpuBinaryOrErr = ++ VFS->getBufferForFile(CudaGpuBinaryFileName, -1, false); + if (std::error_code EC = CudaGpuBinaryOrErr.getError()) { + CGM.getDiags().Report(diag::err_cannot_open_file) + << CudaGpuBinaryFileName << EC.message(); +diff --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp +index 2b2192678..8ea66845e 100644 +--- a/clang/lib/CodeGen/CodeGenAction.cpp ++++ b/clang/lib/CodeGen/CodeGenAction.cpp +@@ -263,6 +263,7 @@ namespace clang { + // Links each entry in LinkModules into our module. Returns true on error. + bool LinkInModules() { + for (auto &LM : LinkModules) { ++ assert(LM.Module && "LinkModule does not actually have a module"); + if (LM.PropagateAttrs) + for (Function &F : *LM.Module) { + // Skip intrinsics. Keep consistent with how intrinsics are created +@@ -291,6 +292,7 @@ namespace clang { + if (Err) + return true; + } ++ LinkModules.clear(); + return false; // success + } + +diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp +index 12d602fed..978e4d404 100644 +--- a/clang/lib/CodeGen/CodeGenModule.cpp ++++ b/clang/lib/CodeGen/CodeGenModule.cpp +@@ -6228,6 +6228,10 @@ void CodeGenModule::EmitLinkageSpec(const LinkageSpecDecl *LSD) { + } + + void CodeGenModule::EmitTopLevelStmt(const TopLevelStmtDecl *D) { ++ // Device code should not be at top level. ++ if (LangOpts.CUDA && LangOpts.CUDAIsDevice) ++ return; ++ + std::unique_ptr &CurCGF = + GlobalTopLevelStmtBlockInFlight.first; + +diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp b/clang/lib/CodeGen/ModuleBuilder.cpp +index e3e953c34..3594f4c66 100644 +--- a/clang/lib/CodeGen/ModuleBuilder.cpp ++++ b/clang/lib/CodeGen/ModuleBuilder.cpp +@@ -36,7 +36,7 @@ namespace { + IntrusiveRefCntPtr FS; // Only used for debug info. + const HeaderSearchOptions &HeaderSearchOpts; // Only used for debug info. + const PreprocessorOptions &PreprocessorOpts; // Only used for debug info. +- const CodeGenOptions CodeGenOpts; // Intentionally copied in. ++ const CodeGenOptions &CodeGenOpts; + + unsigned HandlingTopLevelDecls; + +diff --git a/clang/lib/Interpreter/CMakeLists.txt b/clang/lib/Interpreter/CMakeLists.txt +index 565e824bf..32f1b7c37 100644 +--- a/clang/lib/Interpreter/CMakeLists.txt ++++ b/clang/lib/Interpreter/CMakeLists.txt +@@ -1,6 +1,7 @@ + set(LLVM_LINK_COMPONENTS + core + native ++ MC + Option + OrcJit + Support +@@ -9,6 +10,7 @@ set(LLVM_LINK_COMPONENTS + ) + + add_clang_library(clangInterpreter ++ DeviceOffload.cpp + IncrementalExecutor.cpp + IncrementalParser.cpp + Interpreter.cpp +diff --git a/clang/lib/Interpreter/DeviceOffload.cpp b/clang/lib/Interpreter/DeviceOffload.cpp +new file mode 100644 +index 000000000..8e39af6ab +--- /dev/null ++++ b/clang/lib/Interpreter/DeviceOffload.cpp +@@ -0,0 +1,176 @@ ++//===---------- DeviceOffload.cpp - Device Offloading------------*- C++ -*-===// ++// ++// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. ++// See https://llvm.org/LICENSE.txt for license information. ++// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ++// ++//===----------------------------------------------------------------------===// ++// ++// This file implements offloading to CUDA devices. ++// ++//===----------------------------------------------------------------------===// ++ ++#include "DeviceOffload.h" ++ ++#include "clang/Basic/TargetOptions.h" ++#include "clang/CodeGen/ModuleBuilder.h" ++#include "clang/Frontend/CompilerInstance.h" ++ ++#include "llvm/IR/LegacyPassManager.h" ++#include "llvm/MC/TargetRegistry.h" ++#include "llvm/Target/TargetMachine.h" ++ ++namespace clang { ++ ++IncrementalCUDADeviceParser::IncrementalCUDADeviceParser( ++ Interpreter &Interp, std::unique_ptr Instance, ++ IncrementalParser &HostParser, llvm::LLVMContext &LLVMCtx, ++ llvm::IntrusiveRefCntPtr FS, ++ llvm::Error &Err) ++ : IncrementalParser(Interp, std::move(Instance), LLVMCtx, Err), ++ HostParser(HostParser), VFS(FS) { ++ if (Err) ++ return; ++ StringRef Arch = CI->getTargetOpts().CPU; ++ if (!Arch.starts_with("sm_") || Arch.substr(3).getAsInteger(10, SMVersion)) { ++ Err = llvm::joinErrors(std::move(Err), llvm::make_error( ++ "Invalid CUDA architecture", ++ llvm::inconvertibleErrorCode())); ++ return; ++ } ++} ++ ++llvm::Expected ++IncrementalCUDADeviceParser::Parse(llvm::StringRef Input) { ++ auto PTU = IncrementalParser::Parse(Input); ++ if (!PTU) ++ return PTU.takeError(); ++ ++ auto PTX = GeneratePTX(); ++ if (!PTX) ++ return PTX.takeError(); ++ ++ auto Err = GenerateFatbinary(); ++ if (Err) ++ return std::move(Err); ++ ++ std::string FatbinFileName = ++ "/incr_module_" + std::to_string(PTUs.size()) + ".fatbin"; ++ VFS->addFile(FatbinFileName, 0, ++ llvm::MemoryBuffer::getMemBuffer( ++ llvm::StringRef(FatbinContent.data(), FatbinContent.size()), ++ "", false)); ++ ++ HostParser.getCI()->getCodeGenOpts().CudaGpuBinaryFileName = FatbinFileName; ++ ++ FatbinContent.clear(); ++ ++ return PTU; ++} ++ ++llvm::Expected IncrementalCUDADeviceParser::GeneratePTX() { ++ auto &PTU = PTUs.back(); ++ std::string Error; ++ ++ const llvm::Target *Target = llvm::TargetRegistry::lookupTarget( ++ PTU.TheModule->getTargetTriple(), Error); ++ if (!Target) ++ return llvm::make_error(std::move(Error), ++ std::error_code()); ++ llvm::TargetOptions TO = llvm::TargetOptions(); ++ llvm::TargetMachine *TargetMachine = Target->createTargetMachine( ++ PTU.TheModule->getTargetTriple(), getCI()->getTargetOpts().CPU, "", TO, ++ llvm::Reloc::Model::PIC_); ++ PTU.TheModule->setDataLayout(TargetMachine->createDataLayout()); ++ ++ PTXCode.clear(); ++ llvm::raw_svector_ostream dest(PTXCode); ++ ++ llvm::legacy::PassManager PM; ++ if (TargetMachine->addPassesToEmitFile(PM, dest, nullptr, ++ llvm::CGFT_AssemblyFile)) { ++ return llvm::make_error( ++ "NVPTX backend cannot produce PTX code.", ++ llvm::inconvertibleErrorCode()); ++ } ++ ++ if (!PM.run(*PTU.TheModule)) ++ return llvm::make_error("Failed to emit PTX code.", ++ llvm::inconvertibleErrorCode()); ++ ++ PTXCode += '\0'; ++ while (PTXCode.size() % 8) ++ PTXCode += '\0'; ++ return PTXCode.str(); ++} ++ ++llvm::Error IncrementalCUDADeviceParser::GenerateFatbinary() { ++ enum FatBinFlags { ++ AddressSize64 = 0x01, ++ HasDebugInfo = 0x02, ++ ProducerCuda = 0x04, ++ HostLinux = 0x10, ++ HostMac = 0x20, ++ HostWindows = 0x40 ++ }; ++ ++ struct FatBinInnerHeader { ++ uint16_t Kind; // 0x00 ++ uint16_t unknown02; // 0x02 ++ uint32_t HeaderSize; // 0x04 ++ uint32_t DataSize; // 0x08 ++ uint32_t unknown0c; // 0x0c ++ uint32_t CompressedSize; // 0x10 ++ uint32_t SubHeaderSize; // 0x14 ++ uint16_t VersionMinor; // 0x18 ++ uint16_t VersionMajor; // 0x1a ++ uint32_t CudaArch; // 0x1c ++ uint32_t unknown20; // 0x20 ++ uint32_t unknown24; // 0x24 ++ uint32_t Flags; // 0x28 ++ uint32_t unknown2c; // 0x2c ++ uint32_t unknown30; // 0x30 ++ uint32_t unknown34; // 0x34 ++ uint32_t UncompressedSize; // 0x38 ++ uint32_t unknown3c; // 0x3c ++ uint32_t unknown40; // 0x40 ++ uint32_t unknown44; // 0x44 ++ FatBinInnerHeader(uint32_t DataSize, uint32_t CudaArch, uint32_t Flags) ++ : Kind(1 /*PTX*/), unknown02(0x0101), HeaderSize(sizeof(*this)), ++ DataSize(DataSize), unknown0c(0), CompressedSize(0), ++ SubHeaderSize(HeaderSize - 8), VersionMinor(2), VersionMajor(4), ++ CudaArch(CudaArch), unknown20(0), unknown24(0), Flags(Flags), ++ unknown2c(0), unknown30(0), unknown34(0), UncompressedSize(0), ++ unknown3c(0), unknown40(0), unknown44(0) {} ++ }; ++ ++ struct FatBinHeader { ++ uint32_t Magic; // 0x00 ++ uint16_t Version; // 0x04 ++ uint16_t HeaderSize; // 0x06 ++ uint32_t DataSize; // 0x08 ++ uint32_t unknown0c; // 0x0c ++ public: ++ FatBinHeader(uint32_t DataSize) ++ : Magic(0xba55ed50), Version(1), HeaderSize(sizeof(*this)), ++ DataSize(DataSize), unknown0c(0) {} ++ }; ++ ++ FatBinHeader OuterHeader(sizeof(FatBinInnerHeader) + PTXCode.size()); ++ FatbinContent.append((char *)&OuterHeader, ++ ((char *)&OuterHeader) + OuterHeader.HeaderSize); ++ ++ FatBinInnerHeader InnerHeader(PTXCode.size(), SMVersion, ++ FatBinFlags::AddressSize64 | ++ FatBinFlags::HostLinux); ++ FatbinContent.append((char *)&InnerHeader, ++ ((char *)&InnerHeader) + InnerHeader.HeaderSize); ++ ++ FatbinContent.append(PTXCode.begin(), PTXCode.end()); ++ ++ return llvm::Error::success(); ++} ++ ++IncrementalCUDADeviceParser::~IncrementalCUDADeviceParser() {} ++ ++} // namespace clang +diff --git a/clang/lib/Interpreter/DeviceOffload.h b/clang/lib/Interpreter/DeviceOffload.h +new file mode 100644 +index 000000000..ce4f218c9 +--- /dev/null ++++ b/clang/lib/Interpreter/DeviceOffload.h +@@ -0,0 +1,51 @@ ++//===----------- DeviceOffload.h - Device Offloading ------------*- C++ -*-===// ++// ++// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. ++// See https://llvm.org/LICENSE.txt for license information. ++// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception ++// ++//===----------------------------------------------------------------------===// ++// ++// This file implements classes required for offloading to CUDA devices. ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLVM_CLANG_LIB_INTERPRETER_DEVICE_OFFLOAD_H ++#define LLVM_CLANG_LIB_INTERPRETER_DEVICE_OFFLOAD_H ++ ++#include "IncrementalParser.h" ++#include "llvm/Support/FileSystem.h" ++#include "llvm/Support/VirtualFileSystem.h" ++ ++namespace clang { ++ ++class IncrementalCUDADeviceParser : public IncrementalParser { ++public: ++ IncrementalCUDADeviceParser( ++ Interpreter &Interp, std::unique_ptr Instance, ++ IncrementalParser &HostParser, llvm::LLVMContext &LLVMCtx, ++ llvm::IntrusiveRefCntPtr VFS, ++ llvm::Error &Err); ++ ++ llvm::Expected ++ Parse(llvm::StringRef Input) override; ++ ++ // Generate PTX for the last PTU ++ llvm::Expected GeneratePTX(); ++ ++ // Generate fatbinary contents in memory ++ llvm::Error GenerateFatbinary(); ++ ++ ~IncrementalCUDADeviceParser(); ++ ++protected: ++ IncrementalParser &HostParser; ++ int SMVersion; ++ llvm::SmallString<1024> PTXCode; ++ llvm::SmallVector FatbinContent; ++ llvm::IntrusiveRefCntPtr VFS; ++}; ++ ++} // namespace clang ++ ++#endif // LLVM_CLANG_LIB_INTERPRETER_DEVICE_OFFLOAD_H +diff --git a/clang/lib/Interpreter/IncrementalParser.cpp b/clang/lib/Interpreter/IncrementalParser.cpp +index f892eeb81..a3ae1aa8a 100644 +--- a/clang/lib/Interpreter/IncrementalParser.cpp ++++ b/clang/lib/Interpreter/IncrementalParser.cpp +@@ -194,6 +194,15 @@ public: + } + }; + ++CodeGenerator *IncrementalParser::getCodeGen() const { ++ FrontendAction *WrappedAct = Act->getWrapped(); ++ if (!WrappedAct->hasIRSupport()) ++ return nullptr; ++ return static_cast(WrappedAct)->getCodeGenerator(); ++} ++ ++IncrementalParser::IncrementalParser() {} ++ + IncrementalParser::IncrementalParser(Interpreter &Interp, + std::unique_ptr Instance, + llvm::LLVMContext &LLVMCtx, +@@ -211,6 +220,21 @@ IncrementalParser::IncrementalParser(Interpreter &Interp, + P.reset( + new Parser(CI->getPreprocessor(), CI->getSema(), /*SkipBodies=*/false)); + P->Initialize(); ++ ++ // An initial PTU is needed as CUDA includes some headers automatically ++ auto PTU = ParseOrWrapTopLevelDecl(); ++ if (auto E = PTU.takeError()) { ++ consumeError(std::move(E)); // FIXME ++ return; // PTU.takeError(); ++ } ++ ++ if (CodeGenerator *CG = getCodeGen()) { ++ std::unique_ptr M(CG->ReleaseModule()); ++ CG->StartModule("incr_module_" + std::to_string(PTUs.size()), ++ M->getContext()); ++ PTU->TheModule = std::move(M); ++ assert(PTU->TheModule && "Failed to create initial PTU"); ++ } + } + + IncrementalParser::~IncrementalParser() { +@@ -281,14 +305,6 @@ IncrementalParser::ParseOrWrapTopLevelDecl() { + return LastPTU; + } + +-static CodeGenerator *getCodeGen(FrontendAction *Act) { +- IncrementalAction *IncrAct = static_cast(Act); +- FrontendAction *WrappedAct = IncrAct->getWrapped(); +- if (!WrappedAct->hasIRSupport()) +- return nullptr; +- return static_cast(WrappedAct)->getCodeGenerator(); +-} +- + llvm::Expected + IncrementalParser::Parse(llvm::StringRef input) { + Preprocessor &PP = CI->getPreprocessor(); +@@ -351,7 +367,7 @@ IncrementalParser::Parse(llvm::StringRef input) { + + std::unique_ptr IncrementalParser::GenModule() { + static unsigned ID = 0; +- if (CodeGenerator *CG = getCodeGen(Act.get())) { ++ if (CodeGenerator *CG = getCodeGen()) { + std::unique_ptr M(CG->ReleaseModule()); + CG->StartModule("incr_module_" + std::to_string(ID++), M->getContext()); + return M; +@@ -378,7 +394,7 @@ void IncrementalParser::CleanUpPTU(PartialTranslationUnit &PTU) { + } + + llvm::StringRef IncrementalParser::GetMangledName(GlobalDecl GD) const { +- CodeGenerator *CG = getCodeGen(Act.get()); ++ CodeGenerator *CG = getCodeGen(); + assert(CG); + return CG->GetMangledName(GD); + } +diff --git a/clang/lib/Interpreter/IncrementalParser.h b/clang/lib/Interpreter/IncrementalParser.h +index 99e37588d..def5750d1 100644 +--- a/clang/lib/Interpreter/IncrementalParser.h ++++ b/clang/lib/Interpreter/IncrementalParser.h +@@ -28,6 +28,7 @@ class LLVMContext; + + namespace clang { + class ASTConsumer; ++class CodeGenerator; + class CompilerInstance; + class IncrementalAction; + class Interpreter; +@@ -36,6 +37,7 @@ class Parser; + /// changes between the subsequent incremental input. + /// + class IncrementalParser { ++protected: + /// Long-lived, incremental parsing action. + std::unique_ptr Act; + +@@ -55,18 +57,21 @@ class IncrementalParser { + /// of code. + std::list PTUs; + ++ IncrementalParser(); ++ + public: + IncrementalParser(Interpreter &Interp, + std::unique_ptr Instance, + llvm::LLVMContext &LLVMCtx, llvm::Error &Err); +- ~IncrementalParser(); ++ virtual ~IncrementalParser(); + +- const CompilerInstance *getCI() const { return CI.get(); } ++ CompilerInstance *getCI() { return CI.get(); } ++ CodeGenerator *getCodeGen() const; + + /// Parses incremental input by creating an in-memory file. + ///\returns a \c PartialTranslationUnit which holds information about the + /// \c TranslationUnitDecl and \c llvm::Module corresponding to the input. +- llvm::Expected Parse(llvm::StringRef Input); ++ virtual llvm::Expected Parse(llvm::StringRef Input); + + /// Uses the CodeGenModule mangled name cache and avoids recomputing. + ///\returns the mangled name of a \c GD. +diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp +index 4391bd008..74d428a86 100644 +--- a/clang/lib/Interpreter/Interpreter.cpp ++++ b/clang/lib/Interpreter/Interpreter.cpp +@@ -13,6 +13,7 @@ + + #include "clang/Interpreter/Interpreter.h" + ++#include "DeviceOffload.h" + #include "IncrementalExecutor.h" + #include "IncrementalParser.h" + +@@ -22,6 +23,7 @@ + #include "clang/AST/TypeVisitor.h" + #include "clang/Basic/DiagnosticSema.h" + #include "clang/Basic/TargetInfo.h" ++#include "clang/CodeGen/CodeGenAction.h" + #include "clang/CodeGen/ModuleBuilder.h" + #include "clang/CodeGen/ObjectFilePCHContainerOperations.h" + #include "clang/Driver/Compilation.h" +@@ -146,7 +148,6 @@ IncrementalCompilerBuilder::create(std::vector &ClangArgv) { + // action and use other actions in incremental mode. + // FIXME: Print proper driver diagnostics if the driver flags are wrong. + // We do C++ by default; append right after argv[0] if no "-x" given +- ClangArgv.insert(ClangArgv.end(), "-xc++"); + ClangArgv.insert(ClangArgv.end(), "-Xclang"); + ClangArgv.insert(ClangArgv.end(), "-fincremental-extensions"); + ClangArgv.insert(ClangArgv.end(), "-c"); +@@ -179,6 +180,54 @@ IncrementalCompilerBuilder::create(std::vector &ClangArgv) { + return CreateCI(**ErrOrCC1Args); + } + ++llvm::Expected> ++IncrementalCompilerBuilder::CreateCpp() { ++ std::vector Argv; ++ Argv.reserve(5 + 1 + UserArgs.size()); ++ Argv.push_back("-xc++"); ++ Argv.insert(Argv.end(), UserArgs.begin(), UserArgs.end()); ++ ++ return IncrementalCompilerBuilder::create(Argv); ++} ++ ++llvm::Expected> ++IncrementalCompilerBuilder::createCuda(bool device) { ++ std::vector Argv; ++ Argv.reserve(5 + 4 + UserArgs.size()); ++ ++ Argv.push_back("-xcuda"); ++ if (device) ++ Argv.push_back("--cuda-device-only"); ++ else ++ Argv.push_back("--cuda-host-only"); ++ ++ std::string SDKPathArg = "--cuda-path="; ++ if (!CudaSDKPath.empty()) { ++ SDKPathArg += CudaSDKPath; ++ Argv.push_back(SDKPathArg.c_str()); ++ } ++ ++ std::string ArchArg = "--offload-arch="; ++ if (!OffloadArch.empty()) { ++ ArchArg += OffloadArch; ++ Argv.push_back(ArchArg.c_str()); ++ } ++ ++ Argv.insert(Argv.end(), UserArgs.begin(), UserArgs.end()); ++ ++ return IncrementalCompilerBuilder::create(Argv); ++} ++ ++llvm::Expected> ++IncrementalCompilerBuilder::CreateCudaDevice() { ++ return IncrementalCompilerBuilder::createCuda(true); ++} ++ ++llvm::Expected> ++IncrementalCompilerBuilder::CreateCudaHost() { ++ return IncrementalCompilerBuilder::createCuda(false); ++} ++ + Interpreter::Interpreter(std::unique_ptr CI, + llvm::Error &Err) { + llvm::ErrorAsOutParameter EAO(&Err); +@@ -239,6 +288,34 @@ Interpreter::create(std::unique_ptr CI) { + return std::move(Interp); + } + ++llvm::Expected> ++Interpreter::createWithCUDA(std::unique_ptr CI, ++ std::unique_ptr DCI) { ++ // avoid writing fat binary to disk using an in-memory virtual file system ++ llvm::IntrusiveRefCntPtr IMVFS = ++ std::make_unique(); ++ llvm::IntrusiveRefCntPtr OverlayVFS = ++ std::make_unique( ++ llvm::vfs::getRealFileSystem()); ++ OverlayVFS->pushOverlay(IMVFS); ++ CI->createFileManager(OverlayVFS); ++ ++ auto Interp = Interpreter::create(std::move(CI)); ++ if (auto E = Interp.takeError()) ++ return std::move(E); ++ ++ llvm::Error Err = llvm::Error::success(); ++ auto DeviceParser = std::make_unique( ++ **Interp, std::move(DCI), *(*Interp)->IncrParser.get(), ++ *(*Interp)->TSCtx->getContext(), IMVFS, Err); ++ if (Err) ++ return std::move(Err); ++ ++ (*Interp)->DeviceParser = std::move(DeviceParser); ++ ++ return Interp; ++} ++ + const CompilerInstance *Interpreter::getCompilerInstance() const { + return IncrParser->getCI(); + } +@@ -268,6 +345,14 @@ size_t Interpreter::getEffectivePTUSize() const { + + llvm::Expected + Interpreter::Parse(llvm::StringRef Code) { ++ // If we have a device parser, parse it first. ++ // The generated code will be included in the host compilation ++ if (DeviceParser) { ++ auto DevicePTU = DeviceParser->Parse(Code); ++ if (auto E = DevicePTU.takeError()) ++ return std::move(E); ++ } ++ + // Tell the interpreter sliently ignore unused expressions since value + // printing could cause it. + getCompilerInstance()->getDiagnostics().setSeverity( +diff --git a/clang/test/Interpreter/CUDA/device-function-template.cu b/clang/test/Interpreter/CUDA/device-function-template.cu +new file mode 100644 +index 000000000..f0077a2c5 +--- /dev/null ++++ b/clang/test/Interpreter/CUDA/device-function-template.cu +@@ -0,0 +1,24 @@ ++// Tests device function templates ++// RUN: cat %s | clang-repl --cuda | FileCheck %s ++ ++extern "C" int printf(const char*, ...); ++ ++template __device__ inline T sum(T a, T b) { return a + b; } ++__global__ void test_kernel(int* value) { *value = sum(40, 2); } ++ ++int var; ++int* devptr = nullptr; ++printf("cudaMalloc: %d\n", cudaMalloc((void **) &devptr, sizeof(int))); ++// CHECK: cudaMalloc: 0 ++ ++test_kernel<<<1,1>>>(devptr); ++printf("CUDA Error: %d\n", cudaGetLastError()); ++// CHECK-NEXT: CUDA Error: 0 ++ ++printf("cudaMemcpy: %d\n", cudaMemcpy(&var, devptr, sizeof(int), cudaMemcpyDeviceToHost)); ++// CHECK-NEXT: cudaMemcpy: 0 ++ ++printf("Value: %d\n", var); ++// CHECK-NEXT: Value: 42 ++ ++%quit +diff --git a/clang/test/Interpreter/CUDA/device-function.cu b/clang/test/Interpreter/CUDA/device-function.cu +new file mode 100644 +index 000000000..396f8f0f9 +--- /dev/null ++++ b/clang/test/Interpreter/CUDA/device-function.cu +@@ -0,0 +1,24 @@ ++// Tests __device__ function calls ++// RUN: cat %s | clang-repl --cuda | FileCheck %s ++ ++extern "C" int printf(const char*, ...); ++ ++__device__ inline void test_device(int* value) { *value = 42; } ++__global__ void test_kernel(int* value) { test_device(value); } ++ ++int var; ++int* devptr = nullptr; ++printf("cudaMalloc: %d\n", cudaMalloc((void **) &devptr, sizeof(int))); ++// CHECK: cudaMalloc: 0 ++ ++test_kernel<<<1,1>>>(devptr); ++printf("CUDA Error: %d\n", cudaGetLastError()); ++// CHECK-NEXT: CUDA Error: 0 ++ ++printf("cudaMemcpy: %d\n", cudaMemcpy(&var, devptr, sizeof(int), cudaMemcpyDeviceToHost)); ++// CHECK-NEXT: cudaMemcpy: 0 ++ ++printf("Value: %d\n", var); ++// CHECK-NEXT: Value: 42 ++ ++%quit +diff --git a/clang/test/Interpreter/CUDA/host-and-device.cu b/clang/test/Interpreter/CUDA/host-and-device.cu +new file mode 100644 +index 000000000..8e44e3403 +--- /dev/null ++++ b/clang/test/Interpreter/CUDA/host-and-device.cu +@@ -0,0 +1,27 @@ ++// Checks that a function is available in both __host__ and __device__ ++// RUN: cat %s | clang-repl --cuda | FileCheck %s ++ ++extern "C" int printf(const char*, ...); ++ ++__host__ __device__ inline int sum(int a, int b){ return a + b; } ++__global__ void kernel(int * output){ *output = sum(40,2); } ++ ++printf("Host sum: %d\n", sum(41,1)); ++// CHECK: Host sum: 42 ++ ++int var = 0; ++int * deviceVar; ++printf("cudaMalloc: %d\n", cudaMalloc((void **) &deviceVar, sizeof(int))); ++// CHECK-NEXT: cudaMalloc: 0 ++ ++kernel<<<1,1>>>(deviceVar); ++printf("CUDA Error: %d\n", cudaGetLastError()); ++// CHECK-NEXT: CUDA Error: 0 ++ ++printf("cudaMemcpy: %d\n", cudaMemcpy(&var, deviceVar, sizeof(int), cudaMemcpyDeviceToHost)); ++// CHECK-NEXT: cudaMemcpy: 0 ++ ++printf("var: %d\n", var); ++// CHECK-NEXT: var: 42 ++ ++%quit +diff --git a/clang/test/Interpreter/CUDA/lit.local.cfg b/clang/test/Interpreter/CUDA/lit.local.cfg +new file mode 100644 +index 000000000..999157246 +--- /dev/null ++++ b/clang/test/Interpreter/CUDA/lit.local.cfg +@@ -0,0 +1,2 @@ ++if 'host-supports-cuda' not in config.available_features: ++ config.unsupported = True +diff --git a/clang/test/Interpreter/CUDA/memory.cu b/clang/test/Interpreter/CUDA/memory.cu +new file mode 100644 +index 000000000..852cc04f6 +--- /dev/null ++++ b/clang/test/Interpreter/CUDA/memory.cu +@@ -0,0 +1,23 @@ ++// Tests cudaMemcpy and writes from kernel ++// RUN: cat %s | clang-repl --cuda | FileCheck %s ++ ++extern "C" int printf(const char*, ...); ++ ++__global__ void test_func(int* value) { *value = 42; } ++ ++int var; ++int* devptr = nullptr; ++printf("cudaMalloc: %d\n", cudaMalloc((void **) &devptr, sizeof(int))); ++// CHECK: cudaMalloc: 0 ++ ++test_func<<<1,1>>>(devptr); ++printf("CUDA Error: %d\n", cudaGetLastError()); ++// CHECK-NEXT: CUDA Error: 0 ++ ++printf("cudaMemcpy: %d\n", cudaMemcpy(&var, devptr, sizeof(int), cudaMemcpyDeviceToHost)); ++// CHECK-NEXT: cudaMemcpy: 0 ++ ++printf("Value: %d\n", var); ++// CHECK-NEXT: Value: 42 ++ ++%quit +diff --git a/clang/test/Interpreter/CUDA/sanity.cu b/clang/test/Interpreter/CUDA/sanity.cu +new file mode 100644 +index 000000000..ef9d68df4 +--- /dev/null ++++ b/clang/test/Interpreter/CUDA/sanity.cu +@@ -0,0 +1,11 @@ ++// RUN: cat %s | clang-repl --cuda | FileCheck %s ++ ++extern "C" int printf(const char*, ...); ++ ++__global__ void test_func() {} ++ ++test_func<<<1,1>>>(); ++printf("CUDA Error: %d", cudaGetLastError()); ++// CHECK: CUDA Error: 0 ++ ++%quit +diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py +index cc55c3c44..fe237fadc 100644 +--- a/clang/test/lit.cfg.py ++++ b/clang/test/lit.cfg.py +@@ -86,9 +86,39 @@ def have_host_jit_feature_support(feature_name): + + return 'true' in clang_repl_out + ++def have_host_clang_repl_cuda(): ++ clang_repl_exe = lit.util.which('clang-repl', config.clang_tools_dir) ++ ++ if not clang_repl_exe: ++ return False ++ ++ testcode = b'\n'.join([ ++ b"__global__ void test_func() {}", ++ b"test_func<<<1,1>>>();", ++ b"extern \"C\" int puts(const char *s);", ++ b"puts(cudaGetLastError() ? \"failure\" : \"success\");", ++ b"%quit" ++ ]) ++ try: ++ clang_repl_cmd = subprocess.run([clang_repl_exe, '--cuda'], ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, ++ input=testcode) ++ except OSError: ++ return False ++ ++ if clang_repl_cmd.returncode == 0: ++ if clang_repl_cmd.stdout.find(b"success") != -1: ++ return True ++ ++ return False ++ + if have_host_jit_feature_support('jit'): + config.available_features.add('host-supports-jit') + ++ if have_host_clang_repl_cuda(): ++ config.available_features.add('host-supports-cuda') ++ + if config.clang_staticanalyzer: + config.available_features.add('staticanalyzer') + tools.append('clang-check') +diff --git a/clang/tools/clang-repl/ClangRepl.cpp b/clang/tools/clang-repl/ClangRepl.cpp +index 33faf3fab..19733e193 100644 +--- a/clang/tools/clang-repl/ClangRepl.cpp ++++ b/clang/tools/clang-repl/ClangRepl.cpp +@@ -20,9 +20,13 @@ + #include "llvm/Support/CommandLine.h" + #include "llvm/Support/ManagedStatic.h" // llvm_shutdown + #include "llvm/Support/Signals.h" +-#include "llvm/Support/TargetSelect.h" // llvm::Initialize* ++#include "llvm/Support/TargetSelect.h" + #include + ++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::list + ClangArgs("Xcc", + llvm::cl::desc("Argument to pass to the CompilerInvocation"), +@@ -76,8 +80,11 @@ int main(int argc, const char **argv) { + std::vector ClangArgv(ClangArgs.size()); + std::transform(ClangArgs.begin(), ClangArgs.end(), ClangArgv.begin(), + [](const std::string &s) -> const char * { return s.data(); }); +- llvm::InitializeNativeTarget(); +- llvm::InitializeNativeTargetAsmPrinter(); ++ // Initialize all targets (required for device offloading) ++ llvm::InitializeAllTargetInfos(); ++ llvm::InitializeAllTargets(); ++ llvm::InitializeAllTargetMCs(); ++ llvm::InitializeAllAsmPrinters(); + + if (OptHostSupportsJit) { + auto J = llvm::orc::LLJITBuilder().create(); +@@ -90,9 +97,30 @@ int main(int argc, const char **argv) { + return 0; + } + ++ clang::IncrementalCompilerBuilder CB; ++ CB.SetCompilerArgs(ClangArgv); ++ ++ std::unique_ptr DeviceCI; ++ if (CudaEnabled) { ++ if (!CudaPath.empty()) ++ CB.SetCudaSDK(CudaPath); ++ ++ if (OffloadArch.empty()) { ++ OffloadArch = "sm_35"; ++ } ++ CB.SetOffloadArch(OffloadArch); ++ ++ DeviceCI = ExitOnErr(CB.CreateCudaDevice()); ++ } ++ + // FIXME: Investigate if we could use runToolOnCodeWithArgs from tooling. It + // can replace the boilerplate code for creation of the compiler instance. +- auto CI = ExitOnErr(clang::IncrementalCompilerBuilder::create(ClangArgv)); ++ std::unique_ptr CI; ++ if (CudaEnabled) { ++ CI = ExitOnErr(CB.CreateCudaHost()); ++ } else { ++ CI = ExitOnErr(CB.CreateCpp()); ++ } + + // Set an error handler, so that any LLVM backend diagnostics go through our + // error handler. +@@ -101,8 +129,23 @@ int main(int argc, const char **argv) { + + // Load any requested plugins. + CI->LoadRequestedPlugins(); ++ if (CudaEnabled) ++ DeviceCI->LoadRequestedPlugins(); ++ ++ std::unique_ptr Interp; ++ if (CudaEnabled) { ++ Interp = ExitOnErr( ++ clang::Interpreter::createWithCUDA(std::move(CI), std::move(DeviceCI))); ++ ++ if (CudaPath.empty()) { ++ ExitOnErr(Interp->LoadDynamicLibrary("libcudart.so")); ++ } else { ++ auto CudaRuntimeLibPath = CudaPath + "/lib/libcudart.so"; ++ ExitOnErr(Interp->LoadDynamicLibrary(CudaRuntimeLibPath.c_str())); ++ } ++ } else ++ Interp = ExitOnErr(clang::Interpreter::create(std::move(CI))); + +- auto Interp = ExitOnErr(clang::Interpreter::create(std::move(CI))); + for (const std::string &input : OptInputs) { + if (auto Err = Interp->ParseAndExecute(input)) + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "error: "); +diff --git a/clang/unittests/Interpreter/ExceptionTests/InterpreterExceptionTest.cpp b/clang/unittests/Interpreter/ExceptionTests/InterpreterExceptionTest.cpp +index 6d0433a98..63bb69038 100644 +--- a/clang/unittests/Interpreter/ExceptionTests/InterpreterExceptionTest.cpp ++++ b/clang/unittests/Interpreter/ExceptionTests/InterpreterExceptionTest.cpp +@@ -38,7 +38,9 @@ createInterpreter(const Args &ExtraArgs = {}, + DiagnosticConsumer *Client = nullptr) { + Args ClangArgs = {"-Xclang", "-emit-llvm-only"}; + ClangArgs.insert(ClangArgs.end(), ExtraArgs.begin(), ExtraArgs.end()); +- auto CI = cantFail(clang::IncrementalCompilerBuilder::create(ClangArgs)); ++ auto CB = clang::IncrementalCompilerBuilder(); ++ CB.SetCompilerArgs(ClangArgs); ++ auto CI = cantFail(CB.CreateCpp()); + if (Client) + CI->getDiagnostics().setClient(Client, /*ShouldOwnClient=*/false); + return cantFail(clang::Interpreter::create(std::move(CI))); +diff --git a/clang/unittests/Interpreter/IncrementalProcessingTest.cpp b/clang/unittests/Interpreter/IncrementalProcessingTest.cpp +index b7ad468e1..6d477c9ab 100644 +--- a/clang/unittests/Interpreter/IncrementalProcessingTest.cpp ++++ b/clang/unittests/Interpreter/IncrementalProcessingTest.cpp +@@ -52,7 +52,9 @@ const Function *getGlobalInit(llvm::Module *M) { + + TEST(IncrementalProcessing, EmitCXXGlobalInitFunc) { + std::vector ClangArgv = {"-Xclang", "-emit-llvm-only"}; +- auto CI = llvm::cantFail(IncrementalCompilerBuilder::create(ClangArgv)); ++ auto CB = clang::IncrementalCompilerBuilder(); ++ CB.SetCompilerArgs(ClangArgv); ++ auto CI = cantFail(CB.CreateCpp()); + auto Interp = llvm::cantFail(Interpreter::create(std::move(CI))); + + std::array PTUs; +diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp +index 330fd18ab..338003cd9 100644 +--- a/clang/unittests/Interpreter/InterpreterTest.cpp ++++ b/clang/unittests/Interpreter/InterpreterTest.cpp +@@ -46,7 +46,9 @@ createInterpreter(const Args &ExtraArgs = {}, + DiagnosticConsumer *Client = nullptr) { + Args ClangArgs = {"-Xclang", "-emit-llvm-only"}; + ClangArgs.insert(ClangArgs.end(), ExtraArgs.begin(), ExtraArgs.end()); +- auto CI = cantFail(clang::IncrementalCompilerBuilder::create(ClangArgs)); ++ auto CB = clang::IncrementalCompilerBuilder(); ++ CB.SetCompilerArgs(ClangArgs); ++ auto CI = cantFail(CB.CreateCpp()); + if (Client) + CI->getDiagnostics().setClient(Client, /*ShouldOwnClient=*/false); + return cantFail(clang::Interpreter::create(std::move(CI))); diff --git a/interpreter/CppInterOp/patches/llvm/clang16-3-WeakRef.patch b/interpreter/CppInterOp/patches/llvm/clang16-3-WeakRef.patch new file mode 100644 index 0000000000000..26172ef4b22f8 --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/clang16-3-WeakRef.patch @@ -0,0 +1,33 @@ +diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp +index 978e4d404..683449958 100644 +--- a/clang/lib/CodeGen/CodeGenModule.cpp ++++ b/clang/lib/CodeGen/CodeGenModule.cpp +@@ -7206,7 +7206,6 @@ void CodeGenModule::moveLazyEmissionStates(CodeGenModule *NewBuilder) { + "Newly created module should not have manglings"); + NewBuilder->Manglings = std::move(Manglings); + +- assert(WeakRefReferences.empty() && "Not all WeakRefRefs have been applied"); + NewBuilder->WeakRefReferences = std::move(WeakRefReferences); + + NewBuilder->TBAA = std::move(TBAA); +diff --git a/clang/test/Interpreter/execute-weak.cpp b/clang/test/Interpreter/execute-weak.cpp +index 5b343512c..3c6978165 100644 +--- a/clang/test/Interpreter/execute-weak.cpp ++++ b/clang/test/Interpreter/execute-weak.cpp +@@ -2,11 +2,16 @@ + // RUN: clang-repl "int i = 10;" 'extern "C" int printf(const char*,...);' \ + // RUN: 'auto r1 = printf("i = %d\n", i);' | FileCheck --check-prefix=CHECK-DRIVER %s + // CHECK-DRIVER: i = 10 ++// + // UNSUPPORTED: system-aix, system-windows + // RUN: cat %s | clang-repl | FileCheck %s ++ + extern "C" int printf(const char *, ...); + int __attribute__((weak)) bar() { return 42; } + auto r4 = printf("bar() = %d\n", bar()); + // CHECK: bar() = 42 + ++int a = 12; ++static __typeof(a) b __attribute__((__weakref__("a"))); ++int c = b; + %quit diff --git a/interpreter/CppInterOp/patches/llvm/clang17-1-NewOperator.patch b/interpreter/CppInterOp/patches/llvm/clang17-1-NewOperator.patch new file mode 100644 index 0000000000000..fd32d792c5326 --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/clang17-1-NewOperator.patch @@ -0,0 +1,205 @@ +From a3f213ef4a7e293152c272cce78ad5d10a3ede52 Mon Sep 17 00:00:00 2001 +From: Vassil Vassilev +Date: Fri, 22 Dec 2023 08:38:23 +0000 +Subject: [PATCH] [clang-repl] Add a interpreter-specific overload of operator + new for C++. + +This patch brings back the basic support for C by inserting the required for +value printing runtime only when we are in C++ mode. Additionally, it defines +a new overload of operator placement new because we can't really forward declare +it in a library-agnostic way. + +Fixes the issue described in llvm/llvm-project#69072. +--- + clang/include/clang/Interpreter/Interpreter.h | 4 +-- + clang/lib/Interpreter/Interpreter.cpp | 33 +++++++++++++++---- + clang/test/Interpreter/incremental-mode.cpp | 3 +- + .../unittests/Interpreter/InterpreterTest.cpp | 29 +++------------- + 4 files changed, 36 insertions(+), 33 deletions(-) + +diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h +index 01858dfcc90ac5..292fa566ae7037 100644 +--- a/clang/include/clang/Interpreter/Interpreter.h ++++ b/clang/include/clang/Interpreter/Interpreter.h +@@ -129,7 +129,7 @@ class Interpreter { + llvm::Expected + getSymbolAddressFromLinkerName(llvm::StringRef LinkerName) const; + +- enum InterfaceKind { NoAlloc, WithAlloc, CopyArray }; ++ enum InterfaceKind { NoAlloc, WithAlloc, CopyArray, NewTag }; + + const llvm::SmallVectorImpl &getValuePrintingInfo() const { + return ValuePrintingInfo; +@@ -144,7 +144,7 @@ class Interpreter { + + llvm::DenseMap Dtors; + +- llvm::SmallVector ValuePrintingInfo; ++ llvm::SmallVector ValuePrintingInfo; + }; + } // namespace clang + +diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp +index c9fcef5b5b5af1..9f97a3c6b0be9e 100644 +--- a/clang/lib/Interpreter/Interpreter.cpp ++++ b/clang/lib/Interpreter/Interpreter.cpp +@@ -248,7 +248,7 @@ Interpreter::~Interpreter() { + // can't find the precise resource directory in unittests so we have to hard + // code them. + const char *const Runtimes = R"( +- void* operator new(__SIZE_TYPE__, void* __p) noexcept; ++#ifdef __cplusplus + void *__clang_Interpreter_SetValueWithAlloc(void*, void*, void*); + void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*); + void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, void*); +@@ -256,15 +256,18 @@ const char *const Runtimes = R"( + void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, double); + void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, long double); + void __clang_Interpreter_SetValueNoAlloc(void*,void*,void*,unsigned long long); ++ struct __clang_Interpreter_NewTag{} __ci_newtag; ++ void* operator new(__SIZE_TYPE__, void* __p, __clang_Interpreter_NewTag) noexcept; + template + void __clang_Interpreter_SetValueCopyArr(T* Src, void* Placement, unsigned long Size) { + for (auto Idx = 0; Idx < Size; ++Idx) +- new ((void*)(((T*)Placement) + Idx)) T(Src[Idx]); ++ new ((void*)(((T*)Placement) + Idx), __ci_newtag) T(Src[Idx]); + } + template + void __clang_Interpreter_SetValueCopyArr(const T (*Src)[N], void* Placement, unsigned long Size) { + __clang_Interpreter_SetValueCopyArr(Src[0], Placement, Size); + } ++#endif // __cplusplus + )"; + + llvm::Expected> +@@ -279,7 +282,7 @@ Interpreter::create(std::unique_ptr CI) { + if (!PTU) + return PTU.takeError(); + +- Interp->ValuePrintingInfo.resize(3); ++ Interp->ValuePrintingInfo.resize(4); + // FIXME: This is a ugly hack. Undo command checks its availability by looking + // at the size of the PTU list. However we have parsed something in the + // beginning of the REPL so we have to mark them as 'Irrevocable'. +@@ -500,7 +503,7 @@ Interpreter::CompileDtorCall(CXXRecordDecl *CXXRD) { + static constexpr llvm::StringRef MagicRuntimeInterface[] = { + "__clang_Interpreter_SetValueNoAlloc", + "__clang_Interpreter_SetValueWithAlloc", +- "__clang_Interpreter_SetValueCopyArr"}; ++ "__clang_Interpreter_SetValueCopyArr", "__ci_newtag"}; + + bool Interpreter::FindRuntimeInterface() { + if (llvm::all_of(ValuePrintingInfo, [](Expr *E) { return E != nullptr; })) +@@ -530,6 +533,9 @@ bool Interpreter::FindRuntimeInterface() { + if (!LookupInterface(ValuePrintingInfo[CopyArray], + MagicRuntimeInterface[CopyArray])) + return false; ++ if (!LookupInterface(ValuePrintingInfo[NewTag], ++ MagicRuntimeInterface[NewTag])) ++ return false; + return true; + } + +@@ -607,7 +613,9 @@ class RuntimeInterfaceBuilder + .getValuePrintingInfo()[Interpreter::InterfaceKind::CopyArray], + SourceLocation(), Args, SourceLocation()); + } +- Expr *Args[] = {AllocCall.get()}; ++ Expr *Args[] = { ++ AllocCall.get(), ++ Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NewTag]}; + ExprResult CXXNewCall = S.BuildCXXNew( + E->getSourceRange(), + /*UseGlobal=*/true, /*PlacementLParen=*/SourceLocation(), Args, +@@ -628,8 +636,9 @@ class RuntimeInterfaceBuilder + Interp.getValuePrintingInfo()[Interpreter::InterfaceKind::NoAlloc], + E->getBeginLoc(), Args, E->getEndLoc()); + } ++ default: ++ llvm_unreachable("Unhandled Interpreter::InterfaceKind"); + } +- llvm_unreachable("Unhandled Interpreter::InterfaceKind"); + } + + Interpreter::InterfaceKind VisitRecordType(const RecordType *Ty) { +@@ -814,3 +823,15 @@ __clang_Interpreter_SetValueNoAlloc(void *This, void *OutVal, void *OpaqueType, + VRef = Value(static_cast(This), OpaqueType); + VRef.setLongDouble(Val); + } ++ ++// A trampoline to work around the fact that operator placement new cannot ++// really be forward declared due to libc++ and libstdc++ declaration mismatch. ++// FIXME: __clang_Interpreter_NewTag is ODR violation because we get the same ++// definition in the interpreter runtime. We should move it in a runtime header ++// which gets included by the interpreter and here. ++struct __clang_Interpreter_NewTag {}; ++REPL_EXTERNAL_VISIBILITY void * ++operator new(size_t __sz, void *__p, __clang_Interpreter_NewTag) noexcept { ++ // Just forward to the standard operator placement new. ++ return operator new(__sz, __p); ++} +diff --git a/clang/test/Interpreter/incremental-mode.cpp b/clang/test/Interpreter/incremental-mode.cpp +index e6350d237ef578..d63cee0dd6d15f 100644 +--- a/clang/test/Interpreter/incremental-mode.cpp ++++ b/clang/test/Interpreter/incremental-mode.cpp +@@ -1,3 +1,4 @@ + // RUN: clang-repl -Xcc -E +-// RUN: clang-repl -Xcc -emit-llvm ++// RUN: clang-repl -Xcc -emit-llvm ++// RUN: clang-repl -Xcc -xc + // expected-no-diagnostics +diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp +index 5f2911e9a7adad..1e0854b3c4af46 100644 +--- a/clang/unittests/Interpreter/InterpreterTest.cpp ++++ b/clang/unittests/Interpreter/InterpreterTest.cpp +@@ -248,28 +248,10 @@ TEST(IncrementalProcessing, FindMangledNameSymbol) { + #endif // _WIN32 + } + +-static void *AllocateObject(TypeDecl *TD, Interpreter &Interp) { ++static Value AllocateObject(TypeDecl *TD, Interpreter &Interp) { + std::string Name = TD->getQualifiedNameAsString(); +- const clang::Type *RDTy = TD->getTypeForDecl(); +- clang::ASTContext &C = Interp.getCompilerInstance()->getASTContext(); +- size_t Size = C.getTypeSize(RDTy); +- void *Addr = malloc(Size); +- +- // Tell the interpreter to call the default ctor with this memory. Synthesize: +- // new (loc) ClassName; +- static unsigned Counter = 0; +- std::stringstream SS; +- SS << "auto _v" << Counter++ << " = " +- << "new ((void*)" +- // Windows needs us to prefix the hexadecimal value of a pointer with '0x'. +- << std::hex << std::showbase << (size_t)Addr << ")" << Name << "();"; +- +- auto R = Interp.ParseAndExecute(SS.str()); +- if (!R) { +- free(Addr); +- return nullptr; +- } +- ++ Value Addr; ++ cantFail(Interp.ParseAndExecute("new " + Name + "()", &Addr)); + return Addr; + } + +@@ -317,7 +299,7 @@ TEST(IncrementalProcessing, InstantiateTemplate) { + } + + TypeDecl *TD = cast(LookupSingleName(*Interp, "A")); +- void *NewA = AllocateObject(TD, *Interp); ++ Value NewA = AllocateObject(TD, *Interp); + + // Find back the template specialization + VarDecl *VD = static_cast(*PTUDeclRange.begin()); +@@ -328,8 +310,7 @@ TEST(IncrementalProcessing, InstantiateTemplate) { + typedef int (*TemplateSpecFn)(void *); + auto fn = + cantFail(Interp->getSymbolAddress(MangledName)).toPtr(); +- EXPECT_EQ(42, fn(NewA)); +- free(NewA); ++ EXPECT_EQ(42, fn(NewA.getPtr())); + } + + #ifdef CLANG_INTERPRETER_NO_SUPPORT_EXEC diff --git a/interpreter/CppInterOp/patches/llvm/emscripten-clang19-1-CrossCompile.patch b/interpreter/CppInterOp/patches/llvm/emscripten-clang19-1-CrossCompile.patch new file mode 100644 index 0000000000000..4aa6b1539eaad --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/emscripten-clang19-1-CrossCompile.patch @@ -0,0 +1,16 @@ +diff --git a/llvm/cmake/modules/CrossCompile.cmake b/llvm/cmake/modules/CrossCompile.cmake +index 6af47b51d4c6..c635e7f5be9e 100644 +--- a/llvm/cmake/modules/CrossCompile.cmake ++++ b/llvm/cmake/modules/CrossCompile.cmake +@@ -70,8 +70,8 @@ function(llvm_create_cross_target project_name target_name toolchain buildtype) + add_custom_command(OUTPUT ${${project_name}_${target_name}_BUILD}/CMakeCache.txt + COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" + -DCMAKE_MAKE_PROGRAM="${CMAKE_MAKE_PROGRAM}" +- -DCMAKE_C_COMPILER_LAUNCHER="${CMAKE_C_COMPILER_LAUNCHER}" +- -DCMAKE_CXX_COMPILER_LAUNCHER="${CMAKE_CXX_COMPILER_LAUNCHER}" ++ -DCMAKE_C_COMPILER="clang" ++ -DCMAKE_CXX_COMPILER="clang++" + ${CROSS_TOOLCHAIN_FLAGS_${target_name}} ${CMAKE_CURRENT_SOURCE_DIR} + ${CROSS_TOOLCHAIN_FLAGS_${project_name}_${target_name}} + -DLLVM_TARGET_IS_CROSSCOMPILE_HOST=TRUE + diff --git a/interpreter/CppInterOp/patches/llvm/emscripten-clang19-2-shift-temporary-files-to-tmp-dir.patch b/interpreter/CppInterOp/patches/llvm/emscripten-clang19-2-shift-temporary-files-to-tmp-dir.patch new file mode 100644 index 0000000000000..32ac45c13f3ae --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/emscripten-clang19-2-shift-temporary-files-to-tmp-dir.patch @@ -0,0 +1,15 @@ +diff --git a/clang/lib/Interpreter/Wasm.cpp b/clang/lib/Interpreter/Wasm.cpp +index aa10b160ccf8..184867e2b55f 100644 +--- a/clang/lib/Interpreter/Wasm.cpp ++++ b/clang/lib/Interpreter/Wasm.cpp +@@ -76,8 +76,8 @@ llvm::Error WasmIncrementalExecutor::addModule(PartialTranslationUnit &PTU) { + llvm::TargetMachine *TargetMachine = Target->createTargetMachine( + PTU.TheModule->getTargetTriple(), "", "", TO, llvm::Reloc::Model::PIC_); + PTU.TheModule->setDataLayout(TargetMachine->createDataLayout()); +- std::string ObjectFileName = PTU.TheModule->getName().str() + ".o"; +- std::string BinaryFileName = PTU.TheModule->getName().str() + ".wasm"; ++ std::string ObjectFileName = "/tmp/" + PTU.TheModule->getName().str() + ".o"; ++ std::string BinaryFileName = "/tmp/" + PTU.TheModule->getName().str() + ".wasm"; + + std::error_code Error; + llvm::raw_fd_ostream ObjectFileOutput(llvm::StringRef(ObjectFileName), Error); diff --git a/interpreter/CppInterOp/patches/llvm/emscripten-clang19-3-remove-zdefs.patch b/interpreter/CppInterOp/patches/llvm/emscripten-clang19-3-remove-zdefs.patch new file mode 100644 index 0000000000000..6d897e067d816 --- /dev/null +++ b/interpreter/CppInterOp/patches/llvm/emscripten-clang19-3-remove-zdefs.patch @@ -0,0 +1,13 @@ +diff --git a/llvm/cmake/modules/HandleLLVMOptions.cmake b/llvm/cmake/modules/HandleLLVMOptions.cmake +index 5ca580fbb..cff186b7b 100644 +--- a/llvm/cmake/modules/HandleLLVMOptions.cmake ++++ b/llvm/cmake/modules/HandleLLVMOptions.cmake +@@ -302,7 +302,7 @@ endif() + + # Pass -Wl,-z,defs. This makes sure all symbols are defined. Otherwise a DSO + # build might work on ELF but fail on MachO/COFF. +-if(NOT (CMAKE_SYSTEM_NAME MATCHES "Darwin|FreeBSD|OpenBSD|DragonFly|AIX|OS390" OR ++if(NOT (CMAKE_SYSTEM_NAME MATCHES "Darwin|FreeBSD|OpenBSD|DragonFly|AIX|OS390|Emscripten" OR + WIN32 OR CYGWIN) AND + NOT LLVM_USE_SANITIZER) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,defs") diff --git a/interpreter/CppInterOp/unittests/.clang-tidy b/interpreter/CppInterOp/unittests/.clang-tidy new file mode 100644 index 0000000000000..f5980913957b4 --- /dev/null +++ b/interpreter/CppInterOp/unittests/.clang-tidy @@ -0,0 +1,11 @@ +Checks: > + -misc-use-internal-linkage, + -modernize-pass-by-value, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -performance-unnecessary-value-param, + -performance-no-int-to-ptr, + -bugprone-multi-level-implicit-pointer-conversion, diff --git a/interpreter/CppInterOp/unittests/CMakeLists.txt b/interpreter/CppInterOp/unittests/CMakeLists.txt new file mode 100644 index 0000000000000..972b1e47520b9 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CMakeLists.txt @@ -0,0 +1,48 @@ +set(CTEST_BUILD_NAME + ${CMAKE_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}-${CMAKE_BUILD_TYPE}) +enable_testing() + +# LLVM builds (not installed llvm) provides gtest. +if (NOT TARGET gtest) + include(GoogleTest) +endif() + +set(gtest_libs gtest gtest_main) +# Clang prior than clang13 (I think) merges both gmock into gtest. +if (TARGET gmock) + list(APPEND gtest_libs gmock gmock_main) +endif() + +add_custom_target(CppInterOpUnitTests) +set_target_properties(CppInterOpUnitTests PROPERTIES FOLDER "CppInterOp tests") +add_dependencies(CppInterOpUnitTests clangCppInterOp) + +set (TIMEOUT_VALUE 2400) +function(add_cppinterop_unittest name) + add_executable(${name} EXCLUDE_FROM_ALL ${ARGN}) + add_dependencies(CppInterOpUnitTests ${name}) + target_include_directories(${name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${GTEST_INCLUDE_DIR}) + set_property(TARGET ${name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +if(WIN32) + target_link_libraries(${name} PUBLIC ${ARG_LIBRARIES} ${gtest_libs}) + set_property(TARGET ${name} APPEND_STRING PROPERTY LINK_FLAGS "${MSVC_EXPORTS}") +else() + target_link_libraries(${name} PUBLIC ${ARG_LIBRARIES} ${gtest_libs} pthread) +endif() + add_test(NAME cppinterop-${name} COMMAND ${name}) + set_tests_properties(cppinterop-${name} PROPERTIES + TIMEOUT "${TIMEOUT_VALUE}" + ENVIRONMENT "CPLUS_INCLUDE_PATH=${CMAKE_BINARY_DIR}/etc" + LABELS + DEPENDS) + # FIXME: Just call gtest_add_tests this function is available. + #gtest_add_tests(${name} "${Arguments}" AUTO) +endfunction() + +add_subdirectory(CppInterOp) + +add_custom_target(check-cppinterop COMMAND ${CMAKE_CTEST_COMMAND} -V --build-config $ + DEPENDS CppInterOpUnitTests WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +set_target_properties(check-cppinterop PROPERTIES FOLDER "CppInterOp tests") diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt new file mode 100644 index 0000000000000..25f59b3945186 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt @@ -0,0 +1,52 @@ +set(LLVM_LINK_COMPONENTS + Support +) + +add_cppinterop_unittest(CppInterOpTests + CUDATest.cpp + EnumReflectionTest.cpp + FunctionReflectionTest.cpp + InterpreterTest.cpp + JitTest.cpp + ScopeReflectionTest.cpp + TypeReflectionTest.cpp + Utils.cpp + VariableReflectionTest.cpp +) + +target_link_libraries(CppInterOpTests + PRIVATE + clangCppInterOp +) + +set_output_directory(CppInterOpTests + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/CppInterOpTests/unittests/bin/$/ + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/CppInterOpTests/unittests/bin/$/ +) + +if(NOT WIN32) + set_source_files_properties(VariableReflectionTest.cpp PROPERTIES COMPILE_FLAGS + "-Wno-pedantic" +) +endif() + +set_source_files_properties(InterpreterTest.cpp PROPERTIES COMPILE_DEFINITIONS + "LLVM_BINARY_DIR=\"${LLVM_BINARY_DIR}\"" +) +export_executable_symbols(CppInterOpTests) + +unset(LLVM_LINK_COMPONENTS) + +add_cppinterop_unittest(DynamicLibraryManagerTests DynamicLibraryManagerTest.cpp) +target_link_libraries(DynamicLibraryManagerTests + PRIVATE + clangCppInterOp +) + +set_output_directory(DynamicLibraryManagerTests + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/TestSharedLib/unittests/bin/$/ + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/TestSharedLib/unittests/bin/$/ +) +add_dependencies(DynamicLibraryManagerTests TestSharedLib) +#export_executable_symbols_for_plugins(TestSharedLib) +add_subdirectory(TestSharedLib) diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp new file mode 100644 index 0000000000000..ea171b46e57a8 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp @@ -0,0 +1,77 @@ +#include "Utils.h" + +#include "clang/Basic/Version.h" +#include "clang/Interpreter/CppInterOp.h" + +#include "gtest/gtest.h" + +using namespace TestUtils; + +static bool HasCudaSDK() { + auto supportsCudaSDK = []() { +#if CLANG_VERSION_MAJOR < 16 + // FIXME: Enable this for cling. + return false; +#endif // CLANG_VERSION_MAJOR < 16 + Cpp::CreateInterpreter({}, {"--cuda"}); + return Cpp::Declare("__global__ void test_func() {}" + "test_func<<<1,1>>>();") == 0; + }; + static bool hasCuda = supportsCudaSDK(); + return hasCuda; +} + +static bool HasCudaRuntime() { + auto supportsCuda = []() { +#if CLANG_VERSION_MAJOR < 16 + // FIXME: Enable this for cling. + return false; +#endif //CLANG_VERSION_MAJOR < 16 + if (!HasCudaSDK()) + return false; + + Cpp::CreateInterpreter({}, {"--cuda"}); + if (Cpp::Declare("__global__ void test_func() {}" + "test_func<<<1,1>>>();")) + return false; + intptr_t result = Cpp::Evaluate("(bool)cudaGetLastError()"); + return !(bool)result; + }; + static bool hasCuda = supportsCuda(); + return hasCuda; +} + +#if CLANG_VERSION_MAJOR < 16 +TEST(DISABLED_CUDATest, Sanity) { +#else +TEST(CUDATest, Sanity) { +#endif // CLANG_VERSION_MAJOR < 16 +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + if (!HasCudaSDK()) + GTEST_SKIP() << "Skipping CUDA tests as CUDA SDK not found"; + EXPECT_TRUE(Cpp::CreateInterpreter({}, {"--cuda"})); +} + +TEST(CUDATest, CUDAH) { +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + if (!HasCudaSDK()) + GTEST_SKIP() << "Skipping CUDA tests as CUDA SDK not found"; + + Cpp::CreateInterpreter({}, {"--cuda"}); + bool success = !Cpp::Declare("#include "); + EXPECT_TRUE(success); +} + +TEST(CUDATest, CUDARuntime) { +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + if (!HasCudaRuntime()) + GTEST_SKIP() << "Skipping CUDA tests as CUDA runtime not found"; + + EXPECT_TRUE(HasCudaRuntime()); +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/DynamicLibraryManagerTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/DynamicLibraryManagerTest.cpp new file mode 100644 index 0000000000000..01624ef78b376 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/DynamicLibraryManagerTest.cpp @@ -0,0 +1,56 @@ +#include "gtest/gtest.h" + +#include "clang/Interpreter/CppInterOp.h" + +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +// This function isn't referenced outside its translation unit, but it +// can't use the "static" keyword because its address is used for +// GetMainExecutable (since some platforms don't support taking the +// address of main, and some platforms can't implement GetMainExecutable +// without being given the address of a function in the main executable). +std::string GetExecutablePath(const char* Argv0) { + // This just needs to be some symbol in the binary; C++ doesn't + // allow taking the address of ::main however. + void* MainAddr = (void*)intptr_t(GetExecutablePath); + return llvm::sys::fs::getMainExecutable(Argv0, MainAddr); +} + +TEST(DynamicLibraryManagerTest, Sanity) { + EXPECT_TRUE(Cpp::CreateInterpreter()); + EXPECT_FALSE(Cpp::GetFunctionAddress("ret_zero")); + + std::string BinaryPath = GetExecutablePath(/*Argv0=*/nullptr); + llvm::StringRef Dir = llvm::sys::path::parent_path(BinaryPath); + Cpp::AddSearchPath(Dir.str().c_str()); + + // FIXME: dlsym on mach-o takes the C-level name, however, the macho-o format + // adds an additional underscore (_) prefix to the lowered names. Figure out + // how to harmonize that API. +#ifdef __APPLE__ + std::string PathToTestSharedLib = + Cpp::SearchLibrariesForSymbol("_ret_zero", /*system_search=*/false); +#else + std::string PathToTestSharedLib = + Cpp::SearchLibrariesForSymbol("ret_zero", /*system_search=*/false); +#endif // __APPLE__ + + EXPECT_STRNE("", PathToTestSharedLib.c_str()) + << "Cannot find: '" << PathToTestSharedLib << "' in '" << Dir.str() + << "'"; + + EXPECT_TRUE(Cpp::LoadLibrary(PathToTestSharedLib.c_str())); + // Force ExecutionEngine to be created. + Cpp::Process(""); + // FIXME: Conda returns false to run this code on osx. +#ifndef __APPLE__ + EXPECT_TRUE(Cpp::GetFunctionAddress("ret_zero")); +#endif //__APPLE__ + + Cpp::UnloadLibrary("TestSharedLib"); + // We have no reliable way to check if it was unloaded because posix does not + // require the library to be actually unloaded but just the handle to be + // invalidated... + // EXPECT_FALSE(Cpp::GetFunctionAddress("ret_zero")); +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/EnumReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/EnumReflectionTest.cpp new file mode 100644 index 0000000000000..3ec03e1368cbd --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/EnumReflectionTest.cpp @@ -0,0 +1,360 @@ +#include "Utils.h" + +#include "clang/AST/ASTContext.h" +#include "clang/Interpreter/CppInterOp.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/Sema.h" + +#include "gtest/gtest.h" + +using namespace TestUtils; +using namespace llvm; +using namespace clang; + +TEST(ScopeReflectionTest, IsEnumScope) { + std::vector Decls, SubDecls; + std::string code = R"( + enum Switch { + OFF, + ON + }; + + Switch s = Switch::OFF; + + int i = Switch::ON; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + EXPECT_TRUE(Cpp::IsEnumScope(Decls[0])); + EXPECT_FALSE(Cpp::IsEnumScope(Decls[1])); + EXPECT_FALSE(Cpp::IsEnumScope(Decls[2])); + EXPECT_FALSE(Cpp::IsEnumScope(SubDecls[0])); + EXPECT_FALSE(Cpp::IsEnumScope(SubDecls[1])); +} + +TEST(ScopeReflectionTest, IsEnumConstant) { + std::vector Decls, SubDecls; + std::string code = R"( + enum Switch { + OFF, + ON + }; + + Switch s = Switch::OFF; + + int i = Switch::ON; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + EXPECT_FALSE(Cpp::IsEnumConstant(Decls[0])); + EXPECT_FALSE(Cpp::IsEnumConstant(Decls[1])); + EXPECT_FALSE(Cpp::IsEnumConstant(Decls[2])); + EXPECT_TRUE(Cpp::IsEnumConstant(SubDecls[0])); + EXPECT_TRUE(Cpp::IsEnumConstant(SubDecls[1])); +} + +TEST(EnumReflectionTest, IsEnumType) { + std::vector Decls; + std::string code = R"( + enum class E { + a, + b + }; + + enum F { + c, + d + }; + + E e; + F f; + + auto g = E::a; + auto h = c; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_TRUE(Cpp::IsEnumType(Cpp::GetVariableType(Decls[2]))); + EXPECT_TRUE(Cpp::IsEnumType(Cpp::GetVariableType(Decls[3]))); + EXPECT_TRUE(Cpp::IsEnumType(Cpp::GetVariableType(Decls[4]))); + EXPECT_TRUE(Cpp::IsEnumType(Cpp::GetVariableType(Decls[5]))); +} + +TEST(EnumReflectionTest, GetIntegerTypeFromEnumScope) { + std::vector Decls; + std::string code = R"( + enum Switch : bool { + OFF, + ON + }; + + enum CharEnum : char { + OneChar, + TwoChar + }; + + enum IntEnum : int { + OneInt, + TwoInt + }; + + enum LongEnum : long long { + OneLong, + TwoLong + }; + + enum DefaultEnum { + OneDefault, + TwoDefault + }; + + // Non enum type + struct Employee { + int id; + int salary; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(Decls[0])), "bool"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(Decls[1])), "char"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(Decls[2])), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(Decls[3])), "long long"); +#ifdef _WIN32 + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(Decls[4])), + "int"); +#else + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(Decls[4])), "unsigned int"); +#endif + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(Decls[5])),"NULL TYPE"); +} + +TEST(EnumReflectionTest, GetIntegerTypeFromEnumType) { + std::vector Decls; + std::string code = R"( + enum Switch : bool { + OFF, + ON + }; + + enum CharEnum : char { + OneChar, + TwoChar + }; + + enum IntEnum : int { + OneInt, + TwoInt + }; + + enum LongEnum : long long { + OneLong, + TwoLong + }; + + enum DefaultEnum { + OneDefault, + TwoDefault + }; + + struct Employee { + int id; + int salary; + }; + + Switch s; + CharEnum ch; + IntEnum in; + LongEnum lng; + DefaultEnum def; + Employee emp; + )"; + + GetAllTopLevelDecls(code, Decls); + + auto get_int_type_from_enum_var = [](Decl *D) { + return Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumType(Cpp::GetVariableType(D))); + }; + + EXPECT_EQ(get_int_type_from_enum_var(Decls[5]), "NULL TYPE"); // When a nullptr is returned by GetVariableType() + EXPECT_EQ(get_int_type_from_enum_var(Decls[6]), "bool"); + EXPECT_EQ(get_int_type_from_enum_var(Decls[7]), "char"); + EXPECT_EQ(get_int_type_from_enum_var(Decls[8]), "int"); + EXPECT_EQ(get_int_type_from_enum_var(Decls[9]), "long long"); +#ifdef _WIN32 + EXPECT_EQ(get_int_type_from_enum_var(Decls[10]), "int"); +#else + EXPECT_EQ(get_int_type_from_enum_var(Decls[10]), "unsigned int"); +#endif + EXPECT_EQ(get_int_type_from_enum_var(Decls[11]), "NULL TYPE"); // When a non Enum Type variable is used +} + +TEST(EnumReflectionTest, GetEnumConstants) { + std::vector Decls; + std::string code = R"( + enum ZeroEnum { + }; + + enum OneEnum { + One_OneEnum, + }; + + enum TwoEnum { + One_TwoEnum, + Two_TwoEnum, + }; + + enum ThreeEnum { + One_ThreeEnum, + Two_ThreeEnum, + Three_ThreeEnum, + }; + + enum FourEnum { + One_FourEnum, + Two_FourEnum, + Three_FourEnum, + Four_FourEnum, + }; + + struct Employee { + int id; + int salary; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetEnumConstants(Decls[0]).size(), 0); + EXPECT_EQ(Cpp::GetEnumConstants(Decls[1]).size(), 1); + EXPECT_EQ(Cpp::GetEnumConstants(Decls[2]).size(), 2); + EXPECT_EQ(Cpp::GetEnumConstants(Decls[3]).size(), 3); + EXPECT_EQ(Cpp::GetEnumConstants(Decls[4]).size(), 4); + EXPECT_EQ(Cpp::GetEnumConstants(Decls[5]).size(), 0); +} + +TEST(EnumReflectionTest, GetEnumConstantType) { + std::vector Decls; + std::string code = R"( + enum Enum0 { + Constant0 = 0 + }; + + enum class Enum1 { + Constant1 = 1 + }; + )"; + + GetAllTopLevelDecls(code, Decls); + + auto get_enum_constant_type_as_str = [](Cpp::TCppScope_t enum_constant) { + return Cpp::GetTypeAsString(Cpp::GetEnumConstantType(enum_constant)); + }; + + auto EnumConstants0 = Cpp::GetEnumConstants(Decls[0]); + + EXPECT_EQ(get_enum_constant_type_as_str(EnumConstants0[0]), "Enum0"); + + auto EnumConstants1 = Cpp::GetEnumConstants(Decls[1]); + + EXPECT_EQ(get_enum_constant_type_as_str(EnumConstants1[0]), "Enum1"); + + EXPECT_EQ(get_enum_constant_type_as_str(Decls[1]), "NULL TYPE"); + + EXPECT_EQ(get_enum_constant_type_as_str(nullptr), "NULL TYPE"); +} + +TEST(EnumReflectionTest, GetEnumConstantValue) { + std::vector Decls; + std::string code = R"( + enum Counter { + Zero = 0, + One, + FiftyTwo = 52, + FiftyThree, + FiftyFour, + MinusTen = -10, + MinusNine + }; + int a = 10; + )"; + + GetAllTopLevelDecls(code, Decls); + auto EnumConstants = Cpp::GetEnumConstants(Decls[0]); + + EXPECT_EQ(Cpp::GetEnumConstantValue(EnumConstants[0]), 0); + EXPECT_EQ(Cpp::GetEnumConstantValue(EnumConstants[1]), 1); + EXPECT_EQ(Cpp::GetEnumConstantValue(EnumConstants[2]), 52); + EXPECT_EQ(Cpp::GetEnumConstantValue(EnumConstants[3]), 53); + EXPECT_EQ(Cpp::GetEnumConstantValue(EnumConstants[4]), 54); + EXPECT_EQ(Cpp::GetEnumConstantValue(EnumConstants[5]), -10); + EXPECT_EQ(Cpp::GetEnumConstantValue(EnumConstants[6]), -9); + EXPECT_EQ(Cpp::GetEnumConstantValue(Decls[1]), 0); // Checking value of non enum constant +} + +TEST(EnumReflectionTest, GetEnums) { + std::string code = R"( + enum Color { + Red, + Green, + Blue + }; + + enum Days { + Monday, + Tuesday, + Wednesday, + Thursday, + }; + + namespace Animals { + enum AnimalType { + Dog, + Cat, + Bird + }; + + enum Months { + January, + February, + March + }; + } + + class myClass { + public: + enum Color { + Red, + Green, + Blue + }; + }; + + int myVariable; + )"; + + Cpp::CreateInterpreter(); + Interp->declare(code); + std::vector enumNames1, enumNames2, enumNames3, enumNames4; + Cpp::TCppScope_t globalscope = Cpp::GetScope("", 0); + Cpp::TCppScope_t Animals_scope = Cpp::GetScope("Animals", 0); + Cpp::TCppScope_t myClass_scope = Cpp::GetScope("myClass", 0); + Cpp::TCppScope_t unsupported_scope = Cpp::GetScope("myVariable", 0); + + Cpp::GetEnums(globalscope,enumNames1); + Cpp::GetEnums(Animals_scope,enumNames2); + Cpp::GetEnums(myClass_scope, enumNames3); + Cpp::GetEnums(unsupported_scope, enumNames4); + + // Check if the enum names are correctly retrieved + EXPECT_TRUE(std::find(enumNames1.begin(), enumNames1.end(), "Color") != enumNames1.end()); + EXPECT_TRUE(std::find(enumNames1.begin(), enumNames1.end(), "Days") != enumNames1.end()); + EXPECT_TRUE(std::find(enumNames2.begin(), enumNames2.end(), "AnimalType") != enumNames2.end()); + EXPECT_TRUE(std::find(enumNames2.begin(), enumNames2.end(), "Months") != enumNames2.end()); + EXPECT_TRUE(std::find(enumNames3.begin(), enumNames3.end(), "Color") != enumNames3.end()); + EXPECT_TRUE(enumNames4.empty()); +} \ No newline at end of file diff --git a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp new file mode 100644 index 0000000000000..e8d7b73ac0ea6 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -0,0 +1,1210 @@ +#include "Utils.h" + +#include "clang/AST/ASTContext.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Interpreter/CppInterOp.h" +#include "clang/Sema/Sema.h" + +#include "gtest/gtest.h" + +#include + +using namespace TestUtils; +using namespace llvm; +using namespace clang; + +TEST(FunctionReflectionTest, GetClassMethods) { + std::vector Decls; + std::string code = R"( + class A { + public: + int f1(int a, int b) { return a + b; } + const A *f2() const { return this; } + private: + int f3() { return 0; } + void f4() {} + protected: + int f5(int i) { return i; } + }; + + typedef A shadow_A; + class B { + public: + B(int n) : b{n} {} + int b; + }; + + class C: public B { + public: + using B::B; + }; + using MyInt = int; + )"; + + GetAllTopLevelDecls(code, Decls); + + auto get_method_name = [](Cpp::TCppFunction_t method) { + return Cpp::GetFunctionSignature(method); + }; + + std::vector methods0; + Cpp::GetClassMethods(Decls[0], methods0); + + EXPECT_EQ(methods0.size(), 11); + EXPECT_EQ(get_method_name(methods0[0]), "int A::f1(int a, int b)"); + EXPECT_EQ(get_method_name(methods0[1]), "const A *A::f2() const"); + EXPECT_EQ(get_method_name(methods0[2]), "int A::f3()"); + EXPECT_EQ(get_method_name(methods0[3]), "void A::f4()"); + EXPECT_EQ(get_method_name(methods0[4]), "int A::f5(int i)"); + EXPECT_EQ(get_method_name(methods0[5]), "inline constexpr A::A()"); + EXPECT_EQ(get_method_name(methods0[6]), "inline constexpr A::A(const A &)"); + EXPECT_EQ(get_method_name(methods0[7]), "inline constexpr A &A::operator=(const A &)"); + EXPECT_EQ(get_method_name(methods0[8]), "inline constexpr A::A(A &&)"); + EXPECT_EQ(get_method_name(methods0[9]), "inline constexpr A &A::operator=(A &&)"); + EXPECT_EQ(get_method_name(methods0[10]), "inline A::~A()"); + + std::vector methods1; + Cpp::GetClassMethods(Decls[1], methods1); + EXPECT_EQ(methods0.size(), methods1.size()); + EXPECT_EQ(methods0[0], methods1[0]); + EXPECT_EQ(methods0[1], methods1[1]); + EXPECT_EQ(methods0[2], methods1[2]); + EXPECT_EQ(methods0[3], methods1[3]); + EXPECT_EQ(methods0[4], methods1[4]); + + std::vector methods2; + Cpp::GetClassMethods(Decls[2], methods2); + + EXPECT_EQ(methods2.size(), 6); + EXPECT_EQ(get_method_name(methods2[0]), "B::B(int n)"); + EXPECT_EQ(get_method_name(methods2[1]), "inline constexpr B::B(const B &)"); + EXPECT_EQ(get_method_name(methods2[2]), "inline constexpr B::B(B &&)"); + EXPECT_EQ(get_method_name(methods2[3]), "inline B::~B()"); + EXPECT_EQ(get_method_name(methods2[4]), "inline B &B::operator=(const B &)"); + EXPECT_EQ(get_method_name(methods2[5]), "inline B &B::operator=(B &&)"); + + std::vector methods3; + Cpp::GetClassMethods(Decls[3], methods3); + + EXPECT_EQ(methods3.size(), 9); + EXPECT_EQ(get_method_name(methods3[0]), "B::B(int n)"); + EXPECT_EQ(get_method_name(methods3[1]), "inline constexpr B::B(const B &)"); + EXPECT_EQ(get_method_name(methods3[3]), "inline C::C()"); + EXPECT_EQ(get_method_name(methods3[4]), "inline constexpr C::C(const C &)"); + EXPECT_EQ(get_method_name(methods3[5]), "inline constexpr C::C(C &&)"); + EXPECT_EQ(get_method_name(methods3[6]), "inline C &C::operator=(const C &)"); + EXPECT_EQ(get_method_name(methods3[7]), "inline C &C::operator=(C &&)"); + EXPECT_EQ(get_method_name(methods3[8]), "inline C::~C()"); + + // Should not crash. + std::vector methods4; + Cpp::GetClassMethods(Decls[4], methods4); + EXPECT_EQ(methods4.size(), 0); + + std::vector methods5; + Cpp::GetClassMethods(nullptr, methods5); + EXPECT_EQ(methods5.size(), 0); +} + +TEST(FunctionReflectionTest, ConstructorInGetClassMethods) { + std::vector Decls; + std::string code = R"( + struct S { + int a; + int b; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + + auto has_constructor = [](Decl* D) { + std::vector methods; + Cpp::GetClassMethods(D, methods); + for (auto method : methods) { + if (Cpp::IsConstructor(method)) + return true; + } + return false; + }; + + EXPECT_TRUE(has_constructor(Decls[0])); +} + +TEST(FunctionReflectionTest, HasDefaultConstructor) { + std::vector Decls; + std::string code = R"( + class A { + private: + int n; + }; + class B { + private: + int n; + public: + B() : n(1) {} + }; + class C { + private: + int n; + public: + C() = delete; + C(int i) : n(i) {} + }; + int sum(int a, int b){ + return a+b; + } + + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_TRUE(Cpp::HasDefaultConstructor(Decls[0])); + EXPECT_TRUE(Cpp::HasDefaultConstructor(Decls[1])); + EXPECT_FALSE(Cpp::HasDefaultConstructor(Decls[3])); +} + +TEST(FunctionReflectionTest, GetDestructor) { + std::vector Decls; + std::string code = R"( + class A { + }; + class B { + public: + ~B() {} + }; + class C { + public: + ~C() = delete; + }; + int sum(int a, int b) { + return a+b; + } + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_TRUE(Cpp::GetDestructor(Decls[0])); + EXPECT_TRUE(Cpp::GetDestructor(Decls[1])); + auto DeletedDtor = Cpp::GetDestructor(Decls[2]); + EXPECT_TRUE(DeletedDtor); + EXPECT_TRUE(Cpp::IsFunctionDeleted(DeletedDtor)); + EXPECT_FALSE(Cpp::GetDestructor(Decls[3])); +} + +TEST(FunctionReflectionTest, GetFunctionsUsingName) { + std::vector Decls; + std::string code = R"( + class A { + public: + int f1(int a, int b) { return a + b; } + int f1(int a) { return f1(a, 10); } + int f1() { return f1(10); } + private: + int f2() { return 0; } + protected: + int f3(int i) { return i; } + }; + + namespace N { + int f4(int a) { return a + 1; } + int f4() { return 0; } + } + + typedef A shadow_A; + )"; + + GetAllTopLevelDecls(code, Decls); + + // This lambda can take in the scope and the name of the function + // and returns the size of the vector returned by GetFunctionsUsingName + auto get_number_of_funcs_using_name = [&](Cpp::TCppScope_t scope, + const std::string &name) { + auto Funcs = Cpp::GetFunctionsUsingName(scope, name); + + return Funcs.size(); + }; + + EXPECT_EQ(get_number_of_funcs_using_name(Decls[0], "f1"), 3); + EXPECT_EQ(get_number_of_funcs_using_name(Decls[0], "f2"), 1); + EXPECT_EQ(get_number_of_funcs_using_name(Decls[0], "f3"), 1); + EXPECT_EQ(get_number_of_funcs_using_name(Decls[0], "f4"), 0); + EXPECT_EQ(get_number_of_funcs_using_name(Decls[1], "f4"), 2); + EXPECT_EQ(get_number_of_funcs_using_name(Decls[2], "f1"), 3); + EXPECT_EQ(get_number_of_funcs_using_name(Decls[2], "f2"), 1); + EXPECT_EQ(get_number_of_funcs_using_name(Decls[2], "f3"), 1); + EXPECT_EQ(get_number_of_funcs_using_name(Decls[2], ""), 0); +} + +TEST(FunctionReflectionTest, GetClassDecls) { + std::vector Decls, SubDecls; + std::string code = R"( + class MyTemplatedMethodClass { + template + long get_size(A, B, int i = 0) {} + + template + long get_float_size(int i, A a = A(), B b = B()) {} + + template + void get_char_size(long k, A, char ch = 'a', double l = 0.0) {} + + void f1() {} + void f2(int i, double d, long l, char ch) {} + + template + void get_size(long k, A, char ch = 'a', double l = 0.0) {} + + void f3(int i, double d, long l = 0, char ch = 'a') {} + void f4(int i = 0, double d = 0.0, long l = 0, char ch = 'a') {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + std::vector methods; + std::vector template_methods; + + Cpp::GetClassMethods(Decls[0], methods); + Cpp::GetFunctionTemplatedDecls(Decls[0], template_methods); + + EXPECT_EQ(Cpp::GetName(template_methods[0]), Cpp::GetName(SubDecls[1])); + EXPECT_EQ(Cpp::GetName(template_methods[1]), Cpp::GetName(SubDecls[2])); + EXPECT_EQ(Cpp::GetName(template_methods[2]), Cpp::GetName(SubDecls[3])); + EXPECT_EQ(Cpp::GetName(methods[0]) , Cpp::GetName(SubDecls[4])); + EXPECT_EQ(Cpp::GetName(methods[1]) , Cpp::GetName(SubDecls[5])); + EXPECT_EQ(Cpp::GetName(template_methods[3]), Cpp::GetName(SubDecls[6])); + EXPECT_EQ(Cpp::GetName(methods[2]) , Cpp::GetName(SubDecls[7])); + EXPECT_EQ(Cpp::GetName(methods[3]) , Cpp::GetName(SubDecls[8])); +} + +TEST(FunctionReflectionTest, GetFunctionReturnType) { + std::vector Decls, SubDecls, TemplateSubDecls; + std::string code = R"( + namespace N { class C {}; } + enum Switch { OFF, ON }; + + class A { + A (int i) { i++; } + int f () { return 0; } + }; + + void f1() {} + double f2() { return 0.2; } + Switch f3() { return ON; } + N::C f4() { return N::C(); } + N::C *f5() { return new N::C(); } + const N::C f6() { return N::C(); } + volatile N::C f7() { return N::C(); } + const volatile N::C f8() { return N::C(); } + int n; + + class MyTemplatedMethodClass { + template + char get_string(A) { + return 'A'; + } + + template + void get_size() {} + + template + long add_size (int i) { + return sizeof(A) + i; + } + }; + )"; + + GetAllTopLevelDecls(code, Decls, true); + GetAllSubDecls(Decls[2], SubDecls); + GetAllSubDecls(Decls[12], TemplateSubDecls); + + // #include + + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Decls[3])), "void"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Decls[4])), + "double"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Decls[5])), + "Switch"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Decls[6])), "N::C"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Decls[7])), + "N::C *"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Decls[8])), + "const N::C"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Decls[9])), + "volatile N::C"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Decls[10])), + "const volatile N::C"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Decls[11])), + "NULL TYPE"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(SubDecls[1])), "void"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(SubDecls[2])), "int"); + EXPECT_EQ( + Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(TemplateSubDecls[1])), + "char"); + EXPECT_EQ( + Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(TemplateSubDecls[2])), + "void"); + EXPECT_EQ( + Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(TemplateSubDecls[3])), + "long"); +} + +TEST(FunctionReflectionTest, GetFunctionNumArgs) { + std::vector Decls, TemplateSubDecls; + std::string code = R"( + void f1() {} + void f2(int i, double d, long l, char ch) {} + void f3(int i, double d, long l = 0, char ch = 'a') {} + void f4(int i = 0, double d = 0.0, long l = 0, char ch = 'a') {} + int a; + + class MyTemplatedMethodClass { + template + char get_string(A, int i) { + return 'A'; + } + + template + void get_size() {} + + template + long add_size (A, int i, B) { + return sizeof(A) + i; + } + }; + + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[5], TemplateSubDecls); + EXPECT_EQ(Cpp::GetFunctionNumArgs(Decls[0]), (size_t) 0); + EXPECT_EQ(Cpp::GetFunctionNumArgs(Decls[1]), (size_t) 4); + EXPECT_EQ(Cpp::GetFunctionNumArgs(Decls[2]), (size_t) 4); + EXPECT_EQ(Cpp::GetFunctionNumArgs(Decls[3]), (size_t) 4); + EXPECT_EQ(Cpp::GetFunctionNumArgs(Decls[4]), 0); + + EXPECT_EQ(Cpp::GetFunctionNumArgs(TemplateSubDecls[1]), 2); + EXPECT_EQ(Cpp::GetFunctionNumArgs(TemplateSubDecls[2]), 0); + EXPECT_EQ(Cpp::GetFunctionNumArgs(TemplateSubDecls[3]), 3); +} + +TEST(FunctionReflectionTest, GetFunctionRequiredArgs) { + std::vector Decls, TemplateSubDecls; + std::string code = R"( + void f1() {} + void f2(int i, double d, long l, char ch) {} + void f3(int i, double d, long l = 0, char ch = 'a') {} + void f4(int i = 0, double d = 0.0, long l = 0, char ch = 'a') {} + int a; + + class MyTemplatedMethodClass { + template + long get_size(A, B, int i = 0) {} + + template + long get_size(int i, A a = A(), B b = B()) {} + + template + void get_size(long k, A, char ch = 'a', double l = 0.0) {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[5], TemplateSubDecls); + + EXPECT_EQ(Cpp::GetFunctionRequiredArgs(Decls[0]), (size_t) 0); + EXPECT_EQ(Cpp::GetFunctionRequiredArgs(Decls[1]), (size_t) 4); + EXPECT_EQ(Cpp::GetFunctionRequiredArgs(Decls[2]), (size_t) 2); + EXPECT_EQ(Cpp::GetFunctionRequiredArgs(Decls[3]), (size_t) 0); + EXPECT_EQ(Cpp::GetFunctionRequiredArgs(Decls[4]), 0); + + EXPECT_EQ(Cpp::GetFunctionRequiredArgs(TemplateSubDecls[1]), 2); + EXPECT_EQ(Cpp::GetFunctionRequiredArgs(TemplateSubDecls[2]), 1); + EXPECT_EQ(Cpp::GetFunctionRequiredArgs(TemplateSubDecls[3]), 2); +} + +TEST(FunctionReflectionTest, GetFunctionArgType) { + std::vector Decls, SubDecls; + std::string code = R"( + void f1(int i, double d, long l, char ch) {} + void f2(const int i, double d[], long *l, char ch[4]) {} + int a; + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[0], 0)), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[0], 1)), "double"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[0], 2)), "long"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[0], 3)), "char"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[1], 0)), "const int"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[1], 1)), "double[]"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[1], 2)), "long *"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[1], 3)), "char[4]"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionArgType(Decls[2], 0)), "NULL TYPE"); +} + +TEST(FunctionReflectionTest, GetFunctionSignature) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + void f(int i, double d, long l = 0, char ch = 'a') {} + }; + + namespace N + { + void f(int i, double d, long l = 0, char ch = 'a') {} + } + + void f1() {} + C f2(int i, double d, long l = 0, char ch = 'a') { return C(); } + C *f3(int i, double d, long l = 0, char ch = 'a') { return new C(); } + void f4(int i = 0, double d = 0.0, long l = 0, char ch = 'a') {} + class ABC {}; + )"; + + GetAllTopLevelDecls(code, Decls, true); + GetAllSubDecls(Decls[0], Decls); + GetAllSubDecls(Decls[1], Decls); + + EXPECT_EQ(Cpp::GetFunctionSignature(Decls[2]), "void f1()"); + EXPECT_EQ(Cpp::GetFunctionSignature(Decls[3]), + "C f2(int i, double d, long l = 0, char ch = 'a')"); + EXPECT_EQ(Cpp::GetFunctionSignature(Decls[4]), + "C *f3(int i, double d, long l = 0, char ch = 'a')"); + EXPECT_EQ(Cpp::GetFunctionSignature(Decls[5]), + "void f4(int i = 0, double d = 0., long l = 0, char ch = 'a')"); + EXPECT_EQ(Cpp::GetFunctionSignature(Decls[6]), + ""); + EXPECT_EQ(Cpp::GetFunctionSignature(Decls[8]), + "void C::f(int i, double d, long l = 0, char ch = 'a')"); + EXPECT_EQ(Cpp::GetFunctionSignature(Decls[13]), + "void N::f(int i, double d, long l = 0, char ch = 'a')"); + EXPECT_EQ(Cpp::GetFunctionSignature(nullptr), ""); +} + +TEST(FunctionReflectionTest, IsTemplatedFunction) { + std::vector Decls, SubDeclsC1, SubDeclsC2; + std::string code = R"( + void f1(int a) {} + + template + void f2(T a) {} + + class C1 { + void f1(int a) {} + + template + void f2(T a) {} + }; + + class ABC {}; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[2], SubDeclsC1); + + EXPECT_FALSE(Cpp::IsTemplatedFunction(Decls[0])); + EXPECT_TRUE(Cpp::IsTemplatedFunction(Decls[1])); + EXPECT_FALSE(Cpp::IsTemplatedFunction(Decls[3])); + EXPECT_FALSE(Cpp::IsTemplatedFunction(SubDeclsC1[1])); + EXPECT_TRUE(Cpp::IsTemplatedFunction(SubDeclsC1[2])); +} + +TEST(FunctionReflectionTest, ExistsFunctionTemplate) { + std::vector Decls; + std::string code = R"( + template + void f(T a) {} + + class C { + template + void f(T a) {} + }; + + void f(char ch) {} + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_TRUE(Cpp::ExistsFunctionTemplate("f", 0)); + EXPECT_TRUE(Cpp::ExistsFunctionTemplate("f", Decls[1])); + EXPECT_FALSE(Cpp::ExistsFunctionTemplate("f", Decls[2])); +} + +TEST(FunctionReflectionTest, InstantiateTemplateFunctionFromString) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + Cpp::CreateInterpreter(); + std::string code = R"(#include )"; + Interp->process(code); + const char* str = "std::make_unique"; + auto* Instance1 = (Decl*)Cpp::InstantiateTemplateFunctionFromString(str); + EXPECT_TRUE(Instance1); +} + +TEST(FunctionReflectionTest, InstantiateFunctionTemplate) { + std::vector Decls; + std::string code = R"( +template T TrivialFnTemplate() { return T(); } +)"; + + GetAllTopLevelDecls(code, Decls); + ASTContext& C = Interp->getCI()->getASTContext(); + + std::vector args1 = {C.IntTy.getAsOpaquePtr()}; + auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(), + /*type_size*/ args1.size()); + EXPECT_TRUE(isa((Decl*)Instance1)); + FunctionDecl* FD = cast((Decl*)Instance1); + FunctionDecl* FnTD1 = FD->getTemplateInstantiationPattern(); + EXPECT_TRUE(FnTD1->isThisDeclarationADefinition()); + TemplateArgument TA1 = FD->getTemplateSpecializationArgs()->get(0); + EXPECT_TRUE(TA1.getAsType()->isIntegerType()); +} + +TEST(FunctionReflectionTest, InstantiateTemplateMethod) { + std::vector Decls; + std::string code = R"( + class MyTemplatedMethodClass { + public: + template long get_size(A&); + }; + + template + long MyTemplatedMethodClass::get_size(A&) { + return sizeof(A); + } + )"; + + GetAllTopLevelDecls(code, Decls); + ASTContext& C = Interp->getCI()->getASTContext(); + + std::vector args1 = {C.IntTy.getAsOpaquePtr()}; + auto Instance1 = Cpp::InstantiateTemplate(Decls[1], args1.data(), + /*type_size*/ args1.size()); + EXPECT_TRUE(isa((Decl*)Instance1)); + FunctionDecl* FD = cast((Decl*)Instance1); + FunctionDecl* FnTD1 = FD->getTemplateInstantiationPattern(); + EXPECT_TRUE(FnTD1->isThisDeclarationADefinition()); + TemplateArgument TA1 = FD->getTemplateSpecializationArgs()->get(0); + EXPECT_TRUE(TA1.getAsType()->isIntegerType()); +} + +TEST(FunctionReflectionTest, BestTemplateFunctionMatch) { + std::vector Decls; + std::string code = R"( + class MyTemplatedMethodClass { + public: + template long get_size(A&); + template long get_size(); + template long get_size(A a, B b); + template long add_size(float a); + }; + + template + long MyTemplatedMethodClass::get_size(A&) { + return sizeof(A); + } + + template + long MyTemplatedMethodClass::get_size() { + return sizeof(A) + 1; + } + + template + long MyTemplatedMethodClass::add_size(float a) { + return sizeof(A) + long(a); + } + + template + long MyTemplatedMethodClass::get_size(A a, B b) { + return sizeof(A) + sizeof(B); + } + )"; + + GetAllTopLevelDecls(code, Decls); + std::vector candidates; + + for (auto decl : Decls) + if (Cpp::IsTemplatedFunction(decl)) candidates.push_back((Cpp::TCppFunction_t)decl); + + ASTContext& C = Interp->getCI()->getASTContext(); + + std::vector args0; + std::vector args1 = {C.IntTy.getAsOpaquePtr()}; + std::vector args2 = {C.CharTy.getAsOpaquePtr(), C.FloatTy.getAsOpaquePtr()}; + std::vector args3 = {C.FloatTy.getAsOpaquePtr()}; + + std::vector explicit_args0; + std::vector explicit_args1 = {C.IntTy.getAsOpaquePtr()}; + + Cpp::TCppFunction_t func1 = Cpp::BestTemplateFunctionMatch(candidates, explicit_args0, args1); + Cpp::TCppFunction_t func2 = Cpp::BestTemplateFunctionMatch(candidates, explicit_args1, args0); + Cpp::TCppFunction_t func3 = Cpp::BestTemplateFunctionMatch(candidates, explicit_args0, args2); + Cpp::TCppFunction_t func4 = Cpp::BestTemplateFunctionMatch(candidates, explicit_args1, args3); + + EXPECT_EQ(Cpp::GetFunctionSignature(func1), + "template<> long MyTemplatedMethodClass::get_size(int &)"); + EXPECT_EQ(Cpp::GetFunctionSignature(func2), + "template<> long MyTemplatedMethodClass::get_size()"); + EXPECT_EQ(Cpp::GetFunctionSignature(func3), + "template<> long MyTemplatedMethodClass::get_size(char a, float b)"); + EXPECT_EQ(Cpp::GetFunctionSignature(func4), + "template<> long MyTemplatedMethodClass::get_size(float &)"); +} + +TEST(FunctionReflectionTest, IsPublicMethod) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + public: + C() {} + void pub_f() {} + ~C() {} + private: + void pri_f() {} + protected: + void pro_f() {} + int a; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_TRUE(Cpp::IsPublicMethod(SubDecls[2])); + EXPECT_TRUE(Cpp::IsPublicMethod(SubDecls[3])); + EXPECT_TRUE(Cpp::IsPublicMethod(SubDecls[4])); + EXPECT_FALSE(Cpp::IsPublicMethod(SubDecls[6])); + EXPECT_FALSE(Cpp::IsPublicMethod(SubDecls[8])); + EXPECT_FALSE(Cpp::IsPublicMethod(SubDecls[9])); +} + +TEST(FunctionReflectionTest, IsProtectedMethod) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + public: + C() {} + void pub_f() {} + ~C() {} + private: + void pri_f() {} + protected: + void pro_f() {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_FALSE(Cpp::IsProtectedMethod(SubDecls[2])); + EXPECT_FALSE(Cpp::IsProtectedMethod(SubDecls[3])); + EXPECT_FALSE(Cpp::IsProtectedMethod(SubDecls[4])); + EXPECT_FALSE(Cpp::IsProtectedMethod(SubDecls[6])); + EXPECT_TRUE(Cpp::IsProtectedMethod(SubDecls[8])); +} + +TEST(FunctionReflectionTest, IsPrivateMethod) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + public: + C() {} + void pub_f() {} + ~C() {} + private: + void pri_f() {} + protected: + void pro_f() {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_FALSE(Cpp::IsPrivateMethod(SubDecls[2])); + EXPECT_FALSE(Cpp::IsPrivateMethod(SubDecls[3])); + EXPECT_FALSE(Cpp::IsPrivateMethod(SubDecls[4])); + EXPECT_TRUE(Cpp::IsPrivateMethod(SubDecls[6])); + EXPECT_FALSE(Cpp::IsPrivateMethod(SubDecls[8])); +} + +TEST(FunctionReflectionTest, IsConstructor) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + public: + C() {} + void pub_f() {} + ~C() {} + private: + void pri_f() {} + protected: + void pro_f() {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_TRUE(Cpp::IsConstructor(SubDecls[2])); + EXPECT_FALSE(Cpp::IsConstructor(SubDecls[3])); + EXPECT_FALSE(Cpp::IsConstructor(SubDecls[4])); + EXPECT_FALSE(Cpp::IsConstructor(SubDecls[6])); + EXPECT_FALSE(Cpp::IsConstructor(SubDecls[8])); +} + +TEST(FunctionReflectionTest, IsDestructor) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + public: + C() {} + void pub_f() {} + ~C() {} + private: + void pri_f() {} + protected: + void pro_f() {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_FALSE(Cpp::IsDestructor(SubDecls[2])); + EXPECT_FALSE(Cpp::IsDestructor(SubDecls[3])); + EXPECT_TRUE(Cpp::IsDestructor(SubDecls[4])); + EXPECT_FALSE(Cpp::IsDestructor(SubDecls[6])); + EXPECT_FALSE(Cpp::IsDestructor(SubDecls[8])); +} + +TEST(FunctionReflectionTest, IsStaticMethod) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + void f1() {} + static void f2() {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_FALSE(Cpp::IsStaticMethod(Decls[0])); + EXPECT_FALSE(Cpp::IsStaticMethod(SubDecls[1])); + EXPECT_TRUE(Cpp::IsStaticMethod(SubDecls[2])); +} + +TEST(FunctionReflectionTest, GetFunctionAddress) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + std::vector Decls, SubDecls; + std::string code = "int f1(int i) { return i * i; }"; + + GetAllTopLevelDecls(code, Decls); + + testing::internal::CaptureStdout(); + Interp->declare("#include "); + Interp->process( + "void * address = (void *) &f1; \n" + "std::cout << address; \n" + ); + + std::string output = testing::internal::GetCapturedStdout(); + std::stringstream address; + address << Cpp::GetFunctionAddress(Decls[0]); + EXPECT_EQ(address.str(), output); +} + +TEST(FunctionReflectionTest, IsVirtualMethod) { + std::vector Decls, SubDecls; + std::string code = R"( + class A { + public: + virtual void x() {} + void y() {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_EQ(Cpp::GetName(SubDecls[2]), "x"); + EXPECT_TRUE(Cpp::IsVirtualMethod(SubDecls[2])); + EXPECT_EQ(Cpp::GetName(SubDecls[3]), "y"); + EXPECT_FALSE(Cpp::IsVirtualMethod(SubDecls[3])); // y() + EXPECT_FALSE(Cpp::IsVirtualMethod(Decls[0])); +} + +TEST(FunctionReflectionTest, JitCallAdvanced) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + std::vector Decls; + std::string code = R"( + typedef struct _name { + _name() { p[0] = (void*)0x1; p[1] = (void*)0x2; p[2] = (void*)0x3; } + void* p[3]; + } name; + )"; + + GetAllTopLevelDecls(code, Decls); + auto *CtorD + = (clang::CXXConstructorDecl*)Cpp::GetDefaultConstructor(Decls[0]); + auto Ctor = Cpp::MakeFunctionCallable(CtorD); + EXPECT_TRUE((bool)Ctor) << "Failed to build a wrapper for the ctor"; + void* object = nullptr; + Ctor.Invoke(&object); + EXPECT_TRUE(object) << "Failed to call the ctor."; + // Building a wrapper with a typedef decl must be possible. + Cpp::Destruct(object, Decls[1]); +} + + +TEST(FunctionReflectionTest, GetFunctionCallWrapper) { + 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 + std::vector Decls; + std::string code = R"( + int f1(int i) { return i * i; } + )"; + + GetAllTopLevelDecls(code, Decls); + + Interp->process(R"( + #include + void f2(std::string &s) { printf("%s", s.c_str()); }; + )"); + + Interp->process(R"( + namespace NS { + int f3() { return 3; } + + extern "C" int f4() { return 4; } + } + )"); + + Cpp::JitCall FCI1 = + Cpp::MakeFunctionCallable(Decls[0]); + EXPECT_TRUE(FCI1.getKind() == Cpp::JitCall::kGenericCall); + 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"))); + EXPECT_TRUE(FCI3.getKind() == Cpp::JitCall::kGenericCall); + Cpp::JitCall FCI4 = + Cpp::MakeFunctionCallable(Cpp::GetNamed("f4", Cpp::GetNamed("NS"))); + EXPECT_TRUE(FCI4.getKind() == Cpp::JitCall::kGenericCall); + + int i = 9, ret1, ret3, ret4; + std::string s("Hello World!\n"); + void *args0[1] = { (void *) &i }; + void *args1[1] = { (void *) &s }; + + FCI1.Invoke(&ret1, {args0, /*args_size=*/1}); + EXPECT_EQ(ret1, i * i); + + testing::internal::CaptureStdout(); + FCI2.Invoke({args1, /*args_size=*/1}); + std::string output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, s); + + FCI3.Invoke(&ret3); + EXPECT_EQ(ret3, 3); + + FCI4.Invoke(&ret4); + EXPECT_EQ(ret4, 4); + + // FIXME: Do we need to support private ctors? + Interp->process(R"( + class C { + public: + C() { printf("Default Ctor Called\n"); } + ~C() { printf("Dtor Called\n"); } + }; + )"); + + clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C"); + auto *CtorD = (clang::CXXConstructorDecl*)Cpp::GetDefaultConstructor(ClassC); + auto FCI_Ctor = + Cpp::MakeFunctionCallable(CtorD); + void* object = nullptr; + testing::internal::CaptureStdout(); + FCI_Ctor.Invoke((void*)&object); + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "Default Ctor Called\n"); + EXPECT_TRUE(object != nullptr); + + auto *DtorD = (clang::CXXDestructorDecl*)Cpp::GetDestructor(ClassC); + auto FCI_Dtor = + Cpp::MakeFunctionCallable(DtorD); + testing::internal::CaptureStdout(); + FCI_Dtor.Invoke(object); + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "Dtor Called\n"); + + std::vector Decls1; + std::string code1 = R"( + template + struct S { + + static T Add(T a, T b) { return a + b; } + + }; + )"; + + GetAllTopLevelDecls(code1, Decls1); + ASTContext& C = Interp->getCI()->getASTContext(); + + std::vector argument = {C.IntTy.getAsOpaquePtr()}; + auto Instance1 = Cpp::InstantiateTemplate(Decls1[0], argument.data(), + /*type_size*/ argument.size()); + EXPECT_TRUE(isa((Decl*)Instance1)); + auto* CTSD1 = static_cast(Instance1); + auto* Add_D = Cpp::GetNamed("Add",CTSD1); + Cpp::JitCall FCI_Add = Cpp::MakeFunctionCallable(Add_D); + EXPECT_TRUE(FCI_Add.getKind() == Cpp::JitCall::kGenericCall); + + int a = 5, b = 10, result; + void* args[2] = {(void*)&a, (void*)&b}; + + FCI_Add.Invoke(&result, {args, /*args_size=*/2}); + EXPECT_EQ(result, a + b); + + // call with pointers + Interp->process(R"( + void set_5(int *out) { + *out = 5; + } + )"); + + Cpp::TCppScope_t set_5 = Cpp::GetNamed("set_5"); + EXPECT_TRUE(set_5); + + Cpp::JitCall set_5_f = Cpp::MakeFunctionCallable(set_5); + EXPECT_EQ(set_5_f.getKind(), Cpp::JitCall::kGenericCall); + + int* bp = &b; + void* set_5_args[1] = {(void*)&bp}; + set_5_f.Invoke(nullptr, {set_5_args, 1}); + EXPECT_EQ(b, 5); + +#if CLANG_VERSION_MAJOR > 16 + // typedef resolution testing + // supported for clang version >16 only + Interp->process(R"( + class TypedefToPrivateClass { + private: + class PC { + public: + int m_val = 4; + }; + + public: + typedef PC PP; + static PP f() { return PC(); } + }; + )"); + + Cpp::TCppScope_t TypedefToPrivateClass = + Cpp::GetNamed("TypedefToPrivateClass"); + EXPECT_TRUE(TypedefToPrivateClass); + + Cpp::TCppScope_t f = Cpp::GetNamed("f", TypedefToPrivateClass); + EXPECT_TRUE(f); + + Cpp::JitCall FCI_f = Cpp::MakeFunctionCallable(f); + EXPECT_EQ(FCI_f.getKind(), Cpp::JitCall::kGenericCall); + + void* res = nullptr; + FCI_f.Invoke(&res, {nullptr, 0}); + EXPECT_TRUE(res); +#endif +} + +TEST(FunctionReflectionTest, IsConstMethod) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + void f1() const {} + void f2() {} + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + Cpp::TCppFunction_t method = nullptr; // Simulate an invalid method pointer + + EXPECT_TRUE(Cpp::IsConstMethod(SubDecls[1])); // f1 + EXPECT_FALSE(Cpp::IsConstMethod(SubDecls[2])); // f2 + EXPECT_FALSE(Cpp::IsConstMethod(method)); +} + +TEST(FunctionReflectionTest, GetFunctionArgName) { + std::vector Decls, SubDecls; + std::string code = R"( + void f1(int i, double d, long l, char ch) {} + void f2(const int i, double d[], long *l, char ch[4]) {} + + template + long get_size(A, B, int i = 0) {} + + template + long get_size(int i, A a = A(), B b = B()) {} + + template + void get_size(long k, A, char ch = 'a', double l = 0.0) {} + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[0], 0), "i"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[0], 1), "d"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[0], 2), "l"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[0], 3), "ch"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[1], 0), "i"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[1], 1), "d"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[1], 2), "l"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[1], 3), "ch"); + + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[2], 0), ""); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[2], 1), ""); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[2], 2), "i"); + + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[3], 0), "i"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[3], 1), "a"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[3], 2), "b"); + + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[4], 0), "k"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[4], 1), ""); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[4], 2), "ch"); + EXPECT_EQ(Cpp::GetFunctionArgName(Decls[4], 3), "l"); +} + +TEST(FunctionReflectionTest, GetFunctionArgDefault) { + std::vector Decls, SubDecls; + std::string code = R"( + void f1(int i, double d = 4.0, const char *s = "default", char ch = 'c') {} + void f2(float i = 0.0, double d = 3.123, long m = 34126) {} + + template + long get_size(A, B, int i = 0) {} + + template + long get_size(int i, A a = A(), B b = B()) {} + + template + void get_size(long k, A, char ch = 'a', double l = 0.0) {} + + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[0], 0), ""); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[0], 1), "4."); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[0], 2), "\"default\""); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[0], 3), "\'c\'"); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[1], 0), "0."); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[1], 1), "3.123"); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[1], 2), "34126"); + + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[2], 0), ""); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[2], 1), ""); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[2], 2), "0"); + + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[3], 0), ""); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[3], 1), "A()"); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[3], 2), "B()"); + + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 0), ""); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 1), ""); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 2), "\'a\'"); + EXPECT_EQ(Cpp::GetFunctionArgDefault(Decls[4], 3), "0."); +} + +TEST(FunctionReflectionTest, Construct) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + + Cpp::CreateInterpreter(); + + Interp->declare(R"( + #include + extern "C" int printf(const char*,...); + class C { + int x; + C() { + x = 12345; + printf("Constructor Executed"); + } + }; + )"); + + testing::internal::CaptureStdout(); + 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"); + output.clear(); + + // Placement. + testing::internal::CaptureStdout(); + 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); + Cpp::Deallocate(scope, where); + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "Constructor Executed"); +} + +TEST(FunctionReflectionTest, Destruct) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + + Cpp::CreateInterpreter(); + + Interp->declare(R"( + #include + extern "C" int printf(const char*,...); + class C { + C() {} + ~C() { + printf("Destructor Executed"); + } + }; + )"); + + testing::internal::CaptureStdout(); + Cpp::TCppScope_t scope = Cpp::GetNamed("C"); + Cpp::TCppObject_t object = Cpp::Construct(scope); + Cpp::Destruct(object, scope); + std::string output = testing::internal::GetCapturedStdout(); + + EXPECT_EQ(output, "Destructor Executed"); + + output.clear(); + testing::internal::CaptureStdout(); + 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. + Cpp::Destruct(object, scope, /*withFree=*/false); + Cpp::Deallocate(scope, object); + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "Destructor Executed"); +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp new file mode 100644 index 0000000000000..a0a0d1b3a4565 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp @@ -0,0 +1,239 @@ + +#include "Utils.h" + +#include "clang/Interpreter/CppInterOp.h" + +#ifdef CPPINTEROP_USE_CLING +#include "cling/Interpreter/Interpreter.h" +#endif // CPPINTEROP_USE_CLING + +#ifndef CPPINTEROP_USE_CLING +#include "clang/Interpreter/Interpreter.h" +#endif // CPPINTEROP_USE_REPL + +#include "clang/Basic/Version.h" + +#include "clang-c/CXCppInterOp.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Path.h" +#include + +#include +#include "gtest/gtest.h" + +#include + +using ::testing::StartsWith; + +TEST(InterpreterTest, Version) { + EXPECT_THAT(Cpp::GetVersion(), StartsWith("CppInterOp version")); +} + +#ifdef NDEBUG +TEST(InterpreterTest, DISABLED_DebugFlag) { +#else +TEST(InterpreterTest, DebugFlag) { +#endif // NDEBUG + Cpp::CreateInterpreter(); + EXPECT_FALSE(Cpp::IsDebugOutputEnabled()); + std::string cerrs; + testing::internal::CaptureStderr(); + Cpp::Process("int a = 12;"); + cerrs = testing::internal::GetCapturedStderr(); + EXPECT_STREQ(cerrs.c_str(), ""); + Cpp::EnableDebugOutput(); + EXPECT_TRUE(Cpp::IsDebugOutputEnabled()); + testing::internal::CaptureStderr(); + Cpp::Process("int b = 12;"); + cerrs = testing::internal::GetCapturedStderr(); + EXPECT_STRNE(cerrs.c_str(), ""); + + Cpp::EnableDebugOutput(false); + EXPECT_FALSE(Cpp::IsDebugOutputEnabled()); + testing::internal::CaptureStderr(); + Cpp::Process("int c = 12;"); + cerrs = testing::internal::GetCapturedStderr(); + EXPECT_STREQ(cerrs.c_str(), ""); +} + +TEST(InterpreterTest, Evaluate) { +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + // EXPECT_TRUE(Cpp::Evaluate(I, "") == 0); + //EXPECT_TRUE(Cpp::Evaluate(I, "__cplusplus;") == 201402); + // Due to a deficiency in the clang-repl implementation to get the value we + // always must omit the ; + EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201402); + + bool HadError; + EXPECT_TRUE(Cpp::Evaluate("#error", &HadError) == (intptr_t)~0UL); + EXPECT_TRUE(HadError); + EXPECT_EQ(Cpp::Evaluate("int i = 11; ++i", &HadError), 12); + EXPECT_FALSE(HadError) ; +} + +TEST(InterpreterTest, Process) { +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + Cpp::CreateInterpreter(); + 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); +} + +TEST(InterpreterTest, CreateInterpreter) { + auto I = Cpp::CreateInterpreter(); + EXPECT_TRUE(I); + // Check if the default standard is c++14 + + Cpp::Declare("#if __cplusplus==201402L\n" + "int cpp14() { return 2014; }\n" + "#else\n" + "void cppUnknown() {}\n" + "#endif"); + EXPECT_TRUE(Cpp::GetNamed("cpp14")); + EXPECT_FALSE(Cpp::GetNamed("cppUnknown")); + + I = Cpp::CreateInterpreter({"-std=c++17"}); + Cpp::Declare("#if __cplusplus==201703L\n" + "int cpp17() { return 2017; }\n" + "#else\n" + "void cppUnknown() {}\n" + "#endif"); + EXPECT_TRUE(Cpp::GetNamed("cpp17")); + EXPECT_FALSE(Cpp::GetNamed("cppUnknown")); + + +#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 +} + +#ifdef LLVM_BINARY_DIR +TEST(InterpreterTest, DetectResourceDir) { +#else +TEST(InterpreterTest, DISABLED_DetectResourceDir) { +#endif // LLVM_BINARY_DIR +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + Cpp::CreateInterpreter(); + EXPECT_STRNE(Cpp::DetectResourceDir().c_str(), Cpp::GetResourceDir()); + llvm::SmallString<256> Clang(LLVM_BINARY_DIR); + llvm::sys::path::append(Clang, "bin", "clang"); + + if (!llvm::sys::fs::exists(llvm::Twine(Clang.str().str()))) + GTEST_SKIP() << "Test not run (Clang binary does not exist)"; + + std::string DetectedPath = Cpp::DetectResourceDir(Clang.str().str().c_str()); + EXPECT_STREQ(DetectedPath.c_str(), Cpp::GetResourceDir()); +} + +TEST(InterpreterTest, DetectSystemCompilerIncludePaths) { +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + std::vector includes; + Cpp::DetectSystemCompilerIncludePaths(includes); + EXPECT_FALSE(includes.empty()); +} + +TEST(InterpreterTest, GetIncludePaths) { + std::vector includes; + Cpp::GetIncludePaths(includes); + EXPECT_FALSE(includes.empty()); + + size_t len = includes.size(); + includes.clear(); + Cpp::GetIncludePaths(includes, true, false); + EXPECT_FALSE(includes.empty()); + EXPECT_TRUE(includes.size() >= len); + + len = includes.size(); + includes.clear(); + Cpp::GetIncludePaths(includes, true, true); + EXPECT_FALSE(includes.empty()); + EXPECT_TRUE(includes.size() >= len); +} + +TEST(InterpreterTest, CodeCompletion) { +#if CLANG_VERSION_MAJOR >= 18 || defined(CPPINTEROP_USE_CLING) + Cpp::CreateInterpreter(); + std::vector cc; + 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 + // there, depending on the implementation, but these two + // are required to say that the test is working. + size_t cnt = 0; + for (auto& r : cc) + if (r == "float" || r == "foo") + cnt++; + EXPECT_EQ(2U, cnt); // float and foo +#else + GTEST_SKIP(); +#endif +} + +TEST(InterpreterTest, ExternalInterpreterTest) { + +if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + +#ifndef CPPINTEROP_USE_CLING + llvm::ExitOnError ExitOnErr; + clang::IncrementalCompilerBuilder CB; + CB.SetCompilerArgs({"-std=c++20"}); + + // Create the incremental compiler instance. + std::unique_ptr CI; + CI = ExitOnErr(CB.CreateCpp()); + + // Create the interpreter instance. + std::unique_ptr I = + ExitOnErr(clang::Interpreter::create(std::move(CI))); + auto ExtInterp = I.get(); +#endif // CPPINTEROP_USE_REPL + +#ifdef CPPINTEROP_USE_CLING + std::string MainExecutableName = sys::fs::getMainExecutable(nullptr, nullptr); + std::string ResourceDir = compat::MakeResourceDir(LLVM_BINARY_DIR); + std::vector ClingArgv = {"-resource-dir", ResourceDir.c_str(), + "-std=c++14"}; + ClingArgv.insert(ClingArgv.begin(), MainExecutableName.c_str()); + auto *ExtInterp = new compat::Interpreter(ClingArgv.size(), &ClingArgv[0]); +#endif + + EXPECT_NE(ExtInterp, nullptr); + +#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST +#ifndef _WIN32 // Windows seems to fail to die... + EXPECT_DEATH(Cpp::UseExternalInterpreter(ExtInterp), "sInterpreter already in use!"); +#endif // _WIN32 +#endif + EXPECT_TRUE(Cpp::GetInterpreter()) << "External Interpreter not set"; + +#ifndef CPPINTEROP_USE_CLING + I.release(); +#endif + +#ifdef CPPINTEROP_USE_CLING + delete ExtInterp; +#endif +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp new file mode 100644 index 0000000000000..310f87efe635a --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp @@ -0,0 +1,69 @@ +#include "Utils.h" + +#include "clang/Interpreter/CppInterOp.h" + +#include "gtest/gtest.h" + +using namespace TestUtils; + +static int printf_jit(const char* format, ...) { + llvm::errs() << "printf_jit called!\n"; + return 0; +} + +TEST(JitTest, InsertOrReplaceJitSymbol) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + std::vector Decls; + std::string code = R"( + extern "C" int printf(const char*,...); + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_FALSE(Cpp::InsertOrReplaceJitSymbol("printf", (uint64_t)&printf_jit)); + + testing::internal::CaptureStderr(); + Cpp::Process("printf(\"Blah\");"); + std::string cerrs = testing::internal::GetCapturedStderr(); + EXPECT_STREQ(cerrs.c_str(), "printf_jit called!\n"); + + EXPECT_TRUE( + Cpp::InsertOrReplaceJitSymbol("non_existent", (uint64_t)&printf_jit)); + EXPECT_TRUE(Cpp::InsertOrReplaceJitSymbol("non_existent", 0)); +} + +TEST(Streams, StreamRedirect) { + // printf and etc are fine here. + // NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) + Cpp::BeginStdStreamCapture(Cpp::kStdOut); + Cpp::BeginStdStreamCapture(Cpp::kStdErr); + printf("StdOut is captured\n"); + fprintf(stderr, "StdErr is captured\n"); + std::cout << "Out captured" + << "\n"; + std::cerr << "Err captured" + << "\n"; + std::string CapturedStringErr = Cpp::EndStdStreamCapture(); + std::string CapturedStringOut = Cpp::EndStdStreamCapture(); + EXPECT_STREQ(CapturedStringOut.c_str(), "StdOut is captured\nOut captured\n"); + EXPECT_STREQ(CapturedStringErr.c_str(), "StdErr is captured\nErr captured\n"); + + testing::internal::CaptureStdout(); + std::cout << "Out" + << "\n"; + printf("StdOut\n"); + std::string outs = testing::internal::GetCapturedStdout(); + EXPECT_STREQ(outs.c_str(), "Out\nStdOut\n"); + + testing::internal::CaptureStderr(); + std::cerr << "Err" + << "\n"; + fprintf(stderr, "StdErr\n"); + std::string cerrs = testing::internal::GetCapturedStderr(); + EXPECT_STREQ(cerrs.c_str(), "Err\nStdErr\n"); + // NOLINTEND(cppcoreguidelines-pro-type-vararg) +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp new file mode 100644 index 0000000000000..b3edf5b00d8a1 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -0,0 +1,1100 @@ +#include "Utils.h" + +#include "clang/Interpreter/CppInterOp.h" +#include "clang-c/CXCppInterOp.h" + +#include "clang/AST/ASTContext.h" +#include "clang/Interpreter/CppInterOp.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/Sema.h" + +#include "clang/AST/ASTDumper.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/GlobalDecl.h" + +#include "llvm/Support/Valgrind.h" + +#include "gtest/gtest.h" + +#include + +using namespace TestUtils; +using namespace llvm; +using namespace clang; + +TEST(ScopeReflectionTest, Demangle) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + + std::string code = R"( + int add(int x, int y) { return x + y; } + int add(double x, double y) { return x + y; } + )"; + + std::vector Decls; + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Decls.size(), 2); + + auto Add_int = clang::GlobalDecl(static_cast(Decls[0])); + auto Add_double = clang::GlobalDecl(static_cast(Decls[1])); + + std::string mangled_add_int; + std::string mangled_add_double; + compat::maybeMangleDeclName(Add_int, mangled_add_int); + compat::maybeMangleDeclName(Add_double, mangled_add_double); + + std::string demangled_add_int = Cpp::Demangle(mangled_add_int); + std::string demangled_add_double = Cpp::Demangle(mangled_add_double); + + EXPECT_NE(demangled_add_int.find(Cpp::GetQualifiedCompleteName(Decls[0])), + std::string::npos); + EXPECT_NE(demangled_add_double.find(Cpp::GetQualifiedCompleteName(Decls[1])), + std::string::npos); +} + +TEST(ScopeReflectionTest, IsAggregate) { + std::vector Decls; + std::string code = R"( + char cv[4] = {}; + int x[] = {}; + union u { int a; const char* b; }; + struct S { + int x; + struct Foo { + int i; + int j; + int a[3]; + } b; + }; + int y = 10; // Not an aggregate type + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_TRUE(Cpp::IsAggregate(Decls[0])); + EXPECT_TRUE(Cpp::IsAggregate(Decls[1])); + EXPECT_TRUE(Cpp::IsAggregate(Decls[2])); + EXPECT_TRUE(Cpp::IsAggregate(Decls[3])); + EXPECT_FALSE(Cpp::IsAggregate(Decls[4])); +} + +// Check that the CharInfo table has been constructed reasonably. +TEST(ScopeReflectionTest, IsNamespace) { + std::vector Decls; + GetAllTopLevelDecls("namespace N {} class C{}; int I;", Decls); + EXPECT_TRUE(Cpp::IsNamespace(Decls[0])); + EXPECT_FALSE(Cpp::IsNamespace(Decls[1])); + EXPECT_FALSE(Cpp::IsNamespace(Decls[2])); +} + +TEST(ScopeReflectionTest, IsClass) { + std::vector Decls; + GetAllTopLevelDecls("namespace N {} class C{}; int I;", Decls); + EXPECT_FALSE(Cpp::IsClass(Decls[0])); + EXPECT_TRUE(Cpp::IsClass(Decls[1])); + EXPECT_FALSE(Cpp::IsClass(Decls[2])); +} + +TEST(ScopeReflectionTest, IsClassPolymorphic) { + std::vector Decls; + GetAllTopLevelDecls(R"( + namespace N {} + + class C{}; + + class C2 { + public: + virtual ~C2() {} + }; + + int I; + )", + Decls); + + EXPECT_FALSE(Cpp::IsClassPolymorphic(Decls[0])); + EXPECT_FALSE(Cpp::IsClassPolymorphic(Decls[1])); + EXPECT_TRUE(Cpp::IsClassPolymorphic(Decls[2])); + EXPECT_FALSE(Cpp::IsClassPolymorphic(Decls[3])); +} + +TEST(ScopeReflectionTest, IsComplete) { + std::vector Decls; + std::string code = R"( + namespace N {} + class C{}; + int I; + struct S; + enum E : int; + union U{}; + template struct TemplatedS{ T x; }; + TemplatedS templateF() { return {}; }; + )"; + GetAllTopLevelDecls(code, + Decls); + EXPECT_TRUE(Cpp::IsComplete(Decls[0])); + EXPECT_TRUE(Cpp::IsComplete(Decls[1])); + EXPECT_TRUE(Cpp::IsComplete(Decls[2])); + EXPECT_FALSE(Cpp::IsComplete(Decls[3])); + EXPECT_FALSE(Cpp::IsComplete(Decls[4])); + EXPECT_TRUE(Cpp::IsComplete(Decls[5])); + Cpp::TCppType_t retTy = Cpp::GetFunctionReturnType(Decls[7]); + EXPECT_TRUE(Cpp::IsComplete(Cpp::GetScopeFromType(retTy))); + EXPECT_FALSE(Cpp::IsComplete(nullptr)); +} + +TEST(ScopeReflectionTest, SizeOf) { + std::vector Decls; + std::string code = R"(namespace N {} class C{}; int I; struct S; + enum E : int; union U{}; class Size4{int i;}; + struct Size16 {short a; double b;}; + )"; + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Cpp::SizeOf(Decls[0]), (size_t)0); + EXPECT_EQ(Cpp::SizeOf(Decls[1]), (size_t)1); + EXPECT_EQ(Cpp::SizeOf(Decls[2]), (size_t)0); + EXPECT_EQ(Cpp::SizeOf(Decls[3]), (size_t)0); + EXPECT_EQ(Cpp::SizeOf(Decls[4]), (size_t)0); + EXPECT_EQ(Cpp::SizeOf(Decls[5]), (size_t)1); + EXPECT_EQ(Cpp::SizeOf(Decls[6]), (size_t)4); + EXPECT_EQ(Cpp::SizeOf(Decls[7]), (size_t)16); +} + + +TEST(ScopeReflectionTest, IsBuiltin) { + // static std::set g_builtins = + // {"bool", "char", "signed char", "unsigned char", "wchar_t", "short", "unsigned short", + // "int", "unsigned int", "long", "unsigned long", "long long", "unsigned long long", + // "float", "double", "long double", "void"} + + Cpp::CreateInterpreter(); + ASTContext &C = Interp->getCI()->getASTContext(); + EXPECT_TRUE(Cpp::IsBuiltin(C.BoolTy.getAsOpaquePtr())); + EXPECT_TRUE(Cpp::IsBuiltin(C.CharTy.getAsOpaquePtr())); + EXPECT_TRUE(Cpp::IsBuiltin(C.SignedCharTy.getAsOpaquePtr())); + EXPECT_TRUE(Cpp::IsBuiltin(C.VoidTy.getAsOpaquePtr())); + // ... + + // complex + EXPECT_TRUE(Cpp::IsBuiltin(C.getComplexType(C.FloatTy).getAsOpaquePtr())); + EXPECT_TRUE(Cpp::IsBuiltin(C.getComplexType(C.DoubleTy).getAsOpaquePtr())); + EXPECT_TRUE(Cpp::IsBuiltin(C.getComplexType(C.LongDoubleTy).getAsOpaquePtr())); + EXPECT_TRUE(Cpp::IsBuiltin(C.getComplexType(C.Float128Ty).getAsOpaquePtr())); + + // std::complex + std::vector Decls; + Interp->declare("#include "); + Sema &S = Interp->getCI()->getSema(); + auto lookup = S.getStdNamespace()->lookup(&C.Idents.get("complex")); + auto *CTD = cast(lookup.front()); + for (ClassTemplateSpecializationDecl *CTSD : CTD->specializations()) + EXPECT_TRUE(Cpp::IsBuiltin(C.getTypeDeclType(CTSD).getAsOpaquePtr())); +} + +TEST(ScopeReflectionTest, IsTemplate) { + std::vector Decls; + std::string code = R"(template + class A{}; + + class C{ + template + int func(T t) { + return 0; + } + }; + + template + T f(T t) { + return t; + } + + void g() {} )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_TRUE(Cpp::IsTemplate(Decls[0])); + EXPECT_FALSE(Cpp::IsTemplate(Decls[1])); + EXPECT_TRUE(Cpp::IsTemplate(Decls[2])); + EXPECT_FALSE(Cpp::IsTemplate(Decls[3])); +} + +TEST(ScopeReflectionTest, IsTemplateSpecialization) { + std::vector Decls; + std::string code = R"( + template + class A{}; + + A a; + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_FALSE(Cpp::IsTemplateSpecialization(Decls[0])); + EXPECT_FALSE(Cpp::IsTemplateSpecialization(Decls[1])); + EXPECT_TRUE(Cpp::IsTemplateSpecialization( + Cpp::GetScopeFromType(Cpp::GetVariableType(Decls[1])))); +} + +TEST(ScopeReflectionTest, IsTypedefed) { + std::vector Decls; + std::string code = R"( + typedef int I; + using D = double; + class C {}; + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_TRUE(Cpp::IsTypedefed(Decls[0])); + EXPECT_TRUE(Cpp::IsTypedefed(Decls[1])); + EXPECT_FALSE(Cpp::IsTypedefed(Decls[2])); +} + +TEST(ScopeReflectionTest, IsAbstract) { + std::vector Decls; + std::string code = R"( + class A {}; + + class B { + virtual int f() = 0; + }; + + int sum(int a, int b) { + return a+b; + } + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_FALSE(Cpp::IsAbstract(Decls[0])); + EXPECT_TRUE(Cpp::IsAbstract(Decls[1])); + EXPECT_FALSE(Cpp::IsAbstract(Decls[2])); +} + +TEST(ScopeReflectionTest, IsVariable) { + std::vector Decls; + std::string code = R"( + int i; + + class C { + public: + int a; + static int b; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_TRUE(Cpp::IsVariable(Decls[0])); + EXPECT_FALSE(Cpp::IsVariable(Decls[1])); + + std::vector SubDecls; + GetAllSubDecls(Decls[1], SubDecls); + EXPECT_FALSE(Cpp::IsVariable(SubDecls[0])); + EXPECT_FALSE(Cpp::IsVariable(SubDecls[1])); + EXPECT_FALSE(Cpp::IsVariable(SubDecls[2])); + EXPECT_TRUE(Cpp::IsVariable(SubDecls[3])); +} + +TEST(ScopeReflectionTest, GetName) { + std::vector Decls; + std::string code = R"(namespace N {} class C{}; int I; struct S; + enum E : int; union U{}; class Size4{int i;}; + struct Size16 {short a; double b;}; + )"; + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Cpp::GetName(Decls[0]), "N"); + EXPECT_EQ(Cpp::GetName(Decls[1]), "C"); + EXPECT_EQ(Cpp::GetName(Decls[2]), "I"); + EXPECT_EQ(Cpp::GetName(Decls[3]), "S"); + EXPECT_EQ(Cpp::GetName(Decls[4]), "E"); + EXPECT_EQ(Cpp::GetName(Decls[5]), "U"); + EXPECT_EQ(Cpp::GetName(Decls[6]), "Size4"); + EXPECT_EQ(Cpp::GetName(Decls[7]), "Size16"); + EXPECT_EQ(Cpp::GetName(nullptr), ""); +} + +TEST(ScopeReflectionTest, GetCompleteName) { + std::vector Decls; + std::string code = R"(namespace N {} + class C{}; + int I; + struct S; + enum E : int; + union U{}; + class Size4{int i;}; + struct Size16 {short a; double b;}; + + template + class A {}; + A a; + + enum { enum1 }; + )"; + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetCompleteName(Decls[0]), "N"); + EXPECT_EQ(Cpp::GetCompleteName(Decls[1]), "C"); + EXPECT_EQ(Cpp::GetCompleteName(Decls[2]), "I"); + EXPECT_EQ(Cpp::GetCompleteName(Decls[3]), "S"); + EXPECT_EQ(Cpp::GetCompleteName(Decls[4]), "E"); + EXPECT_EQ(Cpp::GetCompleteName(Decls[5]), "U"); + EXPECT_EQ(Cpp::GetCompleteName(Decls[6]), "Size4"); + EXPECT_EQ(Cpp::GetCompleteName(Decls[7]), "Size16"); + EXPECT_EQ(Cpp::GetCompleteName(Decls[8]), "A"); + EXPECT_EQ(Cpp::GetCompleteName(Cpp::GetScopeFromType( + Cpp::GetVariableType( + Decls[9]))), "A"); + EXPECT_EQ(Cpp::GetCompleteName(Decls[10]), "(unnamed)"); + EXPECT_EQ(Cpp::GetCompleteName(nullptr), ""); +} + +TEST(ScopeReflectionTest, GetQualifiedName) { + std::vector Decls; + std::string code = R"(namespace N { + class C { + int i; + enum E { A, B }; + }; + } + )"; + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], Decls); + GetAllSubDecls(Decls[1], Decls); + + EXPECT_EQ(Cpp::GetQualifiedName(0), ""); + EXPECT_EQ(Cpp::GetQualifiedName(Decls[0]), "N"); + EXPECT_EQ(Cpp::GetQualifiedName(Decls[1]), "N::C"); + EXPECT_EQ(Cpp::GetQualifiedName(Decls[3]), "N::C::i"); + EXPECT_EQ(Cpp::GetQualifiedName(Decls[4]), "N::C::E"); +} + +TEST(ScopeReflectionTest, GetQualifiedCompleteName) { + std::vector Decls; + std::string code = R"(namespace N { + class C { + int i; + enum E { A, B }; + }; + template + class A {}; + A a; + } + )"; + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], Decls); + GetAllSubDecls(Decls[1], Decls); + + EXPECT_EQ(Cpp::GetQualifiedCompleteName(0), ""); + EXPECT_EQ(Cpp::GetQualifiedCompleteName(Decls[0]), "N"); + EXPECT_EQ(Cpp::GetQualifiedCompleteName(Decls[1]), "N::C"); + EXPECT_EQ(Cpp::GetQualifiedCompleteName(Decls[2]), "N::A"); + EXPECT_EQ(Cpp::GetQualifiedCompleteName(Cpp::GetScopeFromType(Cpp::GetVariableType(Decls[3]))), "N::A"); + EXPECT_EQ(Cpp::GetQualifiedCompleteName(Decls[5]), "N::C::i"); + EXPECT_EQ(Cpp::GetQualifiedCompleteName(Decls[6]), "N::C::E"); +} + +TEST(ScopeReflectionTest, GetUsingNamespaces) { + std::vector Decls, Decls1; + std::string code = R"( + namespace abc { + + class C {}; + + } + using namespace std; + using namespace abc; + + using I = int; + )"; + + GetAllTopLevelDecls(code, Decls); + std::vector usingNamespaces; + usingNamespaces = Cpp::GetUsingNamespaces( + Decls[0]->getASTContext().getTranslationUnitDecl()); + + //EXPECT_EQ(Cpp::GetName(usingNamespaces[0]), "runtime"); + EXPECT_EQ(Cpp::GetName(usingNamespaces[usingNamespaces.size()-2]), "std"); + EXPECT_EQ(Cpp::GetName(usingNamespaces[usingNamespaces.size()-1]), "abc"); + + std::string code1 = R"( + int x; + )"; + + GetAllTopLevelDecls(code1, Decls1); + std::vector usingNamespaces1; + usingNamespaces1 = Cpp::GetUsingNamespaces(Decls1[0]); + EXPECT_EQ(usingNamespaces1.size(), 0); +} + +TEST(ScopeReflectionTest, GetGlobalScope) { + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetGlobalScope()), ""); + EXPECT_EQ(Cpp::GetName(Cpp::GetGlobalScope()), ""); +} + +TEST(ScopeReflectionTest, GetUnderlyingScope) { + std::vector Decls; + std::string code = R"( + namespace N { + class C {}; + } + + typedef N::C NC; + typedef int INT; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetUnderlyingScope(Decls[0])), "N"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetUnderlyingScope(Decls[1])), "N::C"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetUnderlyingScope(Decls[2])), "INT"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetUnderlyingScope(nullptr)), ""); +} + +TEST(ScopeReflectionTest, GetScope) { + std::string code = R"(namespace N { + class C { + int i; + enum E { A, B }; + }; + } + + typedef N::C T; + )"; + + Cpp::CreateInterpreter(); + Interp->declare(code); + Cpp::TCppScope_t tu = Cpp::GetScope("", 0); + Cpp::TCppScope_t ns_N = Cpp::GetScope("N", 0); + Cpp::TCppScope_t cl_C = Cpp::GetScope("C", ns_N); + Cpp::TCppScope_t td_T = Cpp::GetScope("T", 0); + Cpp::TCppScope_t non_existent = Cpp::GetScope("sum", 0); + + EXPECT_EQ(Cpp::GetQualifiedName(tu), ""); + EXPECT_EQ(Cpp::GetQualifiedName(ns_N), "N"); + EXPECT_EQ(Cpp::GetQualifiedName(cl_C), "N::C"); + EXPECT_EQ(Cpp::GetQualifiedName(td_T), "T"); + EXPECT_EQ(Cpp::GetQualifiedName(non_existent), ""); +} + +TEST(ScopeReflectionTest, GetScopefromCompleteName) { + std::string code = R"(namespace N1 { + namespace N2 { + class C { + struct S {}; + }; + } + } + )"; + + Cpp::CreateInterpreter(); + + Interp->declare(code); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromCompleteName("N1")), "N1"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromCompleteName("N1::N2")), "N1::N2"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromCompleteName("N1::N2::C")), "N1::N2::C"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromCompleteName("N1::N2::C::S")), "N1::N2::C::S"); +} + +TEST(ScopeReflectionTest, GetNamed) { + std::string code = R"(namespace N1 { + namespace N2 { + class C { + int i; + enum E { A, B }; + struct S {}; + }; + } + } + )"; + Cpp::CreateInterpreter(); + + Interp->declare(code); + Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1", nullptr); + Cpp::TCppScope_t ns_N2 = Cpp::GetNamed("N2", ns_N1); + Cpp::TCppScope_t cl_C = Cpp::GetNamed("C", ns_N2); + Cpp::TCppScope_t en_E = Cpp::GetNamed("E", cl_C); + EXPECT_EQ(Cpp::GetQualifiedName(ns_N1), "N1"); + EXPECT_EQ(Cpp::GetQualifiedName(ns_N2), "N1::N2"); + EXPECT_EQ(Cpp::GetQualifiedName(cl_C), "N1::N2::C"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetNamed("i", cl_C)), "N1::N2::C::i"); + EXPECT_EQ(Cpp::GetQualifiedName(en_E), "N1::N2::C::E"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetNamed("A", en_E)), "N1::N2::C::A"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetNamed("B", en_E)), "N1::N2::C::B"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetNamed("A", cl_C)), "N1::N2::C::A"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetNamed("B", cl_C)), "N1::N2::C::B"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetNamed("S", cl_C)), "N1::N2::C::S"); + + Interp->process("#include "); + Cpp::TCppScope_t std_ns = Cpp::GetNamed("std", nullptr); + Cpp::TCppScope_t std_string_class = Cpp::GetNamed("string", std_ns); + Cpp::TCppScope_t std_string_npos_var = Cpp::GetNamed("npos", std_string_class); + EXPECT_EQ(Cpp::GetQualifiedName(std_ns), "std"); + EXPECT_EQ(Cpp::GetQualifiedName(std_string_class), "std::string"); + EXPECT_EQ(Cpp::GetQualifiedName(std_string_npos_var), "std::basic_string::npos"); +} + +TEST(ScopeReflectionTest, GetParentScope) { + std::string code = R"(namespace N1 { + namespace N2 { + class C { + int i; + enum E { A, B }; + struct S {}; + }; + } + } + )"; + + Cpp::CreateInterpreter(); + + Interp->declare(code); + 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); + Cpp::TCppScope_t en_E = Cpp::GetNamed("E", cl_C); + Cpp::TCppScope_t en_A = Cpp::GetNamed("A", cl_C); + Cpp::TCppScope_t en_B = Cpp::GetNamed("B", cl_C); + + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetParentScope(ns_N1)), ""); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetParentScope(ns_N2)), "N1"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetParentScope(cl_C)), "N1::N2"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetParentScope(int_i)), "N1::N2::C"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetParentScope(en_E)), "N1::N2::C"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetParentScope(en_A)), "N1::N2::C::E"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetParentScope(en_B)), "N1::N2::C::E"); +} + +TEST(ScopeReflectionTest, GetScopeFromType) { + std::vector Decls; + std::string code = R"( + namespace N { + class C {}; + struct S {}; + typedef C T; + enum E {}; + } + + N::C c; + + N::S s; + + int i; + + N::T t; + + N::E e; + + N::C myFunc(); + )"; + + GetAllTopLevelDecls(code, Decls); + QualType QT1 = llvm::dyn_cast(Decls[1])->getType(); + QualType QT2 = llvm::dyn_cast(Decls[2])->getType(); + QualType QT3 = llvm::dyn_cast(Decls[3])->getType(); + QualType QT4 = llvm::dyn_cast(Decls[4])->getType(); + QualType QT5 = llvm::dyn_cast(Decls[5])->getType(); + QualType QT6 = llvm::dyn_cast(Decls[6])->getReturnType(); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromType(QT1.getAsOpaquePtr())), + "N::C"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromType(QT2.getAsOpaquePtr())), + "N::S"); + EXPECT_EQ(Cpp::GetScopeFromType(QT3.getAsOpaquePtr()), + (Cpp::TCppScope_t) 0); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromType(QT4.getAsOpaquePtr())), + "N::C"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromType(QT5.getAsOpaquePtr())), + "N::E"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetScopeFromType(QT6.getAsOpaquePtr())), + "N::C"); +} + +TEST(ScopeReflectionTest, GetNumBases) { + std::vector Decls; + std::string code = R"( + class A {}; + class B : virtual public A {}; + class C : virtual public A {}; + class D : public B, public C {}; + class E : public D {}; + class NoDef; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetNumBases(Decls[0]), 0); + EXPECT_EQ(Cpp::GetNumBases(Decls[1]), 1); + EXPECT_EQ(Cpp::GetNumBases(Decls[2]), 1); + EXPECT_EQ(Cpp::GetNumBases(Decls[3]), 2); + EXPECT_EQ(Cpp::GetNumBases(Decls[4]), 1); + // FIXME: Perhaps we should have a special number or error out as this + // operation is not well defined if a class has no definition. + EXPECT_EQ(Cpp::GetNumBases(Decls[5]), 0); +} + +TEST(ScopeReflectionTest, GetBaseClass) { + std::vector Decls; + std::string code = R"( + class A {}; + class B : virtual public A {}; + class C : virtual public A {}; + class D : public B, public C {}; + class E : public D {}; + + template + class TC1 {}; + + template + class TC2 : TC1 {}; + + template + class TC3 :public A {}; + + TC2 var; + TC3 var1; + int a; + )"; + + GetAllTopLevelDecls(code, Decls); + + auto get_base_class_name = [](Decl *D, int i) { + return Cpp::GetQualifiedName(Cpp::GetBaseClass(D, i)); + }; + + EXPECT_EQ(get_base_class_name(Decls[1], 0), "A"); + EXPECT_EQ(get_base_class_name(Decls[2], 0), "A"); + EXPECT_EQ(get_base_class_name(Decls[3], 0), "B"); + EXPECT_EQ(get_base_class_name(Decls[3], 1), "C"); + EXPECT_EQ(get_base_class_name(Decls[4], 0), "D"); + EXPECT_EQ(get_base_class_name(Decls[10], 0), ""); + + auto *VD = Cpp::GetNamed("var"); + auto *VT = Cpp::GetVariableType(VD); + auto *TC2_A_Decl = Cpp::GetScopeFromType(VT); + auto *TC1_A_Decl = Cpp::GetBaseClass(TC2_A_Decl, 0); + EXPECT_EQ(Cpp::GetCompleteName(TC1_A_Decl), "TC1"); + + auto* VD1 = Cpp::GetNamed("var1"); + auto* VT1 = Cpp::GetVariableType(VD1); + auto* TC3_A_Decl = Cpp::GetScopeFromType(VT1); + auto* A_class = Cpp::GetBaseClass(TC3_A_Decl, 0); + EXPECT_EQ(Cpp::GetCompleteName(A_class), "A"); +} + +TEST(ScopeReflectionTest, IsSubclass) { + std::vector Decls; + std::string code = R"( + class A {}; + class B : virtual public A {}; + class C : virtual public A {}; + class D : public B, public C {}; + class E : public D {}; + void check(); + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_TRUE(Cpp::IsSubclass(Decls[0], Decls[0])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[1], Decls[0])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[2], Decls[0])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[3], Decls[0])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[4], Decls[0])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[0], Decls[1])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[1], Decls[1])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[2], Decls[1])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[3], Decls[1])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[4], Decls[1])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[0], Decls[2])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[1], Decls[2])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[2], Decls[2])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[3], Decls[2])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[4], Decls[2])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[0], Decls[3])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[1], Decls[3])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[2], Decls[3])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[3], Decls[3])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[4], Decls[3])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[0], Decls[4])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[1], Decls[4])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[2], Decls[4])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[3], Decls[4])); + EXPECT_TRUE(Cpp::IsSubclass(Decls[4], Decls[4])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[4], Decls[5])); + EXPECT_FALSE(Cpp::IsSubclass(Decls[4], nullptr)); +} + +TEST(ScopeReflectionTest, GetBaseClassOffset) { + std::vector Decls; +#define Stringify(s) Stringifyx(s) +#define Stringifyx(...) #__VA_ARGS__ +#define CODE \ + struct A { int m_a; }; \ + struct B { int m_b; }; \ + struct C : virtual A, virtual B { int m_c; }; \ + struct D : virtual A, virtual B, public C { int m_d; }; \ + struct E : public A, public B { int m_e; }; \ + struct F : public A { int m_f; }; \ + struct G : public F { int m_g; }; + +CODE; + + GetAllTopLevelDecls(Stringify(CODE), Decls); +#undef Stringifyx +#undef Stringify +#undef CODE + + auto *c = new C(); + EXPECT_EQ(Cpp::GetBaseClassOffset(Decls[2], Decls[0]), (char *)(A*)c - (char *)c); + EXPECT_EQ(Cpp::GetBaseClassOffset(Decls[2], Decls[1]), (char *)(B*)c - (char *)c); + + auto *d = new D(); + EXPECT_EQ(Cpp::GetBaseClassOffset(Decls[3], Decls[0]), (char *)(A*)d - (char *)d); + EXPECT_EQ(Cpp::GetBaseClassOffset(Decls[3], Decls[1]), (char *)(B*)d - (char *)d); + EXPECT_EQ(Cpp::GetBaseClassOffset(Decls[3], Decls[2]), (char *)(C*)d - (char *)d); + + auto *e = new E(); + EXPECT_EQ(Cpp::GetBaseClassOffset(Decls[4], Decls[0]), (char *)(A*)e - (char *)e); + EXPECT_EQ(Cpp::GetBaseClassOffset(Decls[4], Decls[1]), (char *)(B*)e - (char *)e); + + auto *g = new G(); + EXPECT_EQ(Cpp::GetBaseClassOffset(Decls[6], Decls[0]), (char *)(A*)g - (char *)g); +} + +TEST(ScopeReflectionTest, GetAllCppNames) { + std::vector Decls; + std::string code = R"( + class A { int a; }; + class B { int b; }; + class C : public A, public B { int c; }; + class D : public A, public B, public C { int d; }; + namespace N { + class A { int a; }; + class B { int b; }; + class C : public A, public B { int c; }; + class D : public A, public B, public C { int d; }; + } + void myfunc() { + int a; + } + )"; + + GetAllTopLevelDecls(code, Decls); + + auto test_get_all_cpp_names = + [](Decl* D, const std::vector& truth_names) { + std::set names; + Cpp::GetAllCppNames(D, names); + EXPECT_EQ(names.size(), truth_names.size()); + + for (unsigned i = 0; i < truth_names.size() && i < names.size(); i++) { + EXPECT_TRUE(names.find(truth_names[i]) != names.end()); + } + }; + + test_get_all_cpp_names(Decls[0], {"a"}); + test_get_all_cpp_names(Decls[1], {"b"}); + test_get_all_cpp_names(Decls[2], {"c"}); + test_get_all_cpp_names(Decls[3], {"d"}); + test_get_all_cpp_names(Decls[4], {"A", "B", "C", "D"}); + test_get_all_cpp_names(Decls[5], {}); +} + +TEST(ScopeReflectionTest, InstantiateNNTPClassTemplate) { + std::vector Decls; + std::string code = R"( + template + struct Factorial { + enum { value = N * Factorial::value }; + }; + + template <> + struct Factorial<0> { + enum { value = 1 }; + };)"; + + GetAllTopLevelDecls(code, Decls); + + 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())); + + // 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); +} + +TEST(ScopeReflectionTest, InstantiateVarTemplate) { + std::vector Decls; + std::string code = R"( +template constexpr T pi = T(3.1415926535897932385L); +)"; + + GetAllTopLevelDecls(code, Decls); + ASTContext& C = Interp->getCI()->getASTContext(); + + std::vector args1 = {C.IntTy.getAsOpaquePtr()}; + auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(), + /*type_size*/ args1.size()); + EXPECT_TRUE(isa((Decl*)Instance1)); + auto* VD = cast((Decl*)Instance1); + VarTemplateDecl* VDTD1 = VD->getSpecializedTemplate(); + EXPECT_TRUE(VDTD1->isThisDeclarationADefinition()); +#if CLANG_VERSION_MAJOR > 13 +#if CLANG_VERSION_MAJOR <= 18 + TemplateArgument TA1 = (*VD->getTemplateArgsInfo())[0].getArgument(); +#else + TemplateArgument TA1 = (*VD->getTemplateArgsAsWritten())[0].getArgument(); +#endif // CLANG_VERSION_MAJOR +#else + TemplateArgument TA1 = VD->getTemplateArgsInfo()[0].getArgument(); +#endif // CLANG_VERSION_MAJOR + EXPECT_TRUE(TA1.getAsType()->isIntegerType()); +} + +TEST(ScopeReflectionTest, InstantiateFunctionTemplate) { + std::vector Decls; + std::string code = R"( +template T TrivialFnTemplate() { return T(); } +)"; + + GetAllTopLevelDecls(code, Decls); + ASTContext& C = Interp->getCI()->getASTContext(); + + std::vector args1 = {C.IntTy.getAsOpaquePtr()}; + auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(), + /*type_size*/ args1.size()); + EXPECT_TRUE(isa((Decl*)Instance1)); + FunctionDecl* FD = cast((Decl*)Instance1); + FunctionDecl* FnTD1 = FD->getTemplateInstantiationPattern(); + EXPECT_TRUE(FnTD1->isThisDeclarationADefinition()); + TemplateArgument TA1 = FD->getTemplateSpecializationArgs()->get(0); + EXPECT_TRUE(TA1.getAsType()->isIntegerType()); +} + +TEST(ScopeReflectionTest, InstantiateTemplateFunctionFromString) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + Cpp::CreateInterpreter(); + std::string code = R"(#include )"; + Interp->process(code); + const char* str = "std::make_unique"; + auto* Instance1 = (Decl*)Cpp::InstantiateTemplateFunctionFromString(str); + EXPECT_TRUE(Instance1); +} + +TEST(ScopeReflectionTest, InstantiateTemplate) { + std::vector Decls; + std::string code = R"( + template + class AllDefault { + public: + AllDefault(int val) : m_t(val) {} + template + int do_stuff() { return m_t+aap+noot; } + + public: + T m_t; + }; + + template + class C0 { + public: + C0(T val) : m_t(val) {} + template + T do_stuff() { return m_t+aap+noot; } + + public: + T m_t; + }; + + template> + class C1 { + public: + C1(const R & val = R()) : m_t(val.m_t) {} + template + T do_stuff() { return m_t+aap+noot; } + + public: + T m_t; + }; + + template + class C2{}; + )"; + + GetAllTopLevelDecls(code, Decls); + ASTContext &C = Interp->getCI()->getASTContext(); + + std::vector args1 = {C.IntTy.getAsOpaquePtr()}; + auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(), + /*type_size*/ args1.size()); + EXPECT_TRUE(isa((Decl*)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); + EXPECT_TRUE(isa((Decl*)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()); + EXPECT_TRUE(isa((Decl*)Instance3)); + auto *CTSD3 = static_cast(Instance3); + EXPECT_TRUE(CTSD3->hasDefinition()); + TemplateArgument TA3_0 = CTSD3->getTemplateArgs().get(0); + TemplateArgument TA3_1 = CTSD3->getTemplateArgs().get(1); + EXPECT_TRUE(TA3_0.getAsType()->isIntegerType()); + EXPECT_TRUE(Cpp::IsRecordType(TA3_1.getAsType().getAsOpaquePtr())); + + std::vector Inst3_methods; + Cpp::GetClassMethods(Instance3, Inst3_methods); + EXPECT_EQ(Cpp::GetFunctionSignature(Inst3_methods[0]), + "C1::C1(const C0 &val)"); + + std::vector args4 = {C.IntTy.getAsOpaquePtr(), + {C.IntTy.getAsOpaquePtr(), "3"}}; + auto Instance4 = Cpp::InstantiateTemplate(Decls[3], args4.data(), + /*type_size*/ args4.size()); + + EXPECT_TRUE(isa((Decl*)Instance4)); + auto *CTSD4 = static_cast(Instance4); + EXPECT_TRUE(CTSD4->hasDefinition()); + TemplateArgument TA4_0 = CTSD4->getTemplateArgs().get(0); + TemplateArgument TA4_1 = CTSD4->getTemplateArgs().get(1); + EXPECT_TRUE(TA4_0.getAsType()->isIntegerType()); + EXPECT_TRUE(TA4_1.getAsIntegral() == 3); +} + +TEST(ScopeReflectionTest, GetClassTemplateInstantiationArgs) { + std::vector Decls; + std::string code = R"( + template struct __Cppyy_AppendTypesSlow {}; + __Cppyy_AppendTypesSlow v1; + __Cppyy_AppendTypesSlow v2; + __Cppyy_AppendTypesSlow<> v3; + )"; + + GetAllTopLevelDecls(code, Decls); + + 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)); + auto *v2_class = Cpp::GetScopeFromType(Cpp::GetVariableType(v2)); + auto *v3_class = Cpp::GetScopeFromType(Cpp::GetVariableType(v3)); + EXPECT_TRUE(v1_class && v2_class && v3_class); + + std::vector instance_types; + + Cpp::GetClassTemplateInstantiationArgs(v1_class, instance_types); + EXPECT_TRUE(instance_types.size() == 3); + + instance_types.clear(); + + Cpp::GetClassTemplateInstantiationArgs(v2_class, instance_types); + EXPECT_TRUE(instance_types.size() == 1); + + instance_types.clear(); + + Cpp::GetClassTemplateInstantiationArgs(v3_class, instance_types); + EXPECT_TRUE(instance_types.size() == 0); +} + + +TEST(ScopeReflectionTest, IncludeVector) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + std::string code = R"( + #include + #include + )"; + Interp->declare(code); +} + +TEST(ScopeReflectionTest, GetOperator) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + + Cpp::CreateInterpreter(); + + std::string code = R"( + class MyClass { + public: + int x; + MyClass(int x) : x(x) {} + }; + + MyClass operator-(MyClass lhs, MyClass rhs) { + return MyClass(lhs.x - rhs.x); + } + + MyClass operator+(MyClass lhs, MyClass rhs) { + return MyClass(lhs.x + rhs.x); + } + + namespace extra_ops { + + MyClass operator+(MyClass lhs, int rhs) { + return MyClass(lhs.x + rhs); + } + + MyClass operator+(int lhs, MyClass rhs) { + return MyClass(lhs + rhs.x); + } + + MyClass operator~(MyClass self) { + return MyClass(-self.x); + } + + } + )"; + + Cpp::Declare(code.c_str()); + + std::vector ops; + + 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); + EXPECT_EQ(ops.size(), 1); + ops.clear(); + + 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"), Cpp::Operator::OP_Plus, ops); + EXPECT_EQ(ops.size(), 2); + ops.clear(); + + // unary operator + Cpp::GetOperator(Cpp::GetScope("extra_ops"), Cpp::Operator::OP_Tilde, ops); + EXPECT_EQ(ops.size(), 1); + ops.clear(); + + 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"), Cpp::Operator::OP_Tilde, ops, + Cpp::OperatorArity::kBinary); + EXPECT_EQ(ops.size(), 0); +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/CMakeLists.txt b/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/CMakeLists.txt new file mode 100644 index 0000000000000..05f1afb3f1b39 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/CMakeLists.txt @@ -0,0 +1,11 @@ +add_llvm_library(TestSharedLib + SHARED + DISABLE_LLVM_LINK_LLVM_DYLIB + BUILDTREE_ONLY + TestSharedLib.cpp) +# Put TestSharedLib next to the unit test executable. +set_output_directory(TestSharedLib + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/unittests/bin/$/ + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/unittests/bin/$/ + ) +set_target_properties(TestSharedLib PROPERTIES FOLDER "Tests") diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/TestSharedLib.cpp b/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/TestSharedLib.cpp new file mode 100644 index 0000000000000..c0d7c474c00a2 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/TestSharedLib.cpp @@ -0,0 +1,3 @@ +#include "TestSharedLib.h" + +int ret_zero() { return 0; } diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/TestSharedLib.h b/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/TestSharedLib.h new file mode 100644 index 0000000000000..a56ba42293981 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/TestSharedLib/TestSharedLib.h @@ -0,0 +1,11 @@ +#ifndef UNITTESTS_CPPINTEROP_TESTSHAREDLIB_TESTSHAREDLIB_H +#define UNITTESTS_CPPINTEROP_TESTSHAREDLIB_TESTSHAREDLIB_H + +// Avoid having to mangle/demangle the symbol name in tests +#ifdef _WIN32 +extern "C" __declspec(dllexport) int ret_zero(); +#else +extern "C" int __attribute__((visibility("default"))) ret_zero(); +#endif + +#endif // UNITTESTS_CPPINTEROP_TESTSHAREDLIB_TESTSHAREDLIB_H diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp new file mode 100644 index 0000000000000..4f47c0229ae33 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp @@ -0,0 +1,602 @@ +#include "Utils.h" + +#include "clang/AST/ASTContext.h" +#include "clang/Interpreter/CppInterOp.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/Sema.h" + +#include "gtest/gtest.h" + +#include + +using namespace TestUtils; +using namespace llvm; +using namespace clang; + +TEST(TypeReflectionTest, GetTypeAsString) { + std::vector Decls; + std::string code = R"( + namespace N { + class C {}; + struct S {}; + } + + N::C c; + + N::S s; + + int i; + + char ch = 'c'; + char & ch_r = ch; + const char *ch_cp = "Hello!"; + char ch_arr[4]; + + )"; + + GetAllTopLevelDecls(code, Decls); + QualType QT1 = llvm::dyn_cast(Decls[1])->getType(); + QualType QT2 = llvm::dyn_cast(Decls[2])->getType(); + QualType QT3 = llvm::dyn_cast(Decls[3])->getType(); + QualType QT4 = llvm::dyn_cast(Decls[4])->getType(); + QualType QT5 = llvm::dyn_cast(Decls[5])->getType(); + QualType QT6 = llvm::dyn_cast(Decls[6])->getType(); + QualType QT7 = llvm::dyn_cast(Decls[7])->getType(); + EXPECT_EQ(Cpp::GetTypeAsString(QT1.getAsOpaquePtr()), + "N::C"); + EXPECT_EQ(Cpp::GetTypeAsString(QT2.getAsOpaquePtr()), + "N::S"); + EXPECT_EQ(Cpp::GetTypeAsString(QT3.getAsOpaquePtr()), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(QT4.getAsOpaquePtr()), "char"); + EXPECT_EQ(Cpp::GetTypeAsString(QT5.getAsOpaquePtr()), "char &"); + EXPECT_EQ(Cpp::GetTypeAsString(QT6.getAsOpaquePtr()), "const char *"); + EXPECT_EQ(Cpp::GetTypeAsString(QT7.getAsOpaquePtr()), "char[4]"); +} + +TEST(TypeReflectionTest, GetSizeOfType) { + std::vector Decls; + std::string code = R"( + struct S { + int a; + double b; + }; + + char ch; + int n; + double d; + S s; + struct FwdDecl; + FwdDecl *f; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetSizeOfType(Cpp::GetVariableType(Decls[1])), 1); + EXPECT_EQ(Cpp::GetSizeOfType(Cpp::GetVariableType(Decls[2])), 4); + EXPECT_EQ(Cpp::GetSizeOfType(Cpp::GetVariableType(Decls[3])), 8); + EXPECT_EQ(Cpp::GetSizeOfType(Cpp::GetVariableType(Decls[4])), 16); + EXPECT_EQ(Cpp::GetSizeOfType(Cpp::GetTypeFromScope(Decls[5])), 0); + EXPECT_EQ(Cpp::GetSizeOfType(Cpp::GetVariableType(Decls[6])), + sizeof(intptr_t)); +} + +TEST(TypeReflectionTest, GetCanonicalType) { + std::vector Decls; + std::string code = R"( + typedef int I; + typedef double D; + + I n; + D d; + )"; + + GetAllTopLevelDecls(code, Decls); + + auto D2 = Cpp::GetVariableType(Decls[2]); + auto D3 = Cpp::GetVariableType(Decls[3]); + auto D4 = nullptr; + + EXPECT_EQ(Cpp::GetTypeAsString(D2), "I"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetCanonicalType(D2)), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(D3), "D"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetCanonicalType(D3)), "double"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetCanonicalType(D4)), "NULL TYPE"); +} + +TEST(TypeReflectionTest, GetType) { + Cpp::CreateInterpreter(); + + std::string code = R"( + class A {}; + )"; + + Interp->declare(code); + + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("int")), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("double")), "double"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("A")), "A"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("unsigned char")), "unsigned char"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("signed char")),"signed char"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("unsigned short")), "unsigned short"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("unsigned int")), "unsigned int"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("unsigned long")),"unsigned long"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("unsigned long long")), "unsigned long long"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("signed short")),"short"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("signed int")), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("signed long")),"long"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("signed long long")),"long long"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetType("struct")),"NULL TYPE"); +} + +TEST(TypeReflectionTest, IsRecordType) { + std::vector Decls; + + std::string code = R"( + const int var0 = 0; + const int &var1 = var0; + const int *var2 = &var1; + const int *&var3 = var2; + const int var4[] = {}; + const int *var5[] = {var2}; + int var6 = 0; + int &var7 = var6; + int *var8 = &var7; + int *&var9 = var8; + int var10[] = {}; + int *var11[] = {var8}; + + class C { + public: + int i; + }; + const C cvar0{0}; + const C &cvar1 = cvar0; + const C *cvar2 = &cvar1; + const C *&cvar3 = cvar2; + const C cvar4[] = {}; + const C *cvar5[] = {cvar2}; + C cvar6; + C &cvar7 = cvar6; + C *cvar8 = &cvar7; + C *&cvar9 = cvar8; + C cvar10[] = {}; + C *cvar11[] = {cvar8}; + )"; + GetAllTopLevelDecls(code, Decls); + + auto is_var_of_record_ty = [] (Decl *D) { + return Cpp::IsRecordType(Cpp::GetVariableType(D)); + }; + + EXPECT_FALSE(is_var_of_record_ty(Decls[0])); + EXPECT_FALSE(is_var_of_record_ty(Decls[1])); + EXPECT_FALSE(is_var_of_record_ty(Decls[2])); + EXPECT_FALSE(is_var_of_record_ty(Decls[3])); + EXPECT_FALSE(is_var_of_record_ty(Decls[4])); + EXPECT_FALSE(is_var_of_record_ty(Decls[5])); + EXPECT_FALSE(is_var_of_record_ty(Decls[6])); + EXPECT_FALSE(is_var_of_record_ty(Decls[7])); + EXPECT_FALSE(is_var_of_record_ty(Decls[8])); + EXPECT_FALSE(is_var_of_record_ty(Decls[9])); + EXPECT_FALSE(is_var_of_record_ty(Decls[10])); + EXPECT_FALSE(is_var_of_record_ty(Decls[11])); + + EXPECT_TRUE(is_var_of_record_ty(Decls[13])); + EXPECT_FALSE(is_var_of_record_ty(Decls[14])); + EXPECT_FALSE(is_var_of_record_ty(Decls[15])); + EXPECT_FALSE(is_var_of_record_ty(Decls[16])); + EXPECT_FALSE(is_var_of_record_ty(Decls[17])); + EXPECT_FALSE(is_var_of_record_ty(Decls[18])); + EXPECT_TRUE(is_var_of_record_ty(Decls[19])); + EXPECT_FALSE(is_var_of_record_ty(Decls[20])); + EXPECT_FALSE(is_var_of_record_ty(Decls[21])); + EXPECT_FALSE(is_var_of_record_ty(Decls[22])); + EXPECT_FALSE(is_var_of_record_ty(Decls[23])); + EXPECT_FALSE(is_var_of_record_ty(Decls[24])); +} + +TEST(TypeReflectionTest, GetUnderlyingType) { + std::vector Decls; + + std::string code = R"( + const int var0 = 0; + const int &var1 = var0; + const int *var2 = &var1; + const int *&var3 = var2; + const int var4[] = {}; + const int *var5[] = {var2}; + int var6 = 0; + int &var7 = var6; + int *var8 = &var7; + int *&var9 = var8; + int var10[] = {}; + int *var11[] = {var8}; + int var12[2][5]; + int ***var13; + + class C { + public: + int i; + }; + const C cvar0{0}; + const C &cvar1 = cvar0; + const C *cvar2 = &cvar1; + const C *&cvar3 = cvar2; + const C cvar4[] = {}; + const C *cvar5[] = {cvar2}; + C cvar6; + C &cvar7 = cvar6; + C *cvar8 = &cvar7; + C *&cvar9 = cvar8; + C cvar10[] = {}; + C *cvar11[] = {cvar8}; + C cvar12[2][5]; + C ***cvar13; + + enum E { e1, e2 }; + E evar0 = e1; + )"; + GetAllTopLevelDecls(code, Decls); + auto get_underly_var_type_as_str = [] (Decl *D) { + return Cpp::GetTypeAsString(Cpp::GetUnderlyingType(Cpp::GetVariableType(D))); + }; + EXPECT_EQ(get_underly_var_type_as_str(Decls[0]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[1]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[2]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[3]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[4]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[5]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[6]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[7]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[8]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[9]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[10]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[11]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[12]), "int"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[13]), "int"); + + EXPECT_EQ(get_underly_var_type_as_str(Decls[15]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[16]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[17]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[18]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[19]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[20]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[21]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[22]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[23]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[24]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[25]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[26]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[27]), "C"); + EXPECT_EQ(get_underly_var_type_as_str(Decls[28]), "C"); + + EXPECT_EQ(get_underly_var_type_as_str(Decls[30]), "E"); +} + +TEST(TypeReflectionTest, IsUnderlyingTypeRecordType) { + std::vector Decls; + + std::string code = R"( + const int var0 = 0; + const int &var1 = var0; + const int *var2 = &var1; + const int *&var3 = var2; + const int var4[] = {}; + const int *var5[] = {var2}; + int var6 = 0; + int &var7 = var6; + int *var8 = &var7; + int *&var9 = var8; + int var10[] = {}; + int *var11[] = {var8}; + + class C { + public: + int i; + }; + const C cvar0{0}; + const C &cvar1 = cvar0; + const C *cvar2 = &cvar1; + const C *&cvar3 = cvar2; + const C cvar4[] = {}; + const C *cvar5[] = {cvar2}; + C cvar6; + C &cvar7 = cvar6; + C *cvar8 = &cvar7; + C *&cvar9 = cvar8; + C cvar10[] = {}; + C *cvar11[] = {cvar8}; + )"; + GetAllTopLevelDecls(code, Decls); + + auto is_var_of_underly_record_ty = [] (Decl *D) { + return Cpp::IsRecordType(Cpp::GetUnderlyingType(Cpp::GetVariableType(D))); + }; + + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[0])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[1])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[2])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[3])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[4])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[5])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[6])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[7])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[8])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[9])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[10])); + EXPECT_FALSE(is_var_of_underly_record_ty(Decls[11])); + + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[13])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[14])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[15])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[16])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[17])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[18])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[19])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[20])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[21])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[22])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[23])); + EXPECT_TRUE(is_var_of_underly_record_ty(Decls[24])); +} + +TEST(TypeReflectionTest, GetComplexType) { + Cpp::CreateInterpreter(); + + auto get_complex_type_as_string = [&](const std::string &element_type) { + auto ElementQT = Cpp::GetType(element_type); + auto ComplexQT = Cpp::GetComplexType(ElementQT); + return Cpp::GetTypeAsString(Cpp::GetCanonicalType(ComplexQT)); + }; + + 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); +} + +TEST(TypeReflectionTest, GetTypeFromScope) { + std::vector Decls; + + std::string code = R"( + class C {}; + struct S {}; + int a = 10; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetTypeFromScope(Decls[0])), "C"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetTypeFromScope(Decls[1])), "S"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetTypeFromScope(Decls[2])), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetTypeFromScope(nullptr)), "NULL TYPE"); +} + +TEST(TypeReflectionTest, IsTypeDerivedFrom) { + std::vector Decls; + + std::string code = R"( + class A {}; + class B : A {}; + class C {}; + class D : B {}; + class E : A {}; + + A a; + B b; + C c; + D d; + E e; + )"; + + GetAllTopLevelDecls(code, Decls); + + Cpp::TCppType_t type_A = Cpp::GetVariableType(Decls[5]); + Cpp::TCppType_t type_B = Cpp::GetVariableType(Decls[6]); + Cpp::TCppType_t type_C = Cpp::GetVariableType(Decls[7]); + Cpp::TCppType_t type_D = Cpp::GetVariableType(Decls[8]); + Cpp::TCppType_t type_E = Cpp::GetVariableType(Decls[9]); + + EXPECT_TRUE(Cpp::IsTypeDerivedFrom(type_B, type_A)); + EXPECT_TRUE(Cpp::IsTypeDerivedFrom(type_D, type_B)); + EXPECT_TRUE(Cpp::IsTypeDerivedFrom(type_D, type_A)); + EXPECT_TRUE(Cpp::IsTypeDerivedFrom(type_E, type_A)); + + EXPECT_FALSE(Cpp::IsTypeDerivedFrom(type_A, type_B)); + EXPECT_FALSE(Cpp::IsTypeDerivedFrom(type_C, type_A)); + EXPECT_FALSE(Cpp::IsTypeDerivedFrom(type_D, type_C)); + EXPECT_FALSE(Cpp::IsTypeDerivedFrom(type_B, type_D)); + EXPECT_FALSE(Cpp::IsTypeDerivedFrom(type_A, type_E)); +} + +TEST(TypeReflectionTest, GetDimensions) { + std::vector Decls, SubDecls; + + std::string code = R"( + int a; + int b[1]; + int c[1][2]; + int d[1][2][3]; + + struct S1 { + char ch[]; + }; + + + struct S2 { + char ch[][3][4]; + }; + + template + struct S3 { + typedef T type; + }; + S3::type arr; + )"; + + GetAllTopLevelDecls(code, Decls); + + std::vector dims, truth_dims; + + // Variable a + dims = Cpp::GetDimensions(Cpp::GetVariableType(Decls[0])); + truth_dims = std::vector({}); + EXPECT_EQ(dims.size(), truth_dims.size()); + for (unsigned i = 0; i < truth_dims.size() && i < dims.size(); i++) + { + EXPECT_EQ(dims[i], truth_dims[i]); + } + + // Variable b + dims = Cpp::GetDimensions(Cpp::GetVariableType(Decls[1])); + truth_dims = std::vector({1}); + EXPECT_EQ(dims.size(), truth_dims.size()); + for (unsigned i = 0; i < truth_dims.size() && i < dims.size(); i++) + { + EXPECT_EQ(dims[i], truth_dims[i]); + } + + // Variable c + dims = Cpp::GetDimensions(Cpp::GetVariableType(Decls[2])); + truth_dims = std::vector({1, 2}); + EXPECT_EQ(dims.size(), truth_dims.size()); + for (unsigned i = 0; i < truth_dims.size() && i < dims.size(); i++) + { + EXPECT_EQ(dims[i], truth_dims[i]); + } + + // Variable d + dims = Cpp::GetDimensions(Cpp::GetVariableType(Decls[3])); + truth_dims = std::vector({1, 2, 3}); + EXPECT_EQ(dims.size(), truth_dims.size()); + for (unsigned i = 0; i < truth_dims.size() && i < dims.size(); i++) + { + EXPECT_EQ(dims[i], truth_dims[i]); + } + + // Field S1::ch + GetAllSubDecls(Decls[4], SubDecls); + dims = Cpp::GetDimensions(Cpp::GetVariableType(SubDecls[1])); + truth_dims = std::vector({Cpp::DimensionValue::UNKNOWN_SIZE}); + EXPECT_EQ(dims.size(), truth_dims.size()); + for (unsigned i = 0; i < truth_dims.size() && i < dims.size(); i++) + { + EXPECT_EQ(dims[i], truth_dims[i]); + } + + // Field S2::ch + GetAllSubDecls(Decls[5], SubDecls); + dims = Cpp::GetDimensions(Cpp::GetVariableType(SubDecls[3])); + truth_dims = std::vector({Cpp::DimensionValue::UNKNOWN_SIZE, 3, 4}); + EXPECT_EQ(dims.size(), truth_dims.size()); + for (unsigned i = 0; i < truth_dims.size() && i < dims.size(); i++) + { + EXPECT_EQ(dims[i], truth_dims[i]); + } + + // Variable arr + dims = Cpp::GetDimensions(Cpp::GetVariableType(Decls[7])); + truth_dims = std::vector({6}); + EXPECT_EQ(dims.size(), truth_dims.size()); + for (unsigned i = 0; i < truth_dims.size() && i < dims.size(); i++) + { + EXPECT_EQ(dims[i], truth_dims[i]); + } +} + +TEST(TypeReflectionTest, IsPODType) { + std::vector Decls; + + std::string code = R"( + struct A {}; + struct B { + int x; + + private: + int y; + }; + + A a; + B b; + )"; + + GetAllTopLevelDecls(code, Decls); + EXPECT_TRUE(Cpp::IsPODType(Cpp::GetVariableType(Decls[2]))); + EXPECT_FALSE(Cpp::IsPODType(Cpp::GetVariableType(Decls[3]))); + EXPECT_FALSE(Cpp::IsPODType(0)); +} + +TEST(TypeReflectionTest, IsSmartPtrType) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + Cpp::CreateInterpreter(); + + Interp->declare(R"( + #include + + template + class derived_shared_ptr : public std::shared_ptr {}; + template + class derived_unique_ptr : public std::unique_ptr {}; + + class C {}; + + // std::auto_ptr smart_ptr1; // Deprecated but passes the checks. + std::shared_ptr smart_ptr2; + std::unique_ptr smart_ptr3; + std::weak_ptr smart_ptr4; + derived_shared_ptr smart_ptr5; + derived_unique_ptr smart_ptr6; + + C *raw_ptr; + C object(); + )"); + + auto get_type_from_varname = [&](const std::string &varname) { + return Cpp::GetVariableType(Cpp::GetNamed(varname)); + }; + + //EXPECT_TRUE(Cpp::IsSmartPtrType(get_type_from_varname("smart_ptr1"))); + EXPECT_TRUE(Cpp::IsSmartPtrType(get_type_from_varname("smart_ptr2"))); + EXPECT_TRUE(Cpp::IsSmartPtrType(get_type_from_varname("smart_ptr3"))); + EXPECT_TRUE(Cpp::IsSmartPtrType(get_type_from_varname("smart_ptr4"))); + EXPECT_TRUE(Cpp::IsSmartPtrType(get_type_from_varname("smart_ptr5"))); + EXPECT_TRUE(Cpp::IsSmartPtrType(get_type_from_varname("smart_ptr6"))); + EXPECT_FALSE(Cpp::IsSmartPtrType(get_type_from_varname("raw_ptr"))); + EXPECT_FALSE(Cpp::IsSmartPtrType(get_type_from_varname("object"))); +} + +TEST(TypeReflectionTest, IsFunctionPointerType) { + Cpp::CreateInterpreter(); + + Interp->declare(R"( + typedef int (*int_func)(int, int); + int sum(int x, int y) { return x + y; } + int_func f = sum; + int i = 2; + )"); + + EXPECT_TRUE( + Cpp::IsFunctionPointerType(Cpp::GetVariableType(Cpp::GetNamed("f")))); + EXPECT_FALSE( + Cpp::IsFunctionPointerType(Cpp::GetVariableType(Cpp::GetNamed("i")))); +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp b/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp new file mode 100644 index 0000000000000..ca7227b99eae8 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp @@ -0,0 +1,71 @@ +#include "Utils.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Interpreter/CppInterOp.h" +#include "clang/Sema/Lookup.h" +#include "clang/Sema/Sema.h" + +#include +#include + +using namespace clang; +using namespace llvm; + +void TestUtils::GetAllTopLevelDecls(const std::string& code, std::vector& Decls, + bool filter_implicitGenerated /* = false */) { + Cpp::CreateInterpreter(); +#ifdef CPPINTEROP_USE_CLING + cling::Transaction *T = nullptr; + Interp->declare(code, &T); + + for (auto DCI = T->decls_begin(), E = T->decls_end(); DCI != E; ++DCI) { + if (DCI->m_Call != cling::Transaction::kCCIHandleTopLevelDecl) + continue; + for (Decl *D : DCI->m_DGR) { + if (filter_implicitGenerated && D->isImplicit()) + continue; + Decls.push_back(D); + } + } +#else + PartialTranslationUnit *T = nullptr; + Interp->process(code, /*Value*/nullptr, &T); + for (auto *D : T->TUPart->decls()) { + if (filter_implicitGenerated && D->isImplicit()) + continue; + Decls.push_back(D); + } +#endif +} + +void TestUtils::GetAllSubDecls(Decl *D, std::vector& SubDecls, + bool filter_implicitGenerated /* = false */) { + if (!isa_and_nonnull(D)) + return; + DeclContext *DC = cast(D); + for (auto *Di : DC->decls()) { + if (filter_implicitGenerated && Di->isImplicit()) + continue; + SubDecls.push_back(Di); + } +} + +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}}; +} \ No newline at end of file diff --git a/interpreter/CppInterOp/unittests/CppInterOp/Utils.h b/interpreter/CppInterOp/unittests/CppInterOp/Utils.h new file mode 100644 index 0000000000000..180e209fe2f13 --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/Utils.h @@ -0,0 +1,32 @@ +#ifndef CPPINTEROP_UNITTESTS_LIBCPPINTEROP_UTILS_H +#define CPPINTEROP_UNITTESTS_LIBCPPINTEROP_UTILS_H + +#include "../../lib/Interpreter/Compatibility.h" + +#include "llvm/Support/Valgrind.h" +#include +#include +#include "clang-c/CXCppInterOp.h" +#include "clang-c/CXString.h" + +using namespace clang; +using namespace llvm; + +namespace clang { + class Decl; +} +#define Interp (static_cast(Cpp::GetInterpreter())) +namespace TestUtils { + void GetAllTopLevelDecls(const std::string& code, std::vector& Decls, + bool filter_implicitGenerated = false); + 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); + +#endif // CPPINTEROP_UNITTESTS_LIBCPPINTEROP_UTILS_H diff --git a/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp new file mode 100644 index 0000000000000..4a0940848db4f --- /dev/null +++ b/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp @@ -0,0 +1,602 @@ +#include "Utils.h" + +#include "clang/AST/ASTContext.h" +#include "clang/Interpreter/CppInterOp.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/Sema.h" + +#include "gtest/gtest.h" +#include + +#include + +using namespace TestUtils; +using namespace llvm; +using namespace clang; + +TEST(VariableReflectionTest, GetDatamembers) { + std::vector Decls; + std::string code = R"( + class C { + public: + int a; + static int b; + private: + int c; + static int d; + protected: + int e; + static int f; + }; + void sum(int,int); + + class D : public C { + public: + int x; + using C::e; + }; + )"; + + std::vector datamembers; + std::vector datamembers1; + std::vector datamembers2; + GetAllTopLevelDecls(code, Decls); + Cpp::GetDatamembers(Decls[0], datamembers); + Cpp::GetDatamembers(Decls[1], datamembers1); + Cpp::GetDatamembers(Decls[2], datamembers2); + + // non static field + EXPECT_EQ(Cpp::GetQualifiedName(datamembers[0]), "C::a"); + EXPECT_EQ(Cpp::GetQualifiedName(datamembers[1]), "C::c"); + EXPECT_EQ(Cpp::GetQualifiedName(datamembers[2]), "C::e"); + EXPECT_EQ(datamembers.size(), 3); + EXPECT_EQ(datamembers1.size(), 0); + + // static fields + datamembers.clear(); + datamembers1.clear(); + + Cpp::GetStaticDatamembers(Decls[0], datamembers); + Cpp::GetStaticDatamembers(Decls[1], datamembers1); + + EXPECT_EQ(Cpp::GetQualifiedName(datamembers[0]), "C::b"); + EXPECT_EQ(Cpp::GetQualifiedName(datamembers[1]), "C::d"); + EXPECT_EQ(Cpp::GetQualifiedName(datamembers[2]), "C::f"); + EXPECT_EQ(datamembers.size(), 3); + EXPECT_EQ(datamembers1.size(), 0); + + // derived class + EXPECT_EQ(datamembers2.size(), 2); + EXPECT_EQ(Cpp::GetQualifiedName(datamembers2[0]), "D::x"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::GetUnderlyingScope(datamembers2[1])), + "C::e"); + EXPECT_TRUE(Cpp::IsPublicVariable(datamembers2[0])); + EXPECT_TRUE(Cpp::IsPublicVariable(datamembers2[1])); + EXPECT_TRUE( + Cpp::IsProtectedVariable(Cpp::GetUnderlyingScope(datamembers2[1]))); +} +// If on Windows disable warning due to unnamed structs/unions in defined CODE. +#ifdef _WIN32 +#pragma warning(disable : 4201) +#endif +#define CODE \ + struct Klass1 { \ + Klass1(int i) : num(1), b(i) {} \ + int num; \ + union { \ + double a; \ + int b; \ + }; \ + } const k1(5); \ + struct Klass2 { \ + Klass2(double d) : num(2), a(d) {} \ + int num; \ + struct { \ + double a; \ + int b; \ + }; \ + } const k2(2.5); \ + struct Klass3 { \ + Klass3(int i) : num(i) {} \ + int num; \ + struct { \ + double a; \ + union { \ + float b; \ + int c; \ + }; \ + }; \ + int num2; \ + } const k3(5); + +CODE + +TEST(VariableReflectionTest, DatamembersWithAnonymousStructOrUnion) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + + std::vector Decls; +#define Stringify(s) Stringifyx(s) +#define Stringifyx(...) #__VA_ARGS__ + GetAllTopLevelDecls(Stringify(CODE), Decls); +#undef Stringifyx +#undef Stringify +#undef CODE + + std::vector datamembers_klass1; + std::vector datamembers_klass2; + std::vector datamembers_klass3; + + Cpp::GetDatamembers(Decls[0], datamembers_klass1); + Cpp::GetDatamembers(Decls[2], datamembers_klass2); + Cpp::GetDatamembers(Decls[4], datamembers_klass3); + + EXPECT_EQ(datamembers_klass1.size(), 3); + EXPECT_EQ(datamembers_klass2.size(), 3); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[0]), 0); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[1]), + ((intptr_t) & (k1.a)) - ((intptr_t) & (k1.num))); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass1[2]), + ((intptr_t) & (k1.b)) - ((intptr_t) & (k1.num))); + + 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]), + ((intptr_t) & (k2.b)) - ((intptr_t) & (k2.num))); + + 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]), + ((intptr_t) & (k3.b)) - ((intptr_t) & (k3.num))); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[3]), + ((intptr_t) & (k3.c)) - ((intptr_t) & (k3.num))); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers_klass3[4]), + ((intptr_t) & (k3.num2)) - ((intptr_t) & (k3.num))); +#ifdef _WIN32 +#pragma warning(default : 4201) +#endif +} + +TEST(VariableReflectionTest, GetTypeAsString) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + + std::string code = R"( + namespace my_namespace { + + struct Container { + int value; + }; + + struct Wrapper { + Container item; + }; + + } + )"; + + Cpp::CreateInterpreter(); + EXPECT_EQ(Cpp::Declare(code.c_str()), 0); + + Cpp::TCppScope_t wrapper = + Cpp::GetScopeFromCompleteName("my_namespace::Wrapper"); + EXPECT_TRUE(wrapper); + + std::vector datamembers; + Cpp::GetDatamembers(wrapper, datamembers); + EXPECT_EQ(datamembers.size(), 1); + + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetVariableType(datamembers[0])), + "my_namespace::Container"); +} + +TEST(VariableReflectionTest, LookupDatamember) { + std::vector Decls; + std::string code = R"( + class C { + public: + int a; + static int b; + private: + int c; + static int d; + protected: + int e; + static int f; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::LookupDatamember("a", Decls[0])), "C::a"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::LookupDatamember("c", Decls[0])), "C::c"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::LookupDatamember("e", Decls[0])), "C::e"); + EXPECT_EQ(Cpp::GetQualifiedName(Cpp::LookupDatamember("k", Decls[0])), ""); +} + +TEST(VariableReflectionTest, GetVariableType) { + std::vector Decls; + std::string code = R"( + class C {}; + + template + class E {}; + + int a; + char b; + C c; + C *d; + E e; + E *f; + int g[4]; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetVariableType(Decls[2])), "int"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetVariableType(Decls[3])), "char"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetVariableType(Decls[4])), "C"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetVariableType(Decls[5])), "C *"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetVariableType(Decls[6])), "E"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetVariableType(Decls[7])), "E *"); + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetVariableType(Decls[8])), "int[4]"); +} + +#define CODE \ + int a; \ + const int N = 5; \ + static int S = N + 1; \ + static const int SN = S + 1; \ + class C { \ + public: \ + int a; \ + double b; \ + int* c; \ + int d; \ + static int s_a; \ + } c; \ + int C::s_a = 7 + SN; + +CODE + +TEST(VariableReflectionTest, GetVariableOffset) { + std::vector Decls; +#define Stringify(s) Stringifyx(s) +#define Stringifyx(...) #__VA_ARGS__ + GetAllTopLevelDecls(Stringify(CODE), Decls); +#undef Stringifyx +#undef Stringify +#undef CODE + + EXPECT_EQ(7, Decls.size()); + + std::vector datamembers; + Cpp::GetDatamembers(Decls[4], datamembers); + + EXPECT_TRUE((bool) Cpp::GetVariableOffset(Decls[0])); // a + EXPECT_TRUE((bool) Cpp::GetVariableOffset(Decls[1])); // N + EXPECT_TRUE((bool)Cpp::GetVariableOffset(Decls[2])); // S + EXPECT_TRUE((bool)Cpp::GetVariableOffset(Decls[3])); // SN + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[0]), 0); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[1]), + ((intptr_t) &(c.b)) - ((intptr_t) &(c.a))); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[2]), + ((intptr_t) &(c.c)) - ((intptr_t) &(c.a))); + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[3]), + ((intptr_t) &(c.d)) - ((intptr_t) &(c.a))); + + auto* VD_C_s_a = Cpp::GetNamed("s_a", Decls[4]); // C::s_a + EXPECT_TRUE((bool) Cpp::GetVariableOffset(VD_C_s_a)); +} + +#define CODE \ + class BaseA { \ + public: \ + virtual ~BaseA() {} \ + int a; \ + BaseA(int a) : a(a) {} \ + }; \ + \ + class BaseB : public BaseA { \ + public: \ + virtual ~BaseB() {} \ + std::string b; \ + BaseB(int x, std::string b) : BaseA(x), b(b) {} \ + }; \ + \ + class Base1 { \ + public: \ + virtual ~Base1() {} \ + int i; \ + std::string s; \ + Base1(int i, std::string s) : i(i), s(s) {} \ + }; \ + \ + class MyKlass : public BaseB, public Base1 { \ + public: \ + virtual ~MyKlass() {} \ + int k; \ + MyKlass(int k, int i, int x, std::string b, std::string s) \ + : BaseB(x, b), Base1(i, s), k(k) {} \ + } my_k(5, 4, 3, "Cpp", "Python"); + +CODE + +TEST(VariableReflectionTest, VariableOffsetsWithInheritance) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + + Cpp::Declare("#include"); + +#define Stringify(s) Stringifyx(s) +#define Stringifyx(...) #__VA_ARGS__ + Cpp::Declare(Stringify(CODE)); +#undef Stringifyx +#undef Stringify +#undef CODE + + Cpp::TCppScope_t myklass = Cpp::GetNamed("MyKlass"); + EXPECT_TRUE(myklass); + + size_t num_bases = Cpp::GetNumBases(myklass); + EXPECT_EQ(num_bases, 2); + + std::vector datamembers; + Cpp::GetDatamembers(myklass, datamembers); + for (size_t i = 0; i < num_bases; i++) { + Cpp::TCppScope_t base = Cpp::GetBaseClass(myklass, i); + EXPECT_TRUE(base); + for (size_t i = 0; i < Cpp::GetNumBases(base); i++) { + Cpp::TCppScope_t bbase = Cpp::GetBaseClass(base, i); + EXPECT_TRUE(base); + Cpp::GetDatamembers(bbase, datamembers); + } + Cpp::GetDatamembers(base, datamembers); + } + EXPECT_EQ(datamembers.size(), 5); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[0], myklass), + ((intptr_t)&(my_k.k)) - ((intptr_t)&(my_k))); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[1], myklass), + ((intptr_t)&(my_k.a)) - ((intptr_t)&(my_k))); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[2], myklass), + ((intptr_t)&(my_k.b)) - ((intptr_t)&(my_k))); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[3], myklass), + ((intptr_t)&(my_k.i)) - ((intptr_t)&(my_k))); + + EXPECT_EQ(Cpp::GetVariableOffset(datamembers[4], myklass), + ((intptr_t)&(my_k.s)) - ((intptr_t)&(my_k))); +} + +TEST(VariableReflectionTest, IsPublicVariable) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + public: + int a; + private: + int b; + protected: + int c; + int sum(int,int); + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_TRUE(Cpp::IsPublicVariable(SubDecls[2])); + EXPECT_FALSE(Cpp::IsPublicVariable(SubDecls[4])); + EXPECT_FALSE(Cpp::IsPublicVariable(SubDecls[6])); + EXPECT_FALSE(Cpp::IsPublicVariable(SubDecls[7])); +} + +TEST(VariableReflectionTest, IsProtectedVariable) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + public: + int a; + private: + int b; + protected: + int c; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_FALSE(Cpp::IsProtectedVariable(SubDecls[2])); + EXPECT_FALSE(Cpp::IsProtectedVariable(SubDecls[4])); + EXPECT_TRUE(Cpp::IsProtectedVariable(SubDecls[6])); +} + +TEST(VariableReflectionTest, IsPrivateVariable) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + public: + int a; + private: + int b; + protected: + int c; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_FALSE(Cpp::IsPrivateVariable(SubDecls[2])); + EXPECT_TRUE(Cpp::IsPrivateVariable(SubDecls[4])); + EXPECT_FALSE(Cpp::IsPrivateVariable(SubDecls[6])); +} + +TEST(VariableReflectionTest, IsStaticVariable) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + int a; + static int b; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_FALSE(Cpp::IsStaticVariable(SubDecls[1])); + EXPECT_TRUE(Cpp::IsStaticVariable(SubDecls[2])); +} + +TEST(VariableReflectionTest, IsConstVariable) { + std::vector Decls, SubDecls; + std::string code = R"( + class C { + int a; + const int b = 2; + }; + )"; + + GetAllTopLevelDecls(code, Decls); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_FALSE(Cpp::IsConstVariable(Decls[0])); + EXPECT_FALSE(Cpp::IsConstVariable(SubDecls[1])); + EXPECT_TRUE(Cpp::IsConstVariable(SubDecls[2])); +} + +TEST(VariableReflectionTest, DISABLED_GetArrayDimensions) { + std::vector Decls, SubDecls; + std::string code = R"( + int a; + int b[1]; + int c[1][2]; + )"; + + GetAllTopLevelDecls(code, Decls); + + //FIXME: is_vec_eq is an unused variable until test does something + //auto is_vec_eq = [](const std::vector &arr_dims, + // const std::vector &truth_vals) { + // if (arr_dims.size() != truth_vals.size()) + // return false; + // + // return std::equal(arr_dims.begin(), arr_dims.end(), truth_vals.begin()); + //}; + + // EXPECT_TRUE(is_vec_eq(Cpp::GetArrayDimensions(Decls[0]), {})); + // EXPECT_TRUE(is_vec_eq(Cpp::GetArrayDimensions(Decls[1]), {1})); + // EXPECT_TRUE(is_vec_eq(Cpp::GetArrayDimensions(Decls[2]), {1,2})); +} + +TEST(VariableReflectionTest, StaticConstExprDatamember) { + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + + Cpp::CreateInterpreter(); + + Cpp::Declare(R"( + class MyClass { + public: + static constexpr int x = 3; + }; + + template + class MyTemplatedClass { + public: + static constexpr int x = i; + }; + + template + struct integral_constant + { + static constexpr _Tp value = __v; + }; + + template + struct Elements + : public integral_constant {}; + )"); + + 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]); + EXPECT_EQ(3, *(int*)offset); + + ASTContext& C = Interp->getCI()->getASTContext(); + std::vector template_args = { + {C.IntTy.getAsOpaquePtr(), "5"}}; + + Cpp::TCppFunction_t MyTemplatedClass = + Cpp::InstantiateTemplate(Cpp::GetNamed("MyTemplatedClass"), + template_args.data(), template_args.size()); + EXPECT_TRUE(MyTemplatedClass); + + datamembers.clear(); + Cpp::GetStaticDatamembers(MyTemplatedClass, datamembers); + EXPECT_EQ(datamembers.size(), 1); + + offset = Cpp::GetVariableOffset(datamembers[0]); + EXPECT_EQ(5, *(int*)offset); + + std::vector ele_template_args = { + {C.IntTy.getAsOpaquePtr()}, {C.FloatTy.getAsOpaquePtr()}}; + + Cpp::TCppFunction_t Elements = Cpp::InstantiateTemplate( + Cpp::GetNamed("Elements"), ele_template_args.data(), + ele_template_args.size()); + EXPECT_TRUE(Elements); + + EXPECT_EQ(1, Cpp::GetNumBases(Elements)); + + Cpp::TCppScope_t IC = Cpp::GetBaseClass(Elements, 0); + + datamembers.clear(); + Cpp::GetStaticDatamembers(IC, datamembers); + EXPECT_EQ(datamembers.size(), 1); + + offset = Cpp::GetVariableOffset(datamembers[0]); + EXPECT_EQ(2, *(int*)offset); +} + +TEST(VariableReflectionTest, GetEnumConstantDatamembers) { + Cpp::CreateInterpreter(); + + Cpp::Declare(R"( + class MyEnumClass { + enum { FOUR, FIVE, SIX }; + enum A { ONE, TWO, THREE }; + enum class B { SEVEN, EIGHT, NINE }; + }; + )"); + + Cpp::TCppScope_t MyEnumClass = Cpp::GetNamed("MyEnumClass"); + EXPECT_TRUE(MyEnumClass); + + std::vector datamembers; + Cpp::GetEnumConstantDatamembers(MyEnumClass, datamembers); + EXPECT_EQ(datamembers.size(), 9); + EXPECT_TRUE(Cpp::IsEnumType(Cpp::GetVariableType(datamembers[0]))); + + std::vector datamembers2; + Cpp::GetEnumConstantDatamembers(MyEnumClass, datamembers2, false); + EXPECT_EQ(datamembers2.size(), 6); +}