diff --git a/.github/actions/Build_LLVM/action.yml b/.github/actions/Build_LLVM/action.yml index 3d9461fe7..e59f19c2a 100644 --- a/.github/actions/Build_LLVM/action.yml +++ b/.github/actions/Build_LLVM/action.yml @@ -49,6 +49,10 @@ runs: git apply -v ../patches/llvm/clang${{ matrix.clang-runtime }}-*.patch echo "Apply clang${{ matrix.clang-runtime }}-*.patch patches:" fi + if [[ "${{ matrix.oop-jit }}" == "On" ]]; then + git apply -v ../patches/llvm/clang20-2-out-of-process-jit-execution.patch + echo "Apply out-of-process-jit-execution.patch:" + fi cd build cmake -DLLVM_ENABLE_PROJECTS="${{ matrix.llvm_enable_projects}}" \ -DLLVM_TARGETS_TO_BUILD="${{ matrix.llvm_targets_to_build }}" \ @@ -64,6 +68,10 @@ runs: -DLLVM_INCLUDE_TESTS=OFF \ ../llvm ninja clang clangInterpreter clangStaticAnalyzerCore -j ${{ env.ncpus }} + if [[ "${{ matrix.oop-jit }}" == "On" ]]; then + if [ "${{ matrix.os }}}" == "macos"* ]; then SUFFIX="_osx"; fi + ninja clang-repl llvm-jitlink-executor orc_rt${SUFFIX} -j ${{ env.ncpus }} + fi cd ./tools/ rm -rf $(find . -maxdepth 1 ! -name "clang" ! -name ".") cd .. diff --git a/.github/actions/Build_and_Test_CppInterOp/action.yml b/.github/actions/Build_and_Test_CppInterOp/action.yml index b97d2e8ae..c933ad8c3 100644 --- a/.github/actions/Build_and_Test_CppInterOp/action.yml +++ b/.github/actions/Build_and_Test_CppInterOp/action.yml @@ -41,14 +41,15 @@ runs: ../ else cmake -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ - -DCPPINTEROP_INCLUDE_DOCS=${{ matrix.documentation }} \ - -DLLVM_DIR=$LLVM_BUILD_DIR/lib/cmake/llvm \ - -DClang_DIR=$LLVM_BUILD_DIR/lib/cmake/clang \ - -DBUILD_SHARED_LIBS=ON \ - -DCODE_COVERAGE=${{ env.CODE_COVERAGE }} \ - -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR \ - -DLLVM_ENABLE_WERROR=On \ - ../ + -DCPPINTEROP_INCLUDE_DOCS=${{ matrix.documentation }} \ + -DLLVM_DIR=$LLVM_BUILD_DIR/lib/cmake/llvm \ + -DClang_DIR=$LLVM_BUILD_DIR/lib/cmake/clang \ + -DBUILD_SHARED_LIBS=ON \ + -DCODE_COVERAGE=${{ env.CODE_COVERAGE }} \ + -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR \ + -DLLVM_ENABLE_WERROR=On \ + -DCPPINTEROP_WITH_OOP_JIT=${{ matrix.oop-jit }} \ + ../ fi docs_on=$(echo "${{ matrix.documentation }}" | tr '[:lower:]' '[:upper:]') if [[ "${docs_on}" == "ON" ]]; then diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 669958da3..f60857849 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,6 +32,15 @@ jobs: llvm_enable_projects: "clang" llvm_targets_to_build: "host;NVPTX" coverage: true + - name: ubu24-arm-gcc12-clang-repl-20-oop + os: ubuntu-24.04-arm + compiler: gcc-12 + clang-runtime: '20' + cling: Off + cppyy: Off + llvm_enable_projects: "clang;compiler-rt" + llvm_targets_to_build: "host;NVPTX" + oop-jit: On - name: ubu24-arm-gcc12-clang-repl-20 os: ubuntu-24.04-arm compiler: gcc-12 @@ -132,6 +141,15 @@ jobs: llvm_enable_projects: "clang" llvm_targets_to_build: "host;NVPTX" # MacOS Arm Jobs + - name: osx15-arm-clang-clang-repl-20-oop + os: macos-15 + compiler: clang + clang-runtime: '20' + cling: Off + cppyy: Off + llvm_enable_projects: "clang;compiler-rt" + llvm_targets_to_build: "host" + oop-jit: On - name: osx15-arm-clang-clang-repl-20 os: macos-15 compiler: clang @@ -182,6 +200,15 @@ jobs: llvm_enable_projects: "clang" llvm_targets_to_build: "host;NVPTX" # MacOS X86 Jobs + - name: osx13-x86-clang-clang-repl-20-oop + os: macos-13 + compiler: clang + clang-runtime: '20' + cling: Off + cppyy: Off + llvm_enable_projects: "clang;compiler-rt" + llvm_targets_to_build: "host" + oop-jit: On - name: osx13-x86-clang-clang-repl-20 os: macos-13 compiler: clang diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b2df5b4f..57cc29342 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -310,6 +310,16 @@ include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) add_definitions(${LLVM_DEFINITIONS_LIST}) +if(CPPINTEROP_WITH_OOP_JIT) + if(WIN32 OR EMSCRIPTEN) + message(FATAL_ERROR "CPPINTEROP_WITH_OOP_JIT is not supported on Windows or Emscripten platforms.") + endif() + add_definitions(-DCPPINTEROP_WITH_OOP_JIT) +endif() + +string(REGEX REPLACE "/build/lib/cmake/llvm$" "" LLVM_SOURCE_DIR "${LLVM_DIR}") +add_definitions(-DLLVM_SOURCE_DIR="${LLVM_SOURCE_DIR}") + # If the llvm sources are present add them with higher priority. if (LLVM_BUILD_MAIN_SRC_DIR) # LLVM_INCLUDE_DIRS contains the include paths to both LLVM's source and diff --git a/README.md b/README.md index 534fc191e..a367765a4 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,13 @@ git apply -v clang{version}-*.patch on Windows. +If you want to have out-of-process JIT execution enabled in CppInterOp, then apply this patch on Linux and MacOS environment. +> Note that this patch will not work for Windows because out-of-process JIT execution is currently implemented for Linux and MacOS only. + +```bash +git apply -v ../CppInterOp/patches/llvm/clang20-2-out-of-process-jit-execution.patch +``` + ##### Build Clang-REPL Clang-REPL is an interpreter that CppInterOp works alongside. Build Clang (and @@ -175,6 +182,30 @@ $env:LLVM_DIR= $PWD.Path cd ..\ ``` +##### Build Clang-REPL with Out-of-Process JIT Execution + +To have ``Out-of-Process JIT Execution`` enabled, run following commands to build clang and clang-repl to support this feature: +> Only for Linux and Macos +```bash +mkdir build +cd build +cmake -DLLVM_ENABLE_PROJECTS="clang;compiler-rt \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + ../llvm + +## For Linux +cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt --parallel $(nproc --all) + +## For MacOS +cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt_osx --parallel $(sysctl -n hw.ncpu) +``` + #### Build Cling and related dependencies Besides the Clang-REPL interpreter, CppInterOp also works alongside the Cling @@ -324,6 +355,8 @@ cmake -DBUILD_SHARED_LIBS=ON -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_D cmake --build . --target install --parallel $(nproc --all) ``` +> Do make sure to add ``-DCPPINTEROP_WITH_OOP_JIT=ON``, if you want to have out-of-process JIT execution feature enabled. + and ```powershell diff --git a/docs/InstallationAndUsage.rst b/docs/InstallationAndUsage.rst index 8063d9c61..27ed4bfa0 100644 --- a/docs/InstallationAndUsage.rst +++ b/docs/InstallationAndUsage.rst @@ -58,6 +58,15 @@ and on Windows. +If you want to have out-of-process JIT execution enabled in CppInterOp, then apply this patch on Linux and MacOS environment. +.. note:: + + This patch will not work for Windows because out-of-process JIT execution is currently implemented for Linux and MacOS only. + +.. code:: bash + + git apply -v ../CppInterOp/patches/llvm/clang20-2-out-of-process-jit-execution.patch + ****************** Build Clang-REPL ****************** @@ -116,6 +125,36 @@ On Windows you execute the following $env:LLVM_DIR= $PWD.Path cd ..\ +*************************************************** +Build Clang-REPL with Out-of-Process JIT Execution +*************************************************** + +To have `Out-of-Process JIT Execution` enabled, run following commands to build clang and clang-repl to support this feature: + +.. note:: + + Only for Linux and Macos + +.. code:: bash + + mkdir build + cd build + cmake -DLLVM_ENABLE_PROJECTS="clang;compiler-rt \ + -DLLVM_TARGETS_TO_BUILD="host;NVPTX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_ASSERTIONS=ON \ + -DCLANG_ENABLE_STATIC_ANALYZER=OFF \ + -DCLANG_ENABLE_ARCMT=OFF \ + -DCLANG_ENABLE_FORMAT=OFF \ + -DCLANG_ENABLE_BOOTSTRAP=OFF \ + ../llvm + + # For Linux + cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt --parallel $(nproc --all) + + # For MacOS + cmake --build . --target clang clang-repl llvm-jitlink-executor orc_rt_osx --parallel $(sysctl -n hw.ncpu) + ************************************** Build Cling and related dependencies ************************************** @@ -280,6 +319,10 @@ commands on Linux and MacOS cmake -DBUILD_SHARED_LIBS=ON -DLLVM_DIR=$LLVM_DIR/build/lib/cmake/llvm -DClang_DIR=$LLVM_DIR/build/lib/cmake/clang -DCMAKE_INSTALL_PREFIX=$CPPINTEROP_DIR .. cmake --build . --target install --parallel $(nproc --all) +.. note:: + + Do make sure to add `-DCPPINTEROP_WITH_OOP_JIT=ON`, if you want to have out-of-process JIT execution feature enabled. + and .. code:: powershell diff --git a/include/CppInterOp/CppInterOp.h b/include/CppInterOp/CppInterOp.h index 2f3d330db..9b2e95343 100644 --- a/include/CppInterOp/CppInterOp.h +++ b/include/CppInterOp/CppInterOp.h @@ -665,7 +665,7 @@ CPPINTEROP_API void GetOperator(TCppScope_t scope, Operator op, ///\returns nullptr on failure. CPPINTEROP_API TInterp_t CreateInterpreter(const std::vector& Args = {}, - const std::vector& GpuArgs = {}); + const std::vector& GpuArgs = {}, bool outOfProcess = false); /// Deletes an instance of an interpreter. ///\param[in] I - the interpreter to be deleted, if nullptr, deletes the last. @@ -901,6 +901,10 @@ CPPINTEROP_API void CodeComplete(std::vector& Results, ///\returns 0 on success, non-zero on failure. CPPINTEROP_API int Undo(unsigned N = 1); +CPPINTEROP_API pid_t GetExecutorPID(); + +// CPPINTEROP_API void SendInputToChild(const std::string& input); + } // end namespace Cpp #endif // CPPINTEROP_CPPINTEROP_H diff --git a/lib/CppInterOp/Compatibility.h b/lib/CppInterOp/Compatibility.h index 0c7c66f5b..0fbe517e1 100644 --- a/lib/CppInterOp/Compatibility.h +++ b/lib/CppInterOp/Compatibility.h @@ -205,10 +205,27 @@ inline void codeComplete(std::vector& Results, #include "llvm/Support/Error.h" +#include +#include + +#ifdef CPPINTEROP_WITH_OOP_JIT +#include "clang/Basic/Version.h" +#include "clang/Interpreter/RemoteJITUtils.h" +#include "llvm/TargetParser/Host.h" + +#include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h" +#endif + +static const llvm::ExitOnError ExitOnError; + namespace compat { +static int m_child_stdout_fd = -1; +static int m_child_stderr_fd = -1; +// static int m_child_stdin_fd = -1; + inline std::unique_ptr -createClangInterpreter(std::vector& args) { +createClangInterpreter(std::vector& args, bool outOfProcess) { auto has_arg = [](const char* x, llvm::StringRef match = "cuda") { llvm::StringRef Arg = x; Arg = Arg.trim().ltrim('-'); @@ -246,16 +263,78 @@ createClangInterpreter(std::vector& args) { (*ciOrErr)->LoadRequestedPlugins(); if (CudaEnabled) DeviceCI->LoadRequestedPlugins(); + +#ifdef CPPINTEROP_WITH_OOP_JIT + std::unique_ptr JB; + + if (outOfProcess) { + std::string OOPExecutor = + std::string(LLVM_SOURCE_DIR) + "/build/bin/llvm-jitlink-executor"; + bool UseSharedMemory = false; + std::string SlabAllocateSizeString = ""; + std::unique_ptr EPC; + + // Create pipes for child process I/O control + int stdout_pipe[2], stderr_pipe[2], stdin_pipe[2]; + if (pipe(stdout_pipe) != 0 || pipe(stderr_pipe) != 0 || pipe(stdin_pipe)) { + llvm::errs() << "Failed to create pipes for child process I/O\n"; + return nullptr; + } + + EPC = ExitOnError( + launchExecutor(OOPExecutor, UseSharedMemory, SlabAllocateSizeString, + /*stdin_pipe[0]*/0, stdout_pipe[1], stderr_pipe[1])); + + close(stdout_pipe[1]); + close(stderr_pipe[1]); + // close(stdin_pipe[1]); + + m_child_stdout_fd = stdout_pipe[0]; + m_child_stderr_fd = stderr_pipe[0]; + // m_child_stdin_fd = stdin_pipe[1]; + +#ifdef __APPLE__ + std::string OrcRuntimePath = + std::string(LLVM_SOURCE_DIR) + + "/build/lib/clang/20/lib/darwin/liborc_rt_osx.a"; +#else + std::string OrcRuntimePath = + std::string(LLVM_SOURCE_DIR) + + "/build/lib/x86_64-unknown-linux-gnu/liborc_rt.a"; +#endif + if (EPC) { + CB.SetTargetTriple(EPC->getTargetTriple().getTriple()); + JB = ExitOnError(clang::Interpreter::createLLJITBuilder(std::move(EPC), + OrcRuntimePath)); + } + } + + auto innerOrErr = + CudaEnabled + ? clang::Interpreter::createWithCUDA(std::move(*ciOrErr), + std::move(DeviceCI)) + : clang::Interpreter::create(std::move(*ciOrErr), std::move(JB)); +#else + if (outOfProcess) { + llvm::errs() + << "[CreateClangInterpreter]: No compatibility with out-of-process JIT" + << "(To enable recompile CppInterOp with " + "`-DCPPINTEROP_WITH_OOP_JIT=ON`)" + << "\n"; + return nullptr; + } auto innerOrErr = CudaEnabled ? clang::Interpreter::createWithCUDA(std::move(*ciOrErr), std::move(DeviceCI)) : clang::Interpreter::create(std::move(*ciOrErr)); +#endif if (!innerOrErr) { llvm::logAllUnhandledErrors(innerOrErr.takeError(), llvm::errs(), "Failed to build Interpreter:"); return nullptr; } + if (CudaEnabled) { if (auto Err = (*innerOrErr)->LoadDynamicLibrary("libcudart.so")) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), @@ -371,6 +450,12 @@ inline void codeComplete(std::vector& Results, #endif } +#ifdef CPPINTEROP_WITH_OOP_JIT +inline pid_t getExecutorPID() { + return getLastLaunchedExecutorPID(); +} +#endif // CPPINTEROP_WITH_OOP_JIT + } // namespace compat #include "CppInterOpInterpreter.h" @@ -395,7 +480,7 @@ class SynthesizingCodeRAII { "Failed to generate PTU:"); } }; -} +} // namespace compat #endif // CPPINTEROP_USE_REPL diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index bcbc12359..014335083 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -3031,7 +3031,7 @@ static std::string MakeResourcesPath() { } // namespace TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, - const std::vector& GpuArgs /*={}*/) { + const std::vector& GpuArgs /*={}*/, bool outOfProcess) { std::string MainExecutableName = sys::fs::getMainExecutable(nullptr, nullptr); std::string ResourceDir = MakeResourcesPath(); std::vector ClingArgv = {"-resource-dir", ResourceDir.c_str(), @@ -3075,7 +3075,7 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, auto I = new compat::Interpreter(ClingArgv.size(), &ClingArgv[0]); #else auto Interp = compat::Interpreter::create(static_cast(ClingArgv.size()), - ClingArgv.data()); + ClingArgv.data(), nullptr, {}, nullptr, true, outOfProcess); if (!Interp) return nullptr; auto* I = Interp.release(); @@ -3909,4 +3909,14 @@ int Undo(unsigned N) { #endif } +#ifdef CPPINTEROP_WITH_OOP_JIT +pid_t GetExecutorPID() { + return compat::getExecutorPID(); +} + +// void SendInputToChild(const std::string& input) { +// getInterp().sendInputToChild(input); +// } +#endif // CPPINTEROP_WITH_OOP_JIT + } // end namespace Cpp diff --git a/lib/CppInterOp/CppInterOpInterpreter.h b/lib/CppInterOp/CppInterOpInterpreter.h index d87eb33e1..5d9142820 100644 --- a/lib/CppInterOp/CppInterOpInterpreter.h +++ b/lib/CppInterOp/CppInterOpInterpreter.h @@ -39,6 +39,8 @@ #include "llvm/Support/raw_ostream.h" #include "llvm/TargetParser/Triple.h" +#include +#include #include #include @@ -145,12 +147,40 @@ class Interpreter { Interpreter(std::unique_ptr CI) : inner(std::move(CI)) {} + static void drainChildOutput() { + char buffer[4096]; + ssize_t bytes_read; + + if (compat::m_child_stdout_fd != -1) { + int flags = fcntl(compat::m_child_stdout_fd, F_GETFL, 0); + fcntl(compat::m_child_stdout_fd, F_SETFL, flags | O_NONBLOCK); + + while ((bytes_read = read(compat::m_child_stdout_fd, buffer, + sizeof(buffer))) > 0) { + fwrite(buffer, 1, bytes_read, stdout); + } + fflush(stdout); + } + + if (compat::m_child_stderr_fd != -1) { + int flags = fcntl(compat::m_child_stderr_fd, F_GETFL, 0); + fcntl(compat::m_child_stderr_fd, F_SETFL, flags | O_NONBLOCK); + + while ((bytes_read = read(compat::m_child_stderr_fd, buffer, + sizeof(buffer))) > 0) { + fwrite(buffer, 1, bytes_read, stderr); + } + fflush(stderr); + } + } + public: static std::unique_ptr create(int argc, const char* const* argv, const char* llvmdir = nullptr, const std::vector>& moduleExtensions = {}, - void* extraLibHandle = nullptr, bool noRuntime = true) { + void* extraLibHandle = nullptr, bool noRuntime = true, + bool outOfProcess = false) { // Initialize all targets (required for device offloading) llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargets(); @@ -158,7 +188,7 @@ class Interpreter { llvm::InitializeAllAsmPrinters(); std::vector vargs(argv + 1, argv + argc); - auto CI = compat::createClangInterpreter(vargs); + auto CI = compat::createClangInterpreter(vargs, outOfProcess); if (!CI) { llvm::errs() << "Interpreter creation failed\n"; return nullptr; @@ -186,15 +216,21 @@ class Interpreter { } llvm::Expected Parse(llvm::StringRef Code) { - return inner->Parse(Code); + auto outputOrErr = inner->Parse(Code); + drainChildOutput(); + return outputOrErr; } llvm::Error Execute(clang::PartialTranslationUnit& T) { - return inner->Execute(T); + auto outputOrErr = inner->Execute(T); + drainChildOutput(); + return outputOrErr; } llvm::Error ParseAndExecute(llvm::StringRef Code, clang::Value* V = nullptr) { - return inner->ParseAndExecute(Code, V); + auto outputOrErr = inner->ParseAndExecute(Code, V); + drainChildOutput(); + return outputOrErr; } llvm::Error Undo(unsigned N = 1) { return compat::Undo(*inner, N); } @@ -463,6 +499,13 @@ class Interpreter { return kSuccess; } + // void sendInputToChild(const std::string& input) { + // if (compat::m_child_stdin_fd != -1) { + // write(compat::m_child_stdin_fd, input.c_str(), input.size()); + // write(compat::m_child_stdin_fd, "\n", 1); + // } + // } + }; // Interpreter } // namespace Cpp diff --git a/patches/llvm/clang20-2-out-of-process-jit-execution.patch b/patches/llvm/clang20-2-out-of-process-jit-execution.patch new file mode 100644 index 000000000..8aca0ab23 --- /dev/null +++ b/patches/llvm/clang20-2-out-of-process-jit-execution.patch @@ -0,0 +1,1634 @@ +diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h +index f8663e3193..78dff1165d 100644 +--- a/clang/include/clang/Interpreter/Interpreter.h ++++ b/clang/include/clang/Interpreter/Interpreter.h +@@ -20,6 +20,7 @@ + + #include "llvm/ADT/DenseMap.h" + #include "llvm/ExecutionEngine/JITSymbol.h" ++#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" + #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" + #include "llvm/Support/Error.h" + #include +@@ -136,10 +137,14 @@ protected: + public: + virtual ~Interpreter(); + static llvm::Expected> +- create(std::unique_ptr CI); ++ create(std::unique_ptr CI, ++ std::unique_ptr JITBuilder = nullptr); + static llvm::Expected> + createWithCUDA(std::unique_ptr CI, + std::unique_ptr DCI); ++ static llvm::Expected> ++ createLLJITBuilder(std::unique_ptr EPC, ++ llvm::StringRef OrcRuntimePath); + const ASTContext &getASTContext() const; + ASTContext &getASTContext(); + const CompilerInstance *getCompilerInstance() const; +diff --git a/clang/include/clang/Interpreter/RemoteJITUtils.h b/clang/include/clang/Interpreter/RemoteJITUtils.h +new file mode 100644 +index 0000000000..85fb66ddfa +--- /dev/null ++++ b/clang/include/clang/Interpreter/RemoteJITUtils.h +@@ -0,0 +1,43 @@ ++//===-- RemoteJITUtils.h - Utilities for remote-JITing ----------*- 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 ++// ++//===----------------------------------------------------------------------===// ++// ++// Utilities for ExecutorProcessControl-based remote JITing with Orc and ++// JITLink. ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef LLVM_CLANG_INTERPRETER_REMOTEJITUTILS_H ++#define LLVM_CLANG_INTERPRETER_REMOTEJITUTILS_H ++ ++#include "llvm/ADT/StringRef.h" ++#include "llvm/ExecutionEngine/Orc/Core.h" ++#include "llvm/ExecutionEngine/Orc/Layer.h" ++#include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h" ++#include "llvm/Support/Error.h" ++ ++#include ++#include ++#include ++ ++llvm::Expected> ++launchExecutor(llvm::StringRef ExecutablePath, bool UseSharedMemory, ++ llvm::StringRef SlabAllocateSizeString, int stdin_fd, int stdout_fd, int stderr_fd); ++ ++/// Create a JITLinkExecutor that connects to the given network address ++/// through a TCP socket. A valid NetworkAddress provides hostname and port, ++/// e.g. localhost:20000. ++llvm::Expected> ++connectTCPSocket(llvm::StringRef NetworkAddress, bool UseSharedMemory, ++ llvm::StringRef SlabAllocateSizeString); ++ ++ ++/// Get the PID of the last launched executor. ++/// This is useful for debugging or for cleanup purposes. ++pid_t getLastLaunchedExecutorPID(); ++ ++#endif // LLVM_CLANG_INTERPRETER_REMOTEJITUTILS_H +diff --git a/clang/lib/Interpreter/CMakeLists.txt b/clang/lib/Interpreter/CMakeLists.txt +index bf70cdfbee..38cf139fa8 100644 +--- a/clang/lib/Interpreter/CMakeLists.txt ++++ b/clang/lib/Interpreter/CMakeLists.txt +@@ -27,6 +27,7 @@ add_clang_library(clangInterpreter + Interpreter.cpp + InterpreterValuePrinter.cpp + InterpreterUtils.cpp ++ RemoteJITUtils.cpp + Value.cpp + ${WASM_SRC} + PARTIAL_SOURCES_INTENDED +diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp +index 3b81f9d701..eaa5c31cb6 100644 +--- a/clang/lib/Interpreter/Interpreter.cpp ++++ b/clang/lib/Interpreter/Interpreter.cpp +@@ -46,6 +46,7 @@ + #include "clang/Sema/Lookup.h" + #include "clang/Serialization/ObjectFilePCHContainerReader.h" + #include "llvm/ExecutionEngine/JITSymbol.h" ++#include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" + #include "llvm/ExecutionEngine/Orc/LLJIT.h" + #include "llvm/IR/Module.h" + #include "llvm/Support/Errc.h" +@@ -455,10 +456,11 @@ const char *const Runtimes = R"( + )"; + + llvm::Expected> +-Interpreter::create(std::unique_ptr CI) { ++Interpreter::create(std::unique_ptr CI, ++ std::unique_ptr JB) { + llvm::Error Err = llvm::Error::success(); +- auto Interp = +- std::unique_ptr(new Interpreter(std::move(CI), Err)); ++ auto Interp = std::unique_ptr( ++ new Interpreter(std::move(CI), Err, JB ? std::move(JB) : nullptr)); + if (Err) + return std::move(Err); + +@@ -467,6 +469,8 @@ Interpreter::create(std::unique_ptr CI) { + auto PTU = Interp->Parse(Runtimes); + if (!PTU) + return PTU.takeError(); ++ ++ if (llvm::Error Err = Interp->Execute(*PTU)) return Err; + Interp->markUserCodeStart(); + + Interp->ValuePrintingInfo.resize(4); +@@ -617,6 +621,25 @@ createJITTargetMachineBuilder(const std::string &TT) { + return llvm::orc::JITTargetMachineBuilder(llvm::Triple(TT)); + } + ++llvm::Expected> ++Interpreter::createLLJITBuilder( ++ std::unique_ptr EPC, ++ llvm::StringRef OrcRuntimePath) { ++ const std::string &TT = EPC->getTargetTriple().getTriple(); ++ auto JTMB = createJITTargetMachineBuilder(TT); ++ if (!JTMB) ++ return JTMB.takeError(); ++ auto JB = IncrementalExecutor::createDefaultJITBuilder(std::move(*JTMB)); ++ if (!JB) ++ return JB.takeError(); ++ ++ (*JB)->setExecutorProcessControl(std::move(EPC)); ++ (*JB)->setPlatformSetUp( ++ llvm::orc::ExecutorNativePlatform(OrcRuntimePath.str())); ++ ++ return std::move(*JB); ++} ++ + llvm::Error Interpreter::CreateExecutor() { + if (IncrExecutor) + return llvm::make_error("Operation failed. " +@@ -757,11 +780,11 @@ llvm::Error Interpreter::LoadDynamicLibrary(const char *name) { + 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)); ++ if (auto DLSG = llvm::orc::EPCDynamicLibrarySearchGenerator::Load( ++ EE->getExecutionSession(), name)) ++ // FIXME: Eventually we should put each library in its own JITDylib and ++ // turn off process symbols by default. ++ EE->getProcessSymbolsJITDylib()->addGenerator(std::move(*DLSG)); + else + return DLSG.takeError(); + #endif +diff --git a/clang/lib/Interpreter/RemoteJITUtils.cpp b/clang/lib/Interpreter/RemoteJITUtils.cpp +new file mode 100644 +index 0000000000..095a3a3d49 +--- /dev/null ++++ b/clang/lib/Interpreter/RemoteJITUtils.cpp +@@ -0,0 +1,297 @@ ++//===-- RemoteJITUtils.cpp - Utilities for remote-JITing --------*- 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 ++// ++//===----------------------------------------------------------------------===// ++// ++// FIXME: Unify this code with similar functionality in llvm-jitlink. ++// ++//===----------------------------------------------------------------------===// ++ ++#include "clang/Interpreter/RemoteJITUtils.h" ++ ++#include "llvm/ADT/StringExtras.h" ++#include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" ++#include "llvm/ExecutionEngine/Orc/EPCDebugObjectRegistrar.h" ++#include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" ++#include "llvm/ExecutionEngine/Orc/MapperJITLinkMemoryManager.h" ++#include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" ++#include "llvm/ExecutionEngine/Orc/Shared/SimpleRemoteEPCUtils.h" ++#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" ++#include "llvm/Support/FileSystem.h" ++#include "llvm/Support/Path.h" ++ ++#ifdef LLVM_ON_UNIX ++#include ++#include ++#include ++#include ++#include ++#endif // LLVM_ON_UNIX ++ ++using namespace llvm; ++using namespace llvm::orc; ++ ++static std::atomic LaunchedExecutorPID{-1}; ++ ++Expected getSlabAllocSize(StringRef SizeString) { ++ SizeString = SizeString.trim(); ++ ++ uint64_t Units = 1024; ++ ++ if (SizeString.ends_with_insensitive("kb")) ++ SizeString = SizeString.drop_back(2).rtrim(); ++ else if (SizeString.ends_with_insensitive("mb")) { ++ Units = 1024 * 1024; ++ SizeString = SizeString.drop_back(2).rtrim(); ++ } else if (SizeString.ends_with_insensitive("gb")) { ++ Units = 1024 * 1024 * 1024; ++ SizeString = SizeString.drop_back(2).rtrim(); ++ } ++ ++ uint64_t SlabSize = 0; ++ if (SizeString.getAsInteger(10, SlabSize)) ++ return make_error("Invalid numeric format for slab size", ++ inconvertibleErrorCode()); ++ ++ return SlabSize * Units; ++} ++ ++Expected> ++createSharedMemoryManager(SimpleRemoteEPC &SREPC, ++ StringRef SlabAllocateSizeString) { ++ SharedMemoryMapper::SymbolAddrs SAs; ++ if (auto Err = SREPC.getBootstrapSymbols( ++ {{SAs.Instance, rt::ExecutorSharedMemoryMapperServiceInstanceName}, ++ {SAs.Reserve, ++ rt::ExecutorSharedMemoryMapperServiceReserveWrapperName}, ++ {SAs.Initialize, ++ rt::ExecutorSharedMemoryMapperServiceInitializeWrapperName}, ++ {SAs.Deinitialize, ++ rt::ExecutorSharedMemoryMapperServiceDeinitializeWrapperName}, ++ {SAs.Release, ++ rt::ExecutorSharedMemoryMapperServiceReleaseWrapperName}})) ++ return std::move(Err); ++ ++#ifdef _WIN32 ++ size_t SlabSize = 1024 * 1024; ++#else ++ size_t SlabSize = 1024 * 1024 * 1024; ++#endif ++ ++ if (!SlabAllocateSizeString.empty()) { ++ if (auto S = getSlabAllocSize(SlabAllocateSizeString)) ++ SlabSize = *S; ++ else ++ return S.takeError(); ++ } ++ ++ return MapperJITLinkMemoryManager::CreateWithMapper( ++ SlabSize, SREPC, SAs); ++} ++ ++Expected> ++launchExecutor(StringRef ExecutablePath, bool UseSharedMemory, ++ llvm::StringRef SlabAllocateSizeString, int stdin_fd = 0, int stdout_fd = 1, int stderr_fd = 2) { ++#ifndef LLVM_ON_UNIX ++ return make_error("-" + ExecutablePath + ++ " not supported on non-unix platforms", ++ inconvertibleErrorCode()); ++#elif !LLVM_ENABLE_THREADS ++ return make_error( ++ "-" + ExecutablePath + ++ " requires threads, but LLVM was built with " ++ "LLVM_ENABLE_THREADS=Off", ++ inconvertibleErrorCode()); ++#else ++ if (!sys::fs::can_execute(ExecutablePath)) ++ return make_error( ++ formatv("Specified executor invalid: {0}", ExecutablePath), ++ inconvertibleErrorCode()); ++ ++ constexpr int ReadEnd = 0; ++ constexpr int WriteEnd = 1; ++ ++ // Pipe FDs for RPC ++ int ToExecutor[2]; ++ int FromExecutor[2]; ++ ++ pid_t ChildPID; ++ ++ // Create pipes for RPC communication ++ if (pipe(ToExecutor) != 0 || pipe(FromExecutor) != 0) ++ return make_error("Unable to create RPC pipes", ++ inconvertibleErrorCode()); ++ ++ ChildPID = fork(); ++ ++ if (ChildPID == 0) { ++ // In the child... ++ ++ // Close parent ends of RPC pipes ++ close(ToExecutor[WriteEnd]); ++ close(FromExecutor[ReadEnd]); ++ ++ // Redirect stdin, stdout, and stderr to provided file descriptors ++ if (stdin_fd != 0) { ++ dup2(stdin_fd, STDIN_FILENO); ++ if (stdin_fd != STDIN_FILENO) ++ close(stdin_fd); ++ } ++ ++ if (stdout_fd != 1) { ++ dup2(stdout_fd, STDOUT_FILENO); ++ if (stdout_fd != STDOUT_FILENO) ++ close(stdout_fd); ++ ++ setvbuf(stdout, NULL, _IONBF, 0); ++ } ++ ++ if (stderr_fd != 2) { ++ dup2(stderr_fd, STDERR_FILENO); ++ if (stderr_fd != STDERR_FILENO) ++ close(stderr_fd); ++ ++ setvbuf(stderr, NULL, _IONBF, 0); ++ } ++ ++ // Execute the child process ++ std::unique_ptr ExecutorPath, FDSpecifier; ++ { ++ ExecutorPath = std::make_unique(ExecutablePath.size() + 1); ++ strcpy(ExecutorPath.get(), ExecutablePath.data()); ++ ++ std::string FDSpecifierStr("filedescs="); ++ FDSpecifierStr += utostr(ToExecutor[ReadEnd]); ++ FDSpecifierStr += ','; ++ FDSpecifierStr += utostr(FromExecutor[WriteEnd]); ++ FDSpecifier = std::make_unique(FDSpecifierStr.size() + 1); ++ strcpy(FDSpecifier.get(), FDSpecifierStr.c_str()); ++ } ++ ++ char *const Args[] = {ExecutorPath.get(), FDSpecifier.get(), nullptr}; ++ int RC = execvp(ExecutorPath.get(), Args); ++ if (RC != 0) { ++ errs() << "unable to launch out-of-process executor \"" ++ << ExecutorPath.get() << "\"\n"; ++ exit(1); ++ } ++ } else { ++ LaunchedExecutorPID = ChildPID; ++ } ++ ++ // Close child ends of RPC pipes ++ close(ToExecutor[ReadEnd]); ++ close(FromExecutor[WriteEnd]); ++ ++ auto S = SimpleRemoteEPC::Setup(); ++ if (UseSharedMemory) ++ S.CreateMemoryManager = [SlabAllocateSizeString](SimpleRemoteEPC &EPC) { ++ return createSharedMemoryManager(EPC, SlabAllocateSizeString); ++ }; ++ ++ auto EPC = SimpleRemoteEPC::Create( ++ std::make_unique(std::nullopt), ++ std::move(S), FromExecutor[ReadEnd], ToExecutor[WriteEnd]); ++ ++ return EPC; ++#endif ++} ++ ++#if LLVM_ON_UNIX && LLVM_ENABLE_THREADS ++ ++static Expected connectTCPSocketImpl(std::string Host, ++ std::string PortStr) { ++ addrinfo *AI; ++ addrinfo Hints{}; ++ Hints.ai_family = AF_INET; ++ Hints.ai_socktype = SOCK_STREAM; ++ Hints.ai_flags = AI_NUMERICSERV; ++ ++ if (int EC = getaddrinfo(Host.c_str(), PortStr.c_str(), &Hints, &AI)) ++ return make_error( ++ formatv("address resolution failed ({0})", gai_strerror(EC)), ++ inconvertibleErrorCode()); ++ // Cycle through the returned addrinfo structures and connect to the first ++ // reachable endpoint. ++ int SockFD; ++ addrinfo *Server; ++ for (Server = AI; Server != nullptr; Server = Server->ai_next) { ++ // socket might fail, e.g. if the address family is not supported. Skip to ++ // the next addrinfo structure in such a case. ++ if ((SockFD = socket(AI->ai_family, AI->ai_socktype, AI->ai_protocol)) < 0) ++ continue; ++ ++ // If connect returns null, we exit the loop with a working socket. ++ if (connect(SockFD, Server->ai_addr, Server->ai_addrlen) == 0) ++ break; ++ ++ close(SockFD); ++ } ++ freeaddrinfo(AI); ++ ++ // If we reached the end of the loop without connecting to a valid endpoint, ++ // dump the last error that was logged in socket() or connect(). ++ if (Server == nullptr) ++ return make_error("invalid hostname", ++ inconvertibleErrorCode()); ++ ++ return SockFD; ++} ++#endif ++ ++Expected> ++connectTCPSocket(StringRef NetworkAddress, bool UseSharedMemory, ++ llvm::StringRef SlabAllocateSizeString) { ++#ifndef LLVM_ON_UNIX ++ // FIXME: Add TCP support for Windows. ++ return make_error("-" + NetworkAddress + ++ " not supported on non-unix platforms", ++ inconvertibleErrorCode()); ++#elif !LLVM_ENABLE_THREADS ++ // Out of process mode using SimpleRemoteEPC depends on threads. ++ return make_error( ++ "-" + NetworkAddress + ++ " requires threads, but LLVM was built with " ++ "LLVM_ENABLE_THREADS=Off", ++ inconvertibleErrorCode()); ++#else ++ ++ auto CreateErr = [NetworkAddress](Twine Details) { ++ return make_error( ++ formatv("Failed to connect TCP socket '{0}': {1}", NetworkAddress, ++ Details), ++ inconvertibleErrorCode()); ++ }; ++ ++ StringRef Host, PortStr; ++ std::tie(Host, PortStr) = NetworkAddress.split(':'); ++ if (Host.empty()) ++ return CreateErr("Host name for -" + NetworkAddress + " can not be empty"); ++ if (PortStr.empty()) ++ return CreateErr("Port number in -" + NetworkAddress + " can not be empty"); ++ int Port = 0; ++ if (PortStr.getAsInteger(10, Port)) ++ return CreateErr("Port number '" + PortStr + "' is not a valid integer"); ++ ++ Expected SockFD = connectTCPSocketImpl(Host.str(), PortStr.str()); ++ if (!SockFD) ++ return SockFD.takeError(); ++ ++ auto S = SimpleRemoteEPC::Setup(); ++ if (UseSharedMemory) ++ S.CreateMemoryManager = [SlabAllocateSizeString](SimpleRemoteEPC &EPC) { ++ return createSharedMemoryManager(EPC, SlabAllocateSizeString); ++ }; ++ ++ return SimpleRemoteEPC::Create( ++ std::make_unique(std::nullopt), ++ std::move(S), *SockFD, *SockFD); ++#endif ++} ++ ++pid_t getLastLaunchedExecutorPID() { ++ return LaunchedExecutorPID; ++} +diff --git a/clang/test/Interpreter/out-of-process.cpp b/clang/test/Interpreter/out-of-process.cpp +new file mode 100644 +index 0000000000..edc4c3fee0 +--- /dev/null ++++ b/clang/test/Interpreter/out-of-process.cpp +@@ -0,0 +1,88 @@ ++// REQUIRES: host-supports-jit ++ ++// RUN: cat %s | clang-repl -oop-executor -orc-runtime | FileCheck %s ++ ++extern "C" int printf(const char *, ...); ++ ++int intVar = 0; ++double doubleVar = 3.14; ++%undo ++double doubleVar = 2.71; ++ ++auto r1 = printf("intVar = %d\n", intVar); ++// CHECK: intVar = 0 ++auto r2 = printf("doubleVar = %.2f\n", doubleVar); ++// CHECK: doubleVar = 2.71 ++ ++// Test redefinition with inline and static functions. ++int add(int a, int b, int c) { return a + b + c; } ++%undo // Revert to the initial version of add ++inline int add(int a, int b) { return a + b; } ++ ++auto r3 = printf("add(1, 2) = %d\n", add(1, 2)); ++// CHECK-NEXT: add(1, 2) = 3 ++ ++// Test inline and lambda functions with variations. ++inline int square(int x) { return x * x; } ++auto lambdaSquare = [](int x) { return x * x; }; ++auto lambdaMult = [](int a, int b) { return a * b; }; ++ ++auto r4 = printf("square(4) = %d\n", square(4)); ++// CHECK-NEXT: square(4) = 16 ++auto lambda_r1 = printf("lambdaSquare(5) = %d\n", lambdaSquare(5)); ++// CHECK-NEXT: lambdaSquare(5) = 25 ++auto lambda_r2 = printf("lambdaMult(2, 3) = %d\n", lambdaMult(2, 3)); ++// CHECK-NEXT: lambdaMult(2, 3) = 6 ++ ++%undo // Undo previous lambda assignments ++auto lambda_r3 = lambdaMult(3, 4); // Should fail or revert to the original lambda ++ ++// Test weak and strong symbol linkage. ++int __attribute__((weak)) weakFunc() { return 42; } ++int strongFunc() { return 100; } ++%undo // Revert the weak function ++ ++auto r5 = printf("weakFunc() = %d\n", weakFunc()); ++// CHECK: weakFunc() = 42 ++auto r6 = printf("strongFunc() = %d\n", strongFunc()); ++// CHECK-NEXT: strongFunc() = 100 ++ ++// Weak variable linkage with different types. ++int varA = 20; ++static __typeof(varA) weakVarA __attribute__((__weakref__("varA"))); ++char charVar = 'c'; ++static __typeof(charVar) weakCharVar __attribute__((__weakref__("charVar"))); ++auto r7 = printf("weakVarA = %d\n", weakVarA); ++// CHECK: weakVarA = 20 ++auto r8 = printf("weakCharVar = %c\n", weakCharVar); ++// CHECK-NEXT: weakCharVar = c ++ ++// Test complex lambdas with captures. ++int captureVar = 5; ++auto captureLambda = [](int x) { return x + captureVar; };` ++int result1 = captureLambda(10); ++%undo // Undo capture lambda ++ ++auto r9 = printf("captureLambda(10) = %d\n", result1); ++// CHECK: captureLambda(10) = 15 ++ ++// Multiline statement test with arithmetic operations. ++int sum = \ ++ 5 + \ ++ 10; ++int prod = sum * 2; ++auto r10 = printf("sum = %d, prod = %d\n", sum, prod); ++// CHECK: sum = 15, prod = 30 ++ ++// Test multiline functions and macro behavior. ++#define MULTIPLY(a, b) ((a) * (b)) ++ ++int complexFunc(int x) \ ++{ \ ++ return MULTIPLY(x, 2) + x; \ ++} ++ ++auto r11 = printf("complexFunc(5) = %d\n", complexFunc(5)); ++// CHECK: complexFunc(5) = 15 ++ ++%quit +diff --git a/clang/tools/clang-repl/CMakeLists.txt b/clang/tools/clang-repl/CMakeLists.txt +index f9a911b0ae..68d86dd98c 100644 +--- a/clang/tools/clang-repl/CMakeLists.txt ++++ b/clang/tools/clang-repl/CMakeLists.txt +@@ -4,7 +4,9 @@ set( LLVM_LINK_COMPONENTS + LineEditor + Option + OrcJIT ++ OrcShared + Support ++ TargetParser + ) + + add_clang_tool(clang-repl +diff --git a/clang/tools/clang-repl/ClangRepl.cpp b/clang/tools/clang-repl/ClangRepl.cpp +index 7af8e4f25d..33f6839fa8 100644 +--- a/clang/tools/clang-repl/ClangRepl.cpp ++++ b/clang/tools/clang-repl/ClangRepl.cpp +@@ -10,7 +10,11 @@ + // + //===----------------------------------------------------------------------===// + ++#include "clang/Interpreter/RemoteJITUtils.h" ++ + #include "clang/Basic/Diagnostic.h" ++#include "clang/Basic/Version.h" ++#include "clang/Config/config.h" + #include "clang/Frontend/CompilerInstance.h" + #include "clang/Frontend/FrontendDiagnostic.h" + #include "clang/Interpreter/CodeCompletion.h" +@@ -24,7 +28,13 @@ + #include "llvm/Support/ManagedStatic.h" // llvm_shutdown + #include "llvm/Support/Signals.h" + #include "llvm/Support/TargetSelect.h" ++#include "llvm/TargetParser/Host.h" + #include ++#include ++#include ++#include ++ ++#include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h" + + // Disable LSan for this test. + // FIXME: Re-enable once we can assume GCC 13.2 or higher. +@@ -34,10 +44,36 @@ + LLVM_ATTRIBUTE_USED int __lsan_is_turned_off() { return 1; } + #endif + ++#define DEBUG_TYPE "clang-repl" ++ + static llvm::cl::opt CudaEnabled("cuda", llvm::cl::Hidden); + static llvm::cl::opt CudaPath("cuda-path", llvm::cl::Hidden); + static llvm::cl::opt OffloadArch("offload-arch", llvm::cl::Hidden); +- ++static llvm::cl::OptionCategory OOPCategory("Out-of-process Execution Options"); ++static llvm::cl::opt SlabAllocateSizeString( ++ "slab-allocate", ++ llvm::cl::desc("Allocate from a slab of the given size " ++ "(allowable suffixes: Kb, Mb, Gb. default = " ++ "Kb)"), ++ llvm::cl::init(""), llvm::cl::cat(OOPCategory)); ++static llvm::cl::opt ++ OOPExecutor("oop-executor", ++ llvm::cl::desc("Launch an out-of-process executor to run code"), ++ llvm::cl::init(""), llvm::cl::ValueOptional, ++ llvm::cl::cat(OOPCategory)); ++static llvm::cl::opt OOPExecutorConnect( ++ "oop-executor-connect", ++ llvm::cl::desc( ++ "Connect to an out-of-process executor through a TCP socket"), ++ llvm::cl::value_desc(":")); ++static llvm::cl::opt ++ OrcRuntimePath("orc-runtime", llvm::cl::desc("Path to the ORC runtime"), ++ llvm::cl::init(""), llvm::cl::ValueOptional, ++ llvm::cl::cat(OOPCategory)); ++static llvm::cl::opt UseSharedMemory( ++ "use-shared-memory", ++ llvm::cl::desc("Use shared memory to transfer generated code and data"), ++ llvm::cl::init(false), llvm::cl::cat(OOPCategory)); + static llvm::cl::list + ClangArgs("Xcc", + llvm::cl::desc("Argument to pass to the CompilerInvocation"), +@@ -47,6 +83,72 @@ static llvm::cl::opt OptHostSupportsJit("host-supports-jit", + static llvm::cl::list OptInputs(llvm::cl::Positional, + llvm::cl::desc("[code to run]")); + ++static llvm::Error sanitizeOopArguments(const char *ArgV0) { ++ // Only one of -oop-executor and -oop-executor-connect can be used. ++ if (!!OOPExecutor.getNumOccurrences() && ++ !!OOPExecutorConnect.getNumOccurrences()) ++ return llvm::make_error( ++ "Only one of -" + OOPExecutor.ArgStr + " and -" + ++ OOPExecutorConnect.ArgStr + " can be specified", ++ llvm::inconvertibleErrorCode()); ++ ++ llvm::Triple SystemTriple(llvm::sys::getProcessTriple()); ++ // TODO: Remove once out-of-process execution support is implemented for ++ // non-Unix platforms. ++ if ((!SystemTriple.isOSBinFormatELF() && ++ !SystemTriple.isOSBinFormatMachO()) && ++ (OOPExecutor.getNumOccurrences() || ++ OOPExecutorConnect.getNumOccurrences())) ++ return llvm::make_error( ++ "Out-of-process execution is only supported on Unix platforms", ++ llvm::inconvertibleErrorCode()); ++ ++ // If -slab-allocate is passed, check that we're not trying to use it in ++ // -oop-executor or -oop-executor-connect mode. ++ // ++ // FIXME: Remove once we enable remote slab allocation. ++ if (SlabAllocateSizeString != "") { ++ if (OOPExecutor.getNumOccurrences() || ++ OOPExecutorConnect.getNumOccurrences()) ++ return llvm::make_error( ++ "-slab-allocate cannot be used with -oop-executor or " ++ "-oop-executor-connect", ++ llvm::inconvertibleErrorCode()); ++ } ++ ++ // Out-of-process executors require the ORC runtime. ++ if (OrcRuntimePath.empty() && (OOPExecutor.getNumOccurrences() || ++ OOPExecutorConnect.getNumOccurrences())) { ++ llvm::SmallString<256> BasePath(llvm::sys::fs::getMainExecutable( ++ ArgV0, reinterpret_cast(&sanitizeOopArguments))); ++ llvm::sys::path::remove_filename(BasePath); // Remove clang-repl filename. ++ llvm::sys::path::remove_filename(BasePath); // Remove ./bin directory. ++ llvm::sys::path::append(BasePath, CLANG_INSTALL_LIBDIR_BASENAME, "clang", ++ CLANG_VERSION_MAJOR_STRING); ++ if (SystemTriple.isOSBinFormatELF()) ++ OrcRuntimePath = ++ BasePath.str().str() + "/lib/x86_64-unknown-linux-gnu/liborc_rt.a"; ++ else if (SystemTriple.isOSBinFormatMachO()) ++ OrcRuntimePath = BasePath.str().str() + "/lib/darwin/liborc_rt_osx.a"; ++ else ++ return llvm::make_error( ++ "Out-of-process execution is not supported on non-unix platforms", ++ llvm::inconvertibleErrorCode()); ++ } ++ ++ // If -oop-executor was used but no value was specified then use a sensible ++ // default. ++ if (!!OOPExecutor.getNumOccurrences() && OOPExecutor.empty()) { ++ llvm::SmallString<256> OOPExecutorPath(llvm::sys::fs::getMainExecutable( ++ ArgV0, reinterpret_cast(&sanitizeOopArguments))); ++ llvm::sys::path::remove_filename(OOPExecutorPath); ++ llvm::sys::path::append(OOPExecutorPath, "llvm-jitlink-executor"); ++ OOPExecutor = OOPExecutorPath.str().str(); ++ } ++ ++ return llvm::Error::success(); ++} ++ + static void LLVMErrorHandler(void *UserData, const char *Message, + bool GenCrashDiag) { + auto &Diags = *static_cast(UserData); +@@ -138,6 +240,39 @@ ReplListCompleter::operator()(llvm::StringRef Buffer, size_t Pos, + } + + llvm::ExitOnError ExitOnErr; ++ ++static int g_child_stdout_fd = -1; ++static int g_child_stderr_fd = -1; ++ ++void drainChildOutput() { ++ char buffer[4096]; ++ ssize_t bytes_read; ++ ++ // Drain stdout pipe ++ if (g_child_stdout_fd != -1) { ++ // Make non-blocking to avoid hanging if no data ++ int flags = fcntl(g_child_stdout_fd, F_GETFL, 0); ++ fcntl(g_child_stdout_fd, F_SETFL, flags | O_NONBLOCK); ++ ++ while ((bytes_read = read(g_child_stdout_fd, buffer, sizeof(buffer))) > 0) { ++ fwrite(buffer, 1, bytes_read, stdout); ++ } ++ fflush(stdout); ++ } ++ ++ // Drain stderr pipe ++ if (g_child_stderr_fd != -1) { ++ int flags = fcntl(g_child_stderr_fd, F_GETFL, 0); ++ fcntl(g_child_stderr_fd, F_SETFL, flags | O_NONBLOCK); ++ ++ while ((bytes_read = read(g_child_stderr_fd, buffer, sizeof(buffer))) > 0) { ++ fwrite(buffer, 1, bytes_read, stderr); ++ } ++ fflush(stderr); ++ } ++} ++ ++ + int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + +@@ -183,6 +318,41 @@ int main(int argc, const char **argv) { + DeviceCI = ExitOnErr(CB.CreateCudaDevice()); + } + ++ ExitOnErr(sanitizeOopArguments(argv[0])); ++ ++ std::unique_ptr EPC; ++ if (OOPExecutor.getNumOccurrences()) { ++ // Launch an out-of-process executor locally in a child process. ++ std::cout << "OOPExecutor: " << OOPExecutor << std::endl; ++ std::cout << "UseSharedMemory: " << UseSharedMemory << std::endl; ++ std::cout << "SlabAllocateSizeString: " << SlabAllocateSizeString << std::endl; ++ int stdout_pipe[2], stderr_pipe[2]; ++ if (pipe(stdout_pipe) != 0 || pipe(stderr_pipe) != 0) { ++ llvm::errs() << "Failed to create pipes for child process I/O\n"; ++ return 0; ++ } ++ EPC = ExitOnErr( ++ launchExecutor(OOPExecutor, UseSharedMemory, SlabAllocateSizeString, 0, stdout_pipe[1], stderr_pipe[1])); ++ ++ close(stdout_pipe[1]); ++ close(stderr_pipe[1]); ++ ++ g_child_stdout_fd = stdout_pipe[0]; ++ g_child_stderr_fd = stderr_pipe[0]; ++ ++ } else if (OOPExecutorConnect.getNumOccurrences()) { ++ EPC = ExitOnErr(connectTCPSocket(OOPExecutorConnect, UseSharedMemory, ++ SlabAllocateSizeString)); ++ } ++ ++ std::unique_ptr JB; ++ if (EPC) { ++ CB.SetTargetTriple(EPC->getTargetTriple().getTriple()); ++ std::cout << "OrcRuntimePath: " << OrcRuntimePath << std::endl; ++ JB = ExitOnErr( ++ clang::Interpreter::createLLJITBuilder(std::move(EPC), OrcRuntimePath)); ++ } ++ + // FIXME: Investigate if we could use runToolOnCodeWithArgs from tooling. It + // can replace the boilerplate code for creation of the compiler instance. + std::unique_ptr CI; +@@ -214,6 +384,9 @@ int main(int argc, const char **argv) { + auto CudaRuntimeLibPath = CudaPath + "/lib/libcudart.so"; + ExitOnErr(Interp->LoadDynamicLibrary(CudaRuntimeLibPath.c_str())); + } ++ } else if (JB) { ++ Interp = ++ ExitOnErr(clang::Interpreter::create(std::move(CI), std::move(JB))); + } else + Interp = ExitOnErr(clang::Interpreter::create(std::move(CI))); + +@@ -257,6 +430,7 @@ int main(int argc, const char **argv) { + } + + Input = ""; ++ drainChildOutput(); + LE.setPrompt("clang-repl> "); + } + } +diff --git a/failed.patch b/failed.patch +new file mode 100644 +index 0000000000..cb67dece16 +--- /dev/null ++++ b/failed.patch +@@ -0,0 +1,74 @@ ++--- ++ clang/include/clang/Interpreter/Interpreter.h | 2 +- ++ clang/lib/Interpreter/Interpreter.cpp | 13 ++++++++++--- ++ clang/tools/clang-repl/ClangRepl.cpp | 4 +++- ++ 3 files changed, 14 insertions(+), 5 deletions(-) ++ ++diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h ++index 42486304083f4..f7083742a672e 100644 ++--- a/clang/include/clang/Interpreter/Interpreter.h +++++ b/clang/include/clang/Interpreter/Interpreter.h ++@@ -152,7 +152,7 @@ class Interpreter { ++ llvm::Error Undo(unsigned N = 1); ++ ++ /// Link a dynamic library ++- llvm::Error LoadDynamicLibrary(const char *name); +++ llvm::Error LoadDynamicLibrary(const char *name, bool UseEPC = false); ++ ++ /// \returns the \c ExecutorAddr of a \c GlobalDecl. This interface uses ++ /// the CodeGenModule's internal mangling cache to avoid recomputing the ++diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp ++index c281c917cff4b..faa1727d12731 100644 ++--- a/clang/lib/Interpreter/Interpreter.cpp +++++ b/clang/lib/Interpreter/Interpreter.cpp ++@@ -718,13 +718,20 @@ llvm::Error Interpreter::Undo(unsigned N) { ++ return llvm::Error::success(); ++ } ++ ++-llvm::Error Interpreter::LoadDynamicLibrary(const char *name) { +++llvm::Error Interpreter::LoadDynamicLibrary(const char *name, bool UseEPC) { ++ auto EE = getExecutionEngine(); ++ if (!EE) ++ return EE.takeError(); ++ ++- if (auto DLSG = llvm::orc::EPCDynamicLibrarySearchGenerator::Load( ++- EE->getExecutionSession(), name)) +++ auto &DL = EE->getDataLayout(); +++ if (UseEPC) { +++ if (auto DLSG = llvm::orc::EPCDynamicLibrarySearchGenerator::Load( +++ EE->getExecutionSession(), name)) +++ EE->getMainJITDylib().addGenerator(std::move(*DLSG)); +++ else +++ return DLSG.takeError(); +++ } else if (auto DLSG = llvm::orc::DynamicLibrarySearchGenerator::Load( +++ name, DL.getGlobalPrefix())) ++ EE->getMainJITDylib().addGenerator(std::move(*DLSG)); ++ else ++ return DLSG.takeError(); ++diff --git a/clang/tools/clang-repl/ClangRepl.cpp b/clang/tools/clang-repl/ClangRepl.cpp ++index 7fcd5f1730141..13414da734127 100644 ++--- a/clang/tools/clang-repl/ClangRepl.cpp ++ ++-llvm::Error Interpreter::LoadDynamicLibrary(const char *name) { +++llvm::Error Interpreter::LoadDynamicLibrary(const char *name, bool UseEPC) { ++ auto EE = getExecutionEngine(); ++ if (!EE) ++ return EE.takeError(); ++ ++- if (auto DLSG = llvm::orc::EPCDynamicLibrarySearchGenerator::Load( ++- EE->getExecutionSession(), name)) +++ auto &DL = EE->getDataLayout(); +++ if (UseEPC) { +++ if (auto DLSG = llvm::orc::EPCDynamicLibrarySearchGenerator::Load( +++ EE->getExecutionSession(), name)) +++ EE->getMainJITDylib().addGenerator(std::move(*DLSG)); +++ else +++ return DLSG.takeError(); +++ } else if (auto DLSG = llvm::orc::DynamicLibrarySearchGenerator::Load( +++ name, DL.getGlobalPrefix())) ++ EE->getMainJITDylib().addGenerator(std::move(*DLSG)); ++ else ++ return DLSG.takeError(); ++diff --git a/clang/tools/clang-repl/ClangRepl.cpp b/clang/tools/clang-repl/ClangRepl.cpp ++index 7fcd5f1730141..13414da734127 100644 ++--- a/clang/tools/clang-repl/ClangRepl.cpp +diff --git a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp +index 972c24abc7..84f8bc4e24 100644 +--- a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp ++++ b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp +@@ -635,10 +635,12 @@ Error ORCPlatformSupport::initialize(orc::JITDylib &JD) { + int32_t result; + auto E = ES.callSPSWrapper(WrapperAddr->getAddress(), + result, DSOHandles[&JD]); ++ if (E) ++ return E; + if (result) + return make_error("dlupdate failed", + inconvertibleErrorCode()); +- return E; ++ return Error::success(); + } + return ES.callSPSWrapper(WrapperAddr->getAddress(), + DSOHandles[&JD], JD.getName(), +diff --git a/ofp-jit-exec.patch b/ofp-jit-exec.patch +new file mode 100644 +index 0000000000..03a4620ba0 +--- /dev/null ++++ b/ofp-jit-exec.patch +@@ -0,0 +1,710 @@ ++diff --git a/clang/include/clang/Interpreter/Interpreter.h b/clang/include/clang/Interpreter/Interpreter.h ++index f8663e3193a18..78dff1165dcf5 100644 ++--- a/clang/include/clang/Interpreter/Interpreter.h +++++ b/clang/include/clang/Interpreter/Interpreter.h ++@@ -20,6 +20,7 @@ ++ ++ #include "llvm/ADT/DenseMap.h" ++ #include "llvm/ExecutionEngine/JITSymbol.h" +++#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" ++ #include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h" ++ #include "llvm/Support/Error.h" ++ #include ++@@ -136,10 +137,14 @@ protected: ++ public: ++ virtual ~Interpreter(); ++ static llvm::Expected> ++- create(std::unique_ptr CI); +++ create(std::unique_ptr CI, +++ std::unique_ptr JITBuilder = nullptr); ++ static llvm::Expected> ++ createWithCUDA(std::unique_ptr CI, ++ std::unique_ptr DCI); +++ static llvm::Expected> +++ createLLJITBuilder(std::unique_ptr EPC, +++ llvm::StringRef OrcRuntimePath); ++ const ASTContext &getASTContext() const; ++ ASTContext &getASTContext(); ++ const CompilerInstance *getCompilerInstance() const; ++diff --git a/clang/include/clang/Interpreter/RemoteJITUtils.h b/clang/include/clang/Interpreter/RemoteJITUtils.h ++new file mode 100644 ++index 0000000000000..8705a3b1f669d ++--- /dev/null +++++ b/clang/include/clang/Interpreter/RemoteJITUtils.h ++@@ -0,0 +1,38 @@ +++//===-- RemoteJITUtils.h - Utilities for remote-JITing ----------*- 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 +++// +++//===----------------------------------------------------------------------===// +++// +++// Utilities for ExecutorProcessControl-based remote JITing with Orc and +++// JITLink. +++// +++//===----------------------------------------------------------------------===// +++ +++#ifndef LLVM_CLANG_INTERPRETER_REMOTEJITUTILS_H +++#define LLVM_CLANG_INTERPRETER_REMOTEJITUTILS_H +++ +++#include "llvm/ADT/StringRef.h" +++#include "llvm/ExecutionEngine/Orc/Core.h" +++#include "llvm/ExecutionEngine/Orc/Layer.h" +++#include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h" +++#include "llvm/Support/Error.h" +++ +++#include +++#include +++#include +++ +++llvm::Expected> +++launchExecutor(llvm::StringRef ExecutablePath, bool UseSharedMemory, +++ llvm::StringRef SlabAllocateSizeString); +++ +++/// Create a JITLinkExecutor that connects to the given network address +++/// through a TCP socket. A valid NetworkAddress provides hostname and port, +++/// e.g. localhost:20000. +++llvm::Expected> +++connectTCPSocket(llvm::StringRef NetworkAddress, bool UseSharedMemory, +++ llvm::StringRef SlabAllocateSizeString); +++ +++#endif // LLVM_CLANG_INTERPRETER_REMOTEJITUTILS_H ++diff --git a/clang/lib/Interpreter/CMakeLists.txt b/clang/lib/Interpreter/CMakeLists.txt ++index bf70cdfbee01e..38cf139fa86a6 100644 ++--- a/clang/lib/Interpreter/CMakeLists.txt +++++ b/clang/lib/Interpreter/CMakeLists.txt ++@@ -27,6 +27,7 @@ add_clang_library(clangInterpreter ++ Interpreter.cpp ++ InterpreterValuePrinter.cpp ++ InterpreterUtils.cpp +++ RemoteJITUtils.cpp ++ Value.cpp ++ ${WASM_SRC} ++ PARTIAL_SOURCES_INTENDED ++diff --git a/clang/lib/Interpreter/Interpreter.cpp b/clang/lib/Interpreter/Interpreter.cpp ++index 3b81f9d701b42..e59656f554f3d 100644 ++--- a/clang/lib/Interpreter/Interpreter.cpp +++++ b/clang/lib/Interpreter/Interpreter.cpp ++@@ -46,6 +46,7 @@ ++ #include "clang/Sema/Lookup.h" ++ #include "clang/Serialization/ObjectFilePCHContainerReader.h" ++ #include "llvm/ExecutionEngine/JITSymbol.h" +++#include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" ++ #include "llvm/ExecutionEngine/Orc/LLJIT.h" ++ #include "llvm/IR/Module.h" ++ #include "llvm/Support/Errc.h" ++@@ -455,10 +456,11 @@ const char *const Runtimes = R"( ++ )"; ++ ++ llvm::Expected> ++-Interpreter::create(std::unique_ptr CI) { +++Interpreter::create(std::unique_ptr CI, +++ std::unique_ptr JB) { ++ llvm::Error Err = llvm::Error::success(); ++- auto Interp = ++- std::unique_ptr(new Interpreter(std::move(CI), Err)); +++ auto Interp = std::unique_ptr( +++ new Interpreter(std::move(CI), Err, JB ? std::move(JB) : nullptr)); ++ if (Err) ++ return std::move(Err); ++ ++@@ -617,6 +619,25 @@ createJITTargetMachineBuilder(const std::string &TT) { ++ return llvm::orc::JITTargetMachineBuilder(llvm::Triple(TT)); ++ } ++ +++llvm::Expected> +++Interpreter::createLLJITBuilder( +++ std::unique_ptr EPC, +++ llvm::StringRef OrcRuntimePath) { +++ const std::string &TT = EPC->getTargetTriple().getTriple(); +++ auto JTMB = createJITTargetMachineBuilder(TT); +++ if (!JTMB) +++ return JTMB.takeError(); +++ auto JB = IncrementalExecutor::createDefaultJITBuilder(std::move(*JTMB)); +++ if (!JB) +++ return JB.takeError(); +++ +++ (*JB)->setExecutorProcessControl(std::move(EPC)); +++ (*JB)->setPlatformSetUp( +++ llvm::orc::ExecutorNativePlatform(OrcRuntimePath.str())); +++ +++ return std::move(*JB); +++} +++ ++ llvm::Error Interpreter::CreateExecutor() { ++ if (IncrExecutor) ++ return llvm::make_error("Operation failed. " ++@@ -757,11 +778,11 @@ llvm::Error Interpreter::LoadDynamicLibrary(const char *name) { ++ 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)); +++ if (auto DLSG = llvm::orc::EPCDynamicLibrarySearchGenerator::Load( +++ EE->getExecutionSession(), name)) +++ // FIXME: Eventually we should put each library in its own JITDylib and +++ // turn off process symbols by default. +++ EE->getProcessSymbolsJITDylib()->addGenerator(std::move(*DLSG)); ++ else ++ return DLSG.takeError(); ++ #endif ++diff --git a/clang/lib/Interpreter/RemoteJITUtils.cpp b/clang/lib/Interpreter/RemoteJITUtils.cpp ++new file mode 100644 ++index 0000000000000..24a5f729f2dcb ++--- /dev/null +++++ b/clang/lib/Interpreter/RemoteJITUtils.cpp ++@@ -0,0 +1,267 @@ +++//===-- RemoteJITUtils.cpp - Utilities for remote-JITing --------*- 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 +++// +++//===----------------------------------------------------------------------===// +++// +++// FIXME: Unify this code with similar functionality in llvm-jitlink. +++// +++//===----------------------------------------------------------------------===// +++ +++#include "clang/Interpreter/RemoteJITUtils.h" +++ +++#include "llvm/ADT/StringExtras.h" +++#include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" +++#include "llvm/ExecutionEngine/Orc/EPCDebugObjectRegistrar.h" +++#include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" +++#include "llvm/ExecutionEngine/Orc/MapperJITLinkMemoryManager.h" +++#include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" +++#include "llvm/ExecutionEngine/Orc/Shared/SimpleRemoteEPCUtils.h" +++#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" +++#include "llvm/Support/FileSystem.h" +++#include "llvm/Support/Path.h" +++ +++#ifdef LLVM_ON_UNIX +++#include +++#include +++#include +++#include +++#endif // LLVM_ON_UNIX +++ +++using namespace llvm; +++using namespace llvm::orc; +++ +++Expected getSlabAllocSize(StringRef SizeString) { +++ SizeString = SizeString.trim(); +++ +++ uint64_t Units = 1024; +++ +++ if (SizeString.ends_with_insensitive("kb")) +++ SizeString = SizeString.drop_back(2).rtrim(); +++ else if (SizeString.ends_with_insensitive("mb")) { +++ Units = 1024 * 1024; +++ SizeString = SizeString.drop_back(2).rtrim(); +++ } else if (SizeString.ends_with_insensitive("gb")) { +++ Units = 1024 * 1024 * 1024; +++ SizeString = SizeString.drop_back(2).rtrim(); +++ } +++ +++ uint64_t SlabSize = 0; +++ if (SizeString.getAsInteger(10, SlabSize)) +++ return make_error("Invalid numeric format for slab size", +++ inconvertibleErrorCode()); +++ +++ return SlabSize * Units; +++} +++ +++Expected> +++createSharedMemoryManager(SimpleRemoteEPC &SREPC, +++ StringRef SlabAllocateSizeString) { +++ SharedMemoryMapper::SymbolAddrs SAs; +++ if (auto Err = SREPC.getBootstrapSymbols( +++ {{SAs.Instance, rt::ExecutorSharedMemoryMapperServiceInstanceName}, +++ {SAs.Reserve, +++ rt::ExecutorSharedMemoryMapperServiceReserveWrapperName}, +++ {SAs.Initialize, +++ rt::ExecutorSharedMemoryMapperServiceInitializeWrapperName}, +++ {SAs.Deinitialize, +++ rt::ExecutorSharedMemoryMapperServiceDeinitializeWrapperName}, +++ {SAs.Release, +++ rt::ExecutorSharedMemoryMapperServiceReleaseWrapperName}})) +++ return std::move(Err); +++ +++#ifdef _WIN32 +++ size_t SlabSize = 1024 * 1024; +++#else +++ size_t SlabSize = 1024 * 1024 * 1024; +++#endif +++ +++ if (!SlabAllocateSizeString.empty()) { +++ if (auto S = getSlabAllocSize(SlabAllocateSizeString)) +++ SlabSize = *S; +++ else +++ return S.takeError(); +++ } +++ +++ return MapperJITLinkMemoryManager::CreateWithMapper( +++ SlabSize, SREPC, SAs); +++} +++ +++Expected> +++launchExecutor(StringRef ExecutablePath, bool UseSharedMemory, +++ llvm::StringRef SlabAllocateSizeString) { +++#ifndef LLVM_ON_UNIX +++ // FIXME: Add support for Windows. +++ return make_error("-" + ExecutablePath + +++ " not supported on non-unix platforms", +++ inconvertibleErrorCode()); +++#elif !LLVM_ENABLE_THREADS +++ // Out of process mode using SimpleRemoteEPC depends on threads. +++ return make_error( +++ "-" + ExecutablePath + +++ " requires threads, but LLVM was built with " +++ "LLVM_ENABLE_THREADS=Off", +++ inconvertibleErrorCode()); +++#else +++ +++ if (!sys::fs::can_execute(ExecutablePath)) +++ return make_error( +++ formatv("Specified executor invalid: {0}", ExecutablePath), +++ inconvertibleErrorCode()); +++ +++ constexpr int ReadEnd = 0; +++ constexpr int WriteEnd = 1; +++ +++ // Pipe FDs. +++ int ToExecutor[2]; +++ int FromExecutor[2]; +++ +++ pid_t ChildPID; +++ +++ // Create pipes to/from the executor.. +++ if (pipe(ToExecutor) != 0 || pipe(FromExecutor) != 0) +++ return make_error("Unable to create pipe for executor", +++ inconvertibleErrorCode()); +++ +++ ChildPID = fork(); +++ +++ if (ChildPID == 0) { +++ // In the child... +++ +++ // Close the parent ends of the pipes +++ close(ToExecutor[WriteEnd]); +++ close(FromExecutor[ReadEnd]); +++ +++ // Execute the child process. +++ std::unique_ptr ExecutorPath, FDSpecifier; +++ { +++ ExecutorPath = std::make_unique(ExecutablePath.size() + 1); +++ strcpy(ExecutorPath.get(), ExecutablePath.data()); +++ +++ std::string FDSpecifierStr("filedescs="); +++ FDSpecifierStr += utostr(ToExecutor[ReadEnd]); +++ FDSpecifierStr += ','; +++ FDSpecifierStr += utostr(FromExecutor[WriteEnd]); +++ FDSpecifier = std::make_unique(FDSpecifierStr.size() + 1); +++ strcpy(FDSpecifier.get(), FDSpecifierStr.c_str()); +++ } +++ +++ char *const Args[] = {ExecutorPath.get(), FDSpecifier.get(), nullptr}; +++ int RC = execvp(ExecutorPath.get(), Args); +++ if (RC != 0) { +++ errs() << "unable to launch out-of-process executor \"" +++ << ExecutorPath.get() << "\"\n"; +++ exit(1); +++ } +++ } +++ // else we're the parent... +++ +++ // Close the child ends of the pipes +++ close(ToExecutor[ReadEnd]); +++ close(FromExecutor[WriteEnd]); +++ +++ auto S = SimpleRemoteEPC::Setup(); +++ if (UseSharedMemory) +++ S.CreateMemoryManager = [SlabAllocateSizeString](SimpleRemoteEPC &EPC) { +++ return createSharedMemoryManager(EPC, SlabAllocateSizeString); +++ }; +++ +++ return SimpleRemoteEPC::Create( +++ std::make_unique(std::nullopt), +++ std::move(S), FromExecutor[ReadEnd], ToExecutor[WriteEnd]); +++#endif +++} +++ +++#if LLVM_ON_UNIX && LLVM_ENABLE_THREADS +++ +++static Expected connectTCPSocketImpl(std::string Host, +++ std::string PortStr) { +++ addrinfo *AI; +++ addrinfo Hints{}; +++ Hints.ai_family = AF_INET; +++ Hints.ai_socktype = SOCK_STREAM; +++ Hints.ai_flags = AI_NUMERICSERV; +++ +++ if (int EC = getaddrinfo(Host.c_str(), PortStr.c_str(), &Hints, &AI)) +++ return make_error( +++ formatv("address resolution failed ({0})", gai_strerror(EC)), +++ inconvertibleErrorCode()); +++ // Cycle through the returned addrinfo structures and connect to the first +++ // reachable endpoint. +++ int SockFD; +++ addrinfo *Server; +++ for (Server = AI; Server != nullptr; Server = Server->ai_next) { +++ // socket might fail, e.g. if the address family is not supported. Skip to +++ // the next addrinfo structure in such a case. +++ if ((SockFD = socket(AI->ai_family, AI->ai_socktype, AI->ai_protocol)) < 0) +++ continue; +++ +++ // If connect returns null, we exit the loop with a working socket. +++ if (connect(SockFD, Server->ai_addr, Server->ai_addrlen) == 0) +++ break; +++ +++ close(SockFD); +++ } +++ freeaddrinfo(AI); +++ +++ // If we reached the end of the loop without connecting to a valid endpoint, +++ // dump the last error that was logged in socket() or connect(). +++ if (Server == nullptr) +++ return make_error("invalid hostname", +++ inconvertibleErrorCode()); +++ +++ return SockFD; +++} +++#endif +++ +++Expected> +++connectTCPSocket(StringRef NetworkAddress, bool UseSharedMemory, +++ llvm::StringRef SlabAllocateSizeString) { +++#ifndef LLVM_ON_UNIX +++ // FIXME: Add TCP support for Windows. +++ return make_error("-" + NetworkAddress + +++ " not supported on non-unix platforms", +++ inconvertibleErrorCode()); +++#elif !LLVM_ENABLE_THREADS +++ // Out of process mode using SimpleRemoteEPC depends on threads. +++ return make_error( +++ "-" + NetworkAddress + +++ " requires threads, but LLVM was built with " +++ "LLVM_ENABLE_THREADS=Off", +++ inconvertibleErrorCode()); +++#else +++ +++ auto CreateErr = [NetworkAddress](Twine Details) { +++ return make_error( +++ formatv("Failed to connect TCP socket '{0}': {1}", NetworkAddress, +++ Details), +++ inconvertibleErrorCode()); +++ }; +++ +++ StringRef Host, PortStr; +++ std::tie(Host, PortStr) = NetworkAddress.split(':'); +++ if (Host.empty()) +++ return CreateErr("Host name for -" + NetworkAddress + " can not be empty"); +++ if (PortStr.empty()) +++ return CreateErr("Port number in -" + NetworkAddress + " can not be empty"); +++ int Port = 0; +++ if (PortStr.getAsInteger(10, Port)) +++ return CreateErr("Port number '" + PortStr + "' is not a valid integer"); +++ +++ Expected SockFD = connectTCPSocketImpl(Host.str(), PortStr.str()); +++ if (!SockFD) +++ return SockFD.takeError(); +++ +++ auto S = SimpleRemoteEPC::Setup(); +++ if (UseSharedMemory) +++ S.CreateMemoryManager = [SlabAllocateSizeString](SimpleRemoteEPC &EPC) { +++ return createSharedMemoryManager(EPC, SlabAllocateSizeString); +++ }; +++ +++ return SimpleRemoteEPC::Create( +++ std::make_unique(std::nullopt), +++ std::move(S), *SockFD, *SockFD); +++#endif +++} ++diff --git a/clang/test/Interpreter/out-of-process.cpp b/clang/test/Interpreter/out-of-process.cpp ++new file mode 100644 ++index 0000000000000..edc4c3fee0966 ++--- /dev/null +++++ b/clang/test/Interpreter/out-of-process.cpp ++@@ -0,0 +1,88 @@ +++// REQUIRES: host-supports-jit +++ +++// RUN: cat %s | clang-repl -oop-executor -orc-runtime | FileCheck %s +++ +++extern "C" int printf(const char *, ...); +++ +++int intVar = 0; +++double doubleVar = 3.14; +++%undo +++double doubleVar = 2.71; +++ +++auto r1 = printf("intVar = %d\n", intVar); +++// CHECK: intVar = 0 +++auto r2 = printf("doubleVar = %.2f\n", doubleVar); +++// CHECK: doubleVar = 2.71 +++ +++// Test redefinition with inline and static functions. +++int add(int a, int b, int c) { return a + b + c; } +++%undo // Revert to the initial version of add +++inline int add(int a, int b) { return a + b; } +++ +++auto r3 = printf("add(1, 2) = %d\n", add(1, 2)); +++// CHECK-NEXT: add(1, 2) = 3 +++ +++// Test inline and lambda functions with variations. +++inline int square(int x) { return x * x; } +++auto lambdaSquare = [](int x) { return x * x; }; +++auto lambdaMult = [](int a, int b) { return a * b; }; +++ +++auto r4 = printf("square(4) = %d\n", square(4)); +++// CHECK-NEXT: square(4) = 16 +++auto lambda_r1 = printf("lambdaSquare(5) = %d\n", lambdaSquare(5)); +++// CHECK-NEXT: lambdaSquare(5) = 25 +++auto lambda_r2 = printf("lambdaMult(2, 3) = %d\n", lambdaMult(2, 3)); +++// CHECK-NEXT: lambdaMult(2, 3) = 6 +++ +++%undo // Undo previous lambda assignments +++auto lambda_r3 = lambdaMult(3, 4); // Should fail or revert to the original lambda +++ +++// Test weak and strong symbol linkage. +++int __attribute__((weak)) weakFunc() { return 42; } +++int strongFunc() { return 100; } +++%undo // Revert the weak function +++ +++auto r5 = printf("weakFunc() = %d\n", weakFunc()); +++// CHECK: weakFunc() = 42 +++auto r6 = printf("strongFunc() = %d\n", strongFunc()); +++// CHECK-NEXT: strongFunc() = 100 +++ +++// Weak variable linkage with different types. +++int varA = 20; +++static __typeof(varA) weakVarA __attribute__((__weakref__("varA"))); +++char charVar = 'c'; +++static __typeof(charVar) weakCharVar __attribute__((__weakref__("charVar"))); +++auto r7 = printf("weakVarA = %d\n", weakVarA); +++// CHECK: weakVarA = 20 +++auto r8 = printf("weakCharVar = %c\n", weakCharVar); +++// CHECK-NEXT: weakCharVar = c +++ +++// Test complex lambdas with captures. +++int captureVar = 5; +++auto captureLambda = [](int x) { return x + captureVar; };` +++int result1 = captureLambda(10); +++%undo // Undo capture lambda +++ +++auto r9 = printf("captureLambda(10) = %d\n", result1); +++// CHECK: captureLambda(10) = 15 +++ +++// Multiline statement test with arithmetic operations. +++int sum = \ +++ 5 + \ +++ 10; +++int prod = sum * 2; +++auto r10 = printf("sum = %d, prod = %d\n", sum, prod); +++// CHECK: sum = 15, prod = 30 +++ +++// Test multiline functions and macro behavior. +++#define MULTIPLY(a, b) ((a) * (b)) +++ +++int complexFunc(int x) \ +++{ \ +++ return MULTIPLY(x, 2) + x; \ +++} +++ +++auto r11 = printf("complexFunc(5) = %d\n", complexFunc(5)); +++// CHECK: complexFunc(5) = 15 +++ +++%quit ++diff --git a/clang/tools/clang-repl/CMakeLists.txt b/clang/tools/clang-repl/CMakeLists.txt ++index f9a911b0ae8e2..68d86dd98cac3 100644 ++--- a/clang/tools/clang-repl/CMakeLists.txt +++++ b/clang/tools/clang-repl/CMakeLists.txt ++@@ -4,7 +4,9 @@ set( LLVM_LINK_COMPONENTS ++ LineEditor ++ Option ++ OrcJIT +++ OrcShared ++ Support +++ TargetParser ++ ) ++ ++ add_clang_tool(clang-repl ++diff --git a/clang/tools/clang-repl/ClangRepl.cpp b/clang/tools/clang-repl/ClangRepl.cpp ++index 7af8e4f25d99e..54b27782d3db3 100644 ++--- a/clang/tools/clang-repl/ClangRepl.cpp +++++ b/clang/tools/clang-repl/ClangRepl.cpp ++@@ -10,7 +10,11 @@ ++ // ++ //===----------------------------------------------------------------------===// ++ +++#include "clang/Interpreter/RemoteJITUtils.h" +++ ++ #include "clang/Basic/Diagnostic.h" +++#include "clang/Basic/Version.h" +++#include "clang/Config/config.h" ++ #include "clang/Frontend/CompilerInstance.h" ++ #include "clang/Frontend/FrontendDiagnostic.h" ++ #include "clang/Interpreter/CodeCompletion.h" ++@@ -24,8 +28,11 @@ ++ #include "llvm/Support/ManagedStatic.h" // llvm_shutdown ++ #include "llvm/Support/Signals.h" ++ #include "llvm/Support/TargetSelect.h" +++#include "llvm/TargetParser/Host.h" ++ #include ++ +++#include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h" +++ ++ // Disable LSan for this test. ++ // FIXME: Re-enable once we can assume GCC 13.2 or higher. ++ // https://llvm.org/github.com/llvm/llvm-project/issues/67586. ++@@ -34,10 +41,36 @@ ++ LLVM_ATTRIBUTE_USED int __lsan_is_turned_off() { return 1; } ++ #endif ++ +++#define DEBUG_TYPE "clang-repl" +++ ++ static llvm::cl::opt CudaEnabled("cuda", llvm::cl::Hidden); ++ static llvm::cl::opt CudaPath("cuda-path", llvm::cl::Hidden); ++ static llvm::cl::opt OffloadArch("offload-arch", llvm::cl::Hidden); ++- +++static llvm::cl::OptionCategory OOPCategory("Out-of-process Execution Options"); +++static llvm::cl::opt SlabAllocateSizeString( +++ "slab-allocate", +++ llvm::cl::desc("Allocate from a slab of the given size " +++ "(allowable suffixes: Kb, Mb, Gb. default = " +++ "Kb)"), +++ llvm::cl::init(""), llvm::cl::cat(OOPCategory)); +++static llvm::cl::opt +++ OOPExecutor("oop-executor", +++ llvm::cl::desc("Launch an out-of-process executor to run code"), +++ llvm::cl::init(""), llvm::cl::ValueOptional, +++ llvm::cl::cat(OOPCategory)); +++static llvm::cl::opt OOPExecutorConnect( +++ "oop-executor-connect", +++ llvm::cl::desc( +++ "Connect to an out-of-process executor through a TCP socket"), +++ llvm::cl::value_desc(":")); +++static llvm::cl::opt +++ OrcRuntimePath("orc-runtime", llvm::cl::desc("Path to the ORC runtime"), +++ llvm::cl::init(""), llvm::cl::ValueOptional, +++ llvm::cl::cat(OOPCategory)); +++static llvm::cl::opt UseSharedMemory( +++ "use-shared-memory", +++ llvm::cl::desc("Use shared memory to transfer generated code and data"), +++ llvm::cl::init(false), llvm::cl::cat(OOPCategory)); ++ static llvm::cl::list ++ ClangArgs("Xcc", ++ llvm::cl::desc("Argument to pass to the CompilerInvocation"), ++@@ -47,6 +80,72 @@ static llvm::cl::opt OptHostSupportsJit("host-supports-jit", ++ static llvm::cl::list OptInputs(llvm::cl::Positional, ++ llvm::cl::desc("[code to run]")); ++ +++static llvm::Error sanitizeOopArguments(const char *ArgV0) { +++ // Only one of -oop-executor and -oop-executor-connect can be used. +++ if (!!OOPExecutor.getNumOccurrences() && +++ !!OOPExecutorConnect.getNumOccurrences()) +++ return llvm::make_error( +++ "Only one of -" + OOPExecutor.ArgStr + " and -" + +++ OOPExecutorConnect.ArgStr + " can be specified", +++ llvm::inconvertibleErrorCode()); +++ +++ llvm::Triple SystemTriple(llvm::sys::getProcessTriple()); +++ // TODO: Remove once out-of-process execution support is implemented for +++ // non-Unix platforms. +++ if ((!SystemTriple.isOSBinFormatELF() && +++ !SystemTriple.isOSBinFormatMachO()) && +++ (OOPExecutor.getNumOccurrences() || +++ OOPExecutorConnect.getNumOccurrences())) +++ return llvm::make_error( +++ "Out-of-process execution is only supported on Unix platforms", +++ llvm::inconvertibleErrorCode()); +++ +++ // If -slab-allocate is passed, check that we're not trying to use it in +++ // -oop-executor or -oop-executor-connect mode. +++ // +++ // FIXME: Remove once we enable remote slab allocation. +++ if (SlabAllocateSizeString != "") { +++ if (OOPExecutor.getNumOccurrences() || +++ OOPExecutorConnect.getNumOccurrences()) +++ return llvm::make_error( +++ "-slab-allocate cannot be used with -oop-executor or " +++ "-oop-executor-connect", +++ llvm::inconvertibleErrorCode()); +++ } +++ +++ // Out-of-process executors require the ORC runtime. +++ if (OrcRuntimePath.empty() && (OOPExecutor.getNumOccurrences() || +++ OOPExecutorConnect.getNumOccurrences())) { +++ llvm::SmallString<256> BasePath(llvm::sys::fs::getMainExecutable( +++ ArgV0, reinterpret_cast(&sanitizeOopArguments))); +++ llvm::sys::path::remove_filename(BasePath); // Remove clang-repl filename. +++ llvm::sys::path::remove_filename(BasePath); // Remove ./bin directory. +++ llvm::sys::path::append(BasePath, CLANG_INSTALL_LIBDIR_BASENAME, "clang", +++ CLANG_VERSION_MAJOR_STRING); +++ if (SystemTriple.isOSBinFormatELF()) +++ OrcRuntimePath = +++ BasePath.str().str() + "/lib/x86_64-unknown-linux-gnu/liborc_rt.a"; +++ else if (SystemTriple.isOSBinFormatMachO()) +++ OrcRuntimePath = BasePath.str().str() + "/lib/darwin/liborc_rt_osx.a"; +++ else +++ return llvm::make_error( +++ "Out-of-process execution is not supported on non-unix platforms", +++ llvm::inconvertibleErrorCode()); +++ } +++ +++ // If -oop-executor was used but no value was specified then use a sensible +++ // default. +++ if (!!OOPExecutor.getNumOccurrences() && OOPExecutor.empty()) { +++ llvm::SmallString<256> OOPExecutorPath(llvm::sys::fs::getMainExecutable( +++ ArgV0, reinterpret_cast(&sanitizeOopArguments))); +++ llvm::sys::path::remove_filename(OOPExecutorPath); +++ llvm::sys::path::append(OOPExecutorPath, "llvm-jitlink-executor"); +++ OOPExecutor = OOPExecutorPath.str().str(); +++ } +++ +++ return llvm::Error::success(); +++} +++ ++ static void LLVMErrorHandler(void *UserData, const char *Message, ++ bool GenCrashDiag) { ++ auto &Diags = *static_cast(UserData); ++@@ -183,6 +282,25 @@ int main(int argc, const char **argv) { ++ DeviceCI = ExitOnErr(CB.CreateCudaDevice()); ++ } ++ +++ ExitOnErr(sanitizeOopArguments(argv[0])); +++ +++ std::unique_ptr EPC; +++ if (OOPExecutor.getNumOccurrences()) { +++ // Launch an out-of-process executor locally in a child process. +++ EPC = ExitOnErr( +++ launchExecutor(OOPExecutor, UseSharedMemory, SlabAllocateSizeString)); +++ } else if (OOPExecutorConnect.getNumOccurrences()) { +++ EPC = ExitOnErr(connectTCPSocket(OOPExecutorConnect, UseSharedMemory, +++ SlabAllocateSizeString)); +++ } +++ +++ std::unique_ptr JB; +++ if (EPC) { +++ CB.SetTargetTriple(EPC->getTargetTriple().getTriple()); +++ JB = ExitOnErr( +++ clang::Interpreter::createLLJITBuilder(std::move(EPC), OrcRuntimePath)); +++ } +++ ++ // FIXME: Investigate if we could use runToolOnCodeWithArgs from tooling. It ++ // can replace the boilerplate code for creation of the compiler instance. ++ std::unique_ptr CI; ++@@ -214,6 +332,9 @@ int main(int argc, const char **argv) { ++ auto CudaRuntimeLibPath = CudaPath + "/lib/libcudart.so"; ++ ExitOnErr(Interp->LoadDynamicLibrary(CudaRuntimeLibPath.c_str())); ++ } +++ } else if (JB) { +++ Interp = +++ ExitOnErr(clang::Interpreter::create(std::move(CI), std::move(JB))); ++ } else ++ Interp = ExitOnErr(clang::Interpreter::create(std::move(CI))); ++ diff --git a/unittests/CppInterOp/InterpreterTest.cpp b/unittests/CppInterOp/InterpreterTest.cpp index 841913c54..36c128391 100644 --- a/unittests/CppInterOp/InterpreterTest.cpp +++ b/unittests/CppInterOp/InterpreterTest.cpp @@ -146,6 +146,23 @@ TEST(InterpreterTest, Process) { clang_Interpreter_dispose(CXI); } +#ifdef CPPINTEROP_WITH_OOP_JIT +TEST(InterpreterTest, OopJITProcess) { +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + std::vector interpreter_args = { "-include", "new" }; + auto* I = Cpp::CreateInterpreter(interpreter_args, {}, true); + 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); +} +#endif + TEST(InterpreterTest, EmscriptenExceptionHandling) { #ifndef EMSCRIPTEN GTEST_SKIP() << "This test is intended to check exception handling for Emscripten builds.";