blob: 74b751d0e2dae56615d1673b439e7b02351cdc30 [file] [log] [blame] [edit]
//===--- IncrementalExecutor.cpp - Incremental 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 has the implementation of the base facilities for incremental execution.
//
//===----------------------------------------------------------------------===//
#include "clang/Interpreter/IncrementalExecutor.h"
#include "OrcIncrementalExecutor.h"
#ifdef __EMSCRIPTEN__
#include "Wasm.h"
#endif // __EMSCRIPTEN__
#include "clang/Basic/TargetInfo.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/ToolChain.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h"
#include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h"
#include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h"
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.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/SimpleRemoteEPC.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Host.h"
#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#ifdef LLVM_ON_UNIX
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
namespace clang {
IncrementalExecutorBuilder::~IncrementalExecutorBuilder() = default;
static llvm::Expected<llvm::orc::JITTargetMachineBuilder>
createJITTargetMachineBuilder(const llvm::Triple &TT) {
if (TT.getTriple() == llvm::sys::getProcessTriple())
// This fails immediately if the target backend is not registered
return llvm::orc::JITTargetMachineBuilder::detectHost();
// If the target backend is not registered, LLJITBuilder::create() will fail
return llvm::orc::JITTargetMachineBuilder(TT);
}
static llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>
createDefaultJITBuilder(llvm::orc::JITTargetMachineBuilder JTMB) {
auto JITBuilder = std::make_unique<llvm::orc::LLJITBuilder>();
JITBuilder->setJITTargetMachineBuilder(std::move(JTMB));
JITBuilder->setPrePlatformSetup([](llvm::orc::LLJIT &J) {
// Try to enable debugging of JIT'd code (only works with JITLink for
// ELF and MachO).
consumeError(llvm::orc::enableDebuggerSupport(J));
return llvm::Error::success();
});
return std::move(JITBuilder);
}
Expected<std::unique_ptr<llvm::jitlink::JITLinkMemoryManager>>
createSharedMemoryManager(llvm::orc::SimpleRemoteEPC &SREPC,
unsigned SlabAllocateSize) {
llvm::orc::SharedMemoryMapper::SymbolAddrs SAs;
if (auto Err = SREPC.getBootstrapSymbols(
{{SAs.Instance,
llvm::orc::rt::ExecutorSharedMemoryMapperServiceInstanceName},
{SAs.Reserve,
llvm::orc::rt::ExecutorSharedMemoryMapperServiceReserveWrapperName},
{SAs.Initialize,
llvm::orc::rt::
ExecutorSharedMemoryMapperServiceInitializeWrapperName},
{SAs.Deinitialize,
llvm::orc::rt::
ExecutorSharedMemoryMapperServiceDeinitializeWrapperName},
{SAs.Release,
llvm::orc::rt::
ExecutorSharedMemoryMapperServiceReleaseWrapperName}}))
return std::move(Err);
size_t SlabSize;
if (llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows())
SlabSize = 1024 * 1024;
else
SlabSize = 1024 * 1024 * 1024;
if (SlabAllocateSize > 0)
SlabSize = SlabAllocateSize;
return llvm::orc::MapperJITLinkMemoryManager::CreateWithMapper<
llvm::orc::SharedMemoryMapper>(SlabSize, SREPC, SAs);
}
static llvm::Expected<
std::pair<std::unique_ptr<llvm::orc::SimpleRemoteEPC>, uint32_t>>
launchExecutor(llvm::StringRef ExecutablePath, bool UseSharedMemory,
unsigned SlabAllocateSize, std::function<void()> CustomizeFork) {
#ifndef LLVM_ON_UNIX
// FIXME: Add support for Windows.
return llvm::make_error<llvm::StringError>(
"-" + ExecutablePath + " not supported on non-unix platforms",
llvm::inconvertibleErrorCode());
#elif !LLVM_ENABLE_THREADS
// Out of process mode using SimpleRemoteEPC depends on threads.
return llvm::make_error<llvm::StringError>(
"-" + ExecutablePath +
" requires threads, but LLVM was built with "
"LLVM_ENABLE_THREADS=Off",
llvm::inconvertibleErrorCode());
#else
if (!llvm::sys::fs::can_execute(ExecutablePath))
return llvm::make_error<llvm::StringError>(
llvm::formatv("Specified executor invalid: {0}", ExecutablePath),
llvm::inconvertibleErrorCode());
constexpr int ReadEnd = 0;
constexpr int WriteEnd = 1;
// Pipe FDs.
int ToExecutor[2];
int FromExecutor[2];
uint32_t ChildPID;
// Create pipes to/from the executor..
if (pipe(ToExecutor) != 0 || pipe(FromExecutor) != 0)
return llvm::make_error<llvm::StringError>(
"Unable to create pipe for executor", llvm::inconvertibleErrorCode());
ChildPID = fork();
if (ChildPID == 0) {
// In the child...
// Close the parent ends of the pipes
close(ToExecutor[WriteEnd]);
close(FromExecutor[ReadEnd]);
if (CustomizeFork)
CustomizeFork();
// Execute the child process.
std::unique_ptr<char[]> ExecutorPath, FDSpecifier;
{
ExecutorPath = std::make_unique<char[]>(ExecutablePath.size() + 1);
strcpy(ExecutorPath.get(), ExecutablePath.data());
std::string FDSpecifierStr("filedescs=");
FDSpecifierStr += llvm::utostr(ToExecutor[ReadEnd]);
FDSpecifierStr += ',';
FDSpecifierStr += llvm::utostr(FromExecutor[WriteEnd]);
FDSpecifier = std::make_unique<char[]>(FDSpecifierStr.size() + 1);
strcpy(FDSpecifier.get(), FDSpecifierStr.c_str());
}
char *const Args[] = {ExecutorPath.get(), FDSpecifier.get(), nullptr};
int RC = execvp(ExecutorPath.get(), Args);
if (RC != 0) {
llvm::errs() << "unable to launch out-of-process executor \""
<< ExecutorPath.get() << "\"\n";
exit(1);
}
}
// else we're the parent...
// Close the child ends of the pipes
close(ToExecutor[ReadEnd]);
close(FromExecutor[WriteEnd]);
llvm::orc::SimpleRemoteEPC::Setup S = llvm::orc::SimpleRemoteEPC::Setup();
if (UseSharedMemory)
S.CreateMemoryManager =
[SlabAllocateSize](llvm::orc::SimpleRemoteEPC &EPC) {
return createSharedMemoryManager(EPC, SlabAllocateSize);
};
auto EPCOrErr =
llvm::orc::SimpleRemoteEPC::Create<llvm::orc::FDSimpleRemoteEPCTransport>(
std::make_unique<llvm::orc::DynamicThreadPoolTaskDispatcher>(
std::nullopt),
std::move(S), FromExecutor[ReadEnd], ToExecutor[WriteEnd]);
if (!EPCOrErr)
return EPCOrErr.takeError();
return std::make_pair(std::move(*EPCOrErr), ChildPID);
#endif
}
#if LLVM_ON_UNIX && LLVM_ENABLE_THREADS
static Expected<int> connectTCPSocketImpl(std::string Host,
std::string PortStr) {
addrinfo *AI;
addrinfo Hints{};
Hints.ai_family = AF_INET;
Hints.ai_socktype = SOCK_STREAM;
Hints.ai_flags = AI_NUMERICSERV;
if (int EC = getaddrinfo(Host.c_str(), PortStr.c_str(), &Hints, &AI))
return llvm::make_error<llvm::StringError>(
llvm::formatv("address resolution failed ({0})", strerror(EC)),
llvm::inconvertibleErrorCode());
// Cycle through the returned addrinfo structures and connect to the first
// reachable endpoint.
int SockFD;
addrinfo *Server;
for (Server = AI; Server != nullptr; Server = Server->ai_next) {
// socket might fail, e.g. if the address family is not supported. Skip to
// the next addrinfo structure in such a case.
if ((SockFD = socket(AI->ai_family, AI->ai_socktype, AI->ai_protocol)) < 0)
continue;
// If connect returns null, we exit the loop with a working socket.
if (connect(SockFD, Server->ai_addr, Server->ai_addrlen) == 0)
break;
close(SockFD);
}
freeaddrinfo(AI);
// If we reached the end of the loop without connecting to a valid endpoint,
// dump the last error that was logged in socket() or connect().
if (Server == nullptr)
return llvm::make_error<llvm::StringError>("invalid hostname",
llvm::inconvertibleErrorCode());
return SockFD;
}
static llvm::Expected<std::unique_ptr<llvm::orc::SimpleRemoteEPC>>
connectTCPSocket(llvm::StringRef NetworkAddress, bool UseSharedMemory,
unsigned SlabAllocateSize) {
#ifndef LLVM_ON_UNIX
// FIXME: Add TCP support for Windows.
return llvm::make_error<llvm::StringError>(
"-" + NetworkAddress + " not supported on non-unix platforms",
llvm::inconvertibleErrorCode());
#elif !LLVM_ENABLE_THREADS
// Out of process mode using SimpleRemoteEPC depends on threads.
return llvm::make_error<llvm::StringError>(
"-" + NetworkAddress +
" requires threads, but LLVM was built with "
"LLVM_ENABLE_THREADS=Off",
llvm::inconvertibleErrorCode());
#else
auto CreateErr = [NetworkAddress](Twine Details) {
return llvm::make_error<llvm::StringError>(
formatv("Failed to connect TCP socket '{0}': {1}", NetworkAddress,
Details),
llvm::inconvertibleErrorCode());
};
StringRef Host, PortStr;
std::tie(Host, PortStr) = NetworkAddress.split(':');
if (Host.empty())
return CreateErr("Host name for -" + NetworkAddress + " can not be empty");
if (PortStr.empty())
return CreateErr("Port number in -" + NetworkAddress + " can not be empty");
int Port = 0;
if (PortStr.getAsInteger(10, Port))
return CreateErr("Port number '" + PortStr + "' is not a valid integer");
Expected<int> SockFD = connectTCPSocketImpl(Host.str(), PortStr.str());
if (!SockFD)
return SockFD.takeError();
llvm::orc::SimpleRemoteEPC::Setup S = llvm::orc::SimpleRemoteEPC::Setup();
if (UseSharedMemory)
S.CreateMemoryManager =
[SlabAllocateSize](llvm::orc::SimpleRemoteEPC &EPC) {
return createSharedMemoryManager(EPC, SlabAllocateSize);
};
return llvm::orc::SimpleRemoteEPC::Create<
llvm::orc::FDSimpleRemoteEPCTransport>(
std::make_unique<llvm::orc::DynamicThreadPoolTaskDispatcher>(
std::nullopt),
std::move(S), *SockFD, *SockFD);
#endif
}
#endif // _WIN32
static llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>
createLLJITBuilder(std::unique_ptr<llvm::orc::ExecutorProcessControl> EPC,
llvm::StringRef OrcRuntimePath) {
auto JTMB = createJITTargetMachineBuilder(EPC->getTargetTriple());
if (!JTMB)
return JTMB.takeError();
auto JB = 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);
}
static llvm::Expected<
std::pair<std::unique_ptr<llvm::orc::LLJITBuilder>, uint32_t>>
outOfProcessJITBuilder(const IncrementalExecutorBuilder &IncrExecutorBuilder) {
std::unique_ptr<llvm::orc::ExecutorProcessControl> EPC;
uint32_t childPid = -1;
if (!IncrExecutorBuilder.OOPExecutor.empty()) {
// Launch an out-of-process executor locally in a child process.
auto ResultOrErr = launchExecutor(IncrExecutorBuilder.OOPExecutor,
IncrExecutorBuilder.UseSharedMemory,
IncrExecutorBuilder.SlabAllocateSize,
IncrExecutorBuilder.CustomizeFork);
if (!ResultOrErr)
return ResultOrErr.takeError();
childPid = ResultOrErr->second;
auto EPCOrErr = std::move(ResultOrErr->first);
EPC = std::move(EPCOrErr);
} else if (IncrExecutorBuilder.OOPExecutorConnect != "") {
#if LLVM_ON_UNIX && LLVM_ENABLE_THREADS
auto EPCOrErr = connectTCPSocket(IncrExecutorBuilder.OOPExecutorConnect,
IncrExecutorBuilder.UseSharedMemory,
IncrExecutorBuilder.SlabAllocateSize);
if (!EPCOrErr)
return EPCOrErr.takeError();
EPC = std::move(*EPCOrErr);
#else
return llvm::make_error<llvm::StringError>(
"Out-of-process JIT over TCP is not supported on this platform",
std::error_code());
#endif
}
std::unique_ptr<llvm::orc::LLJITBuilder> JB;
if (EPC) {
auto JBOrErr =
createLLJITBuilder(std::move(EPC), IncrExecutorBuilder.OrcRuntimePath);
if (!JBOrErr)
return JBOrErr.takeError();
JB = std::move(*JBOrErr);
}
return std::make_pair(std::move(JB), childPid);
}
llvm::Expected<std::unique_ptr<IncrementalExecutor>>
IncrementalExecutorBuilder::create(llvm::orc::ThreadSafeContext &TSC,
const clang::TargetInfo &TI) {
if (IE)
return std::move(IE);
llvm::Triple TT = TI.getTriple();
if (!TT.isOSWindows() && IsOutOfProcess) {
if (!JITBuilder) {
auto ResOrErr = outOfProcessJITBuilder(*this);
if (!ResOrErr)
return ResOrErr.takeError();
JITBuilder = std::move(ResOrErr->first);
ExecutorPID = ResOrErr->second;
}
if (!JITBuilder)
return llvm::make_error<llvm::StringError>(
"Operation failed. No LLJITBuilder for out-of-process JIT",
std::error_code());
}
if (!JITBuilder) {
auto JTMB = createJITTargetMachineBuilder(TT);
if (!JTMB)
return JTMB.takeError();
if (CM)
JTMB->setCodeModel(CM);
auto JB = createDefaultJITBuilder(std::move(*JTMB));
if (!JB)
return JB.takeError();
JITBuilder = std::move(*JB);
}
llvm::Error Err = llvm::Error::success();
std::unique_ptr<IncrementalExecutor> Executor;
#ifdef __EMSCRIPTEN__
Executor = std::make_unique<WasmIncrementalExecutor>(Err);
#else
Executor = std::make_unique<OrcIncrementalExecutor>(TSC, *JITBuilder, Err);
#endif
if (Err)
return std::move(Err);
return std::move(Executor);
}
llvm::Error IncrementalExecutorBuilder::UpdateOrcRuntimePath(
const clang::driver::Compilation &C) {
if (!IsOutOfProcess)
return llvm::Error::success();
static constexpr std::array<const char *, 3> OrcRTLibNames = {
"liborc_rt.a",
"liborc_rt_osx.a",
"liborc_rt-x86_64.a",
};
auto findInDir = [&](llvm::StringRef Base) -> std::optional<std::string> {
if (Base.empty() || !llvm::sys::fs::exists(Base))
return std::nullopt;
for (const char *LibName : OrcRTLibNames) {
llvm::SmallString<256> Candidate(Base);
llvm::sys::path::append(Candidate, LibName);
if (llvm::sys::fs::exists(Candidate))
return std::string(Candidate.str());
}
return std::nullopt;
};
const clang::driver::Driver &D = C.getDriver();
const clang::driver::ToolChain &TC = C.getDefaultToolChain();
llvm::SmallVector<std::string, 8> triedPaths;
llvm::SmallString<256> Resource(D.ResourceDir);
if (llvm::sys::fs::exists(Resource)) {
// Ask the ToolChain for its runtime paths first (most authoritative).
for (auto RuntimePath :
{TC.getRuntimePath(), std::make_optional(TC.getCompilerRTPath())}) {
if (RuntimePath) {
if (auto Found = findInDir(*RuntimePath)) {
OrcRuntimePath = *Found;
return llvm::Error::success();
}
triedPaths.emplace_back(*RuntimePath);
}
}
// Check ResourceDir and ResourceDir/lib
for (auto P : {Resource.str().str(), (Resource + "/lib").str()}) {
if (auto F = findInDir(P)) {
OrcRuntimePath = *F;
return llvm::Error::success();
}
triedPaths.emplace_back(P);
}
} else {
// The binary was misplaced. Generic Backward Search (Climbing the tree)
// This allows unit tests in tools/clang/unittests to find the real lib/
llvm::SmallString<256> Cursor = Resource;
// ResourceDir-derived locations
llvm::StringRef Version = llvm::sys::path::filename(Resource);
llvm::StringRef OSName = TC.getOSLibName();
while (llvm::sys::path::has_parent_path(Cursor)) {
Cursor = llvm::sys::path::parent_path(Cursor).str();
// At each level, try standard relative layouts
for (auto Rel :
{(llvm::Twine("lib/clang/") + Version + "/lib/" + OSName).str(),
(llvm::Twine("lib/clang/") + Version + "/lib").str(),
(llvm::Twine("lib/") + OSName).str(), std::string("lib/clang")}) {
llvm::SmallString<256> Candidate = Cursor;
llvm::sys::path::append(Candidate, Rel);
if (auto F = findInDir(Candidate)) {
OrcRuntimePath = *F;
return llvm::Error::success();
}
triedPaths.emplace_back(std::string(Candidate.str()));
}
// Stop if we hit the root or go too far (safety check)
if (triedPaths.size() > 32)
break;
}
}
// Build a helpful error string if everything failed.
std::string Joined;
for (size_t i = 0; i < triedPaths.size(); ++i) {
if (i)
Joined += ", ";
Joined += triedPaths[i];
}
if (Joined.empty())
Joined = "<no candidate paths available>";
return llvm::make_error<llvm::StringError>(
llvm::formatv("OrcRuntime library not found in: {0}", Joined).str(),
llvm::inconvertibleErrorCode());
}
} // end namespace clang