Lang Hames 84fe1f63b0
[ORC] Switch to singleton pattern for UnwindInfoManager. (#126691)
The find-dynamic-unwind-info callback registration APIs in libunwind
limit the number of callbacks that can be registered. If we use multiple
UnwindInfoManager instances, each with their own own callback function
(as was the case prior to this patch) we can quickly exceed this limit
(see https://github.com/llvm/llvm-project/issues/126611).

This patch updates the UnwindInfoManager class to use a singleton
pattern, with the single instance shared between all LLVM JITs in the
process.

This change does _not_ apply to compact unwind info registered through
the ORC runtime (which currently installs its own callbacks).

As a bonus this change eliminates the need to load an IR "bouncer"
module to supply the unique callback for each instance, so support for
compact-unwind can be extended to the llvm-jitlink tools (which does not
support adding IR).
2025-02-12 10:00:10 +11:00

222 lines
7.2 KiB
C++

//===- llvm-jitlink-executor.cpp - Out-of-proc executor for llvm-jitlink -===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Simple out-of-process executor for llvm-jitlink.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/StringRef.h"
#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX, LLVM_ENABLE_THREADS
#include "llvm/ExecutionEngine/Orc/TargetProcess/DefaultHostBootstrapValues.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/ExecutorSharedMemoryMapperService.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/RegisterEHFrames.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/SimpleExecutorMemoryManager.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/SimpleRemoteEPCServer.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/UnwindInfoManager.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/DynamicLibrary.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
#include <cstring>
#include <sstream>
#ifdef LLVM_ON_UNIX
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif
using namespace llvm;
using namespace llvm::orc;
ExitOnError ExitOnErr;
LLVM_ATTRIBUTE_USED void linkComponents() {
errs() << (void *)&llvm_orc_registerEHFrameSectionWrapper
<< (void *)&llvm_orc_deregisterEHFrameSectionWrapper
<< (void *)&llvm_orc_registerJITLoaderGDBWrapper
<< (void *)&llvm_orc_registerJITLoaderGDBAllocAction;
}
void printErrorAndExit(Twine ErrMsg) {
#ifndef NDEBUG
const char *DebugOption = "[debug] ";
#else
const char *DebugOption = "";
#endif
errs() << "error: " << ErrMsg.str() << "\n\n"
<< "Usage:\n"
<< " llvm-jitlink-executor " << DebugOption
<< "[test-jitloadergdb] filedescs=<infd>,<outfd> [args...]\n"
<< " llvm-jitlink-executor " << DebugOption
<< "[test-jitloadergdb] listen=<host>:<port> [args...]\n";
exit(1);
}
int openListener(std::string Host, std::string PortStr) {
#ifndef LLVM_ON_UNIX
// FIXME: Add TCP support for Windows.
printErrorAndExit("listen option not supported");
return 0;
#else
addrinfo Hints{};
Hints.ai_family = AF_INET;
Hints.ai_socktype = SOCK_STREAM;
Hints.ai_flags = AI_PASSIVE;
addrinfo *AI;
if (int EC = getaddrinfo(nullptr, PortStr.c_str(), &Hints, &AI)) {
errs() << "Error setting up bind address: " << gai_strerror(EC) << "\n";
exit(1);
}
// Create a socket from first addrinfo structure returned by getaddrinfo.
int SockFD;
if ((SockFD = socket(AI->ai_family, AI->ai_socktype, AI->ai_protocol)) < 0) {
errs() << "Error creating socket: " << std::strerror(errno) << "\n";
exit(1);
}
// Avoid "Address already in use" errors.
const int Yes = 1;
if (setsockopt(SockFD, SOL_SOCKET, SO_REUSEADDR, &Yes, sizeof(int)) == -1) {
errs() << "Error calling setsockopt: " << std::strerror(errno) << "\n";
exit(1);
}
// Bind the socket to the desired port.
if (bind(SockFD, AI->ai_addr, AI->ai_addrlen) < 0) {
errs() << "Error on binding: " << std::strerror(errno) << "\n";
exit(1);
}
// Listen for incomming connections.
static constexpr int ConnectionQueueLen = 1;
listen(SockFD, ConnectionQueueLen);
#if defined(_AIX)
assert(Hi_32(AI->ai_addrlen) == 0 && "Field is a size_t on 64-bit AIX");
socklen_t AddrLen = Lo_32(AI->ai_addrlen);
return accept(SockFD, AI->ai_addr, &AddrLen);
#else
return accept(SockFD, AI->ai_addr, &AI->ai_addrlen);
#endif
#endif // LLVM_ON_UNIX
}
#if LLVM_ENABLE_THREADS
// JITLink debug support plugins put information about JITed code in this GDB
// JIT Interface global from OrcTargetProcess.
extern "C" LLVM_ABI struct jit_descriptor __jit_debug_descriptor;
static void *findLastDebugDescriptorEntryPtr() {
struct jit_code_entry *Last = __jit_debug_descriptor.first_entry;
while (Last && Last->next_entry)
Last = Last->next_entry;
return Last;
}
#endif
int main(int argc, char *argv[]) {
#if LLVM_ENABLE_THREADS
ExitOnErr.setBanner(std::string(argv[0]) + ": ");
unsigned FirstProgramArg = 1;
int InFD = 0;
int OutFD = 0;
if (argc < 2)
printErrorAndExit("insufficient arguments");
StringRef NextArg = argv[FirstProgramArg++];
#ifndef NDEBUG
if (NextArg == "debug") {
DebugFlag = true;
NextArg = argv[FirstProgramArg++];
}
#endif
std::vector<StringRef> TestOutputFlags;
while (NextArg.starts_with("test-")) {
TestOutputFlags.push_back(NextArg);
NextArg = argv[FirstProgramArg++];
}
if (llvm::is_contained(TestOutputFlags, "test-jitloadergdb"))
fprintf(stderr, "__jit_debug_descriptor.last_entry = 0x%016" PRIx64 "\n",
pointerToJITTargetAddress(findLastDebugDescriptorEntryPtr()));
StringRef SpecifierType, Specifier;
std::tie(SpecifierType, Specifier) = NextArg.split('=');
if (SpecifierType == "filedescs") {
StringRef FD1Str, FD2Str;
std::tie(FD1Str, FD2Str) = Specifier.split(',');
if (FD1Str.getAsInteger(10, InFD))
printErrorAndExit(FD1Str + " is not a valid file descriptor");
if (FD2Str.getAsInteger(10, OutFD))
printErrorAndExit(FD2Str + " is not a valid file descriptor");
} else if (SpecifierType == "listen") {
StringRef Host, PortStr;
std::tie(Host, PortStr) = Specifier.split(':');
int Port = 0;
if (PortStr.getAsInteger(10, Port))
printErrorAndExit("port number '" + PortStr + "' is not a valid integer");
InFD = OutFD = openListener(Host.str(), PortStr.str());
} else
printErrorAndExit("invalid specifier type \"" + SpecifierType + "\"");
auto Server =
ExitOnErr(SimpleRemoteEPCServer::Create<FDSimpleRemoteEPCTransport>(
[](SimpleRemoteEPCServer::Setup &S) -> Error {
S.setDispatcher(
std::make_unique<SimpleRemoteEPCServer::ThreadDispatcher>());
S.bootstrapSymbols() =
SimpleRemoteEPCServer::defaultBootstrapSymbols();
addDefaultBootstrapValuesForHostProcess(S.bootstrapMap(),
S.bootstrapSymbols());
#ifdef __APPLE__
if (UnwindInfoManager::TryEnable())
UnwindInfoManager::addBootstrapSymbols(S.bootstrapSymbols());
#endif // __APPLE__
S.services().push_back(
std::make_unique<rt_bootstrap::SimpleExecutorMemoryManager>());
S.services().push_back(
std::make_unique<
rt_bootstrap::ExecutorSharedMemoryMapperService>());
return Error::success();
},
InFD, OutFD));
ExitOnErr(Server->waitForDisconnect());
if (llvm::is_contained(TestOutputFlags, "test-jitloadergdb"))
fprintf(stderr, "__jit_debug_descriptor.last_entry = 0x%016" PRIx64 "\n",
pointerToJITTargetAddress(findLastDebugDescriptorEntryPtr()));
return 0;
#else
errs() << argv[0]
<< " error: this tool requires threads, but LLVM was "
"built with LLVM_ENABLE_THREADS=Off\n";
return 1;
#endif
}