| 1 | //===-- RemoteJITUtils.cpp - Utilities for remote-JITing --------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // FIXME: Unify this code with similar functionality in llvm-jitlink. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "clang/Interpreter/RemoteJITUtils.h" |
| 14 | |
| 15 | #include "llvm/ADT/StringExtras.h" |
| 16 | #include "llvm/ExecutionEngine/Orc/DebugObjectManagerPlugin.h" |
| 17 | #include "llvm/ExecutionEngine/Orc/EPCDebugObjectRegistrar.h" |
| 18 | #include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" |
| 19 | #include "llvm/ExecutionEngine/Orc/MapperJITLinkMemoryManager.h" |
| 20 | #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" |
| 21 | #include "llvm/ExecutionEngine/Orc/Shared/SimpleRemoteEPCUtils.h" |
| 22 | #include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h" |
| 23 | #include "llvm/Support/FileSystem.h" |
| 24 | #include "llvm/Support/Path.h" |
| 25 | |
| 26 | #ifdef LLVM_ON_UNIX |
| 27 | #include <netdb.h> |
| 28 | #include <netinet/in.h> |
| 29 | #include <sys/socket.h> |
| 30 | #include <unistd.h> |
| 31 | #endif // LLVM_ON_UNIX |
| 32 | |
| 33 | using namespace llvm; |
| 34 | using namespace llvm::orc; |
| 35 | |
| 36 | Expected<uint64_t> getSlabAllocSize(StringRef SizeString) { |
| 37 | SizeString = SizeString.trim(); |
| 38 | |
| 39 | uint64_t Units = 1024; |
| 40 | |
| 41 | if (SizeString.ends_with_insensitive(Suffix: "kb" )) |
| 42 | SizeString = SizeString.drop_back(N: 2).rtrim(); |
| 43 | else if (SizeString.ends_with_insensitive(Suffix: "mb" )) { |
| 44 | Units = 1024 * 1024; |
| 45 | SizeString = SizeString.drop_back(N: 2).rtrim(); |
| 46 | } else if (SizeString.ends_with_insensitive(Suffix: "gb" )) { |
| 47 | Units = 1024 * 1024 * 1024; |
| 48 | SizeString = SizeString.drop_back(N: 2).rtrim(); |
| 49 | } |
| 50 | |
| 51 | uint64_t SlabSize = 0; |
| 52 | if (SizeString.getAsInteger(Radix: 10, Result&: SlabSize)) |
| 53 | return make_error<StringError>(Args: "Invalid numeric format for slab size" , |
| 54 | Args: inconvertibleErrorCode()); |
| 55 | |
| 56 | return SlabSize * Units; |
| 57 | } |
| 58 | |
| 59 | Expected<std::unique_ptr<jitlink::JITLinkMemoryManager>> |
| 60 | createSharedMemoryManager(SimpleRemoteEPC &SREPC, |
| 61 | StringRef SlabAllocateSizeString) { |
| 62 | SharedMemoryMapper::SymbolAddrs SAs; |
| 63 | if (auto Err = SREPC.getBootstrapSymbols( |
| 64 | Pairs: {{SAs.Instance, rt::ExecutorSharedMemoryMapperServiceInstanceName}, |
| 65 | {SAs.Reserve, |
| 66 | rt::ExecutorSharedMemoryMapperServiceReserveWrapperName}, |
| 67 | {SAs.Initialize, |
| 68 | rt::ExecutorSharedMemoryMapperServiceInitializeWrapperName}, |
| 69 | {SAs.Deinitialize, |
| 70 | rt::ExecutorSharedMemoryMapperServiceDeinitializeWrapperName}, |
| 71 | {SAs.Release, |
| 72 | rt::ExecutorSharedMemoryMapperServiceReleaseWrapperName}})) |
| 73 | return std::move(Err); |
| 74 | |
| 75 | #ifdef _WIN32 |
| 76 | size_t SlabSize = 1024 * 1024; |
| 77 | #else |
| 78 | size_t SlabSize = 1024 * 1024 * 1024; |
| 79 | #endif |
| 80 | |
| 81 | if (!SlabAllocateSizeString.empty()) { |
| 82 | if (Expected<uint64_t> S = getSlabAllocSize(SizeString: SlabAllocateSizeString)) |
| 83 | SlabSize = *S; |
| 84 | else |
| 85 | return S.takeError(); |
| 86 | } |
| 87 | |
| 88 | return MapperJITLinkMemoryManager::CreateWithMapper<SharedMemoryMapper>( |
| 89 | ReservationGranularity: SlabSize, A&: SREPC, A&: SAs); |
| 90 | } |
| 91 | |
| 92 | Expected<std::unique_ptr<SimpleRemoteEPC>> |
| 93 | launchExecutor(StringRef ExecutablePath, bool UseSharedMemory, |
| 94 | llvm::StringRef SlabAllocateSizeString) { |
| 95 | #ifndef LLVM_ON_UNIX |
| 96 | // FIXME: Add support for Windows. |
| 97 | return make_error<StringError>("-" + ExecutablePath + |
| 98 | " not supported on non-unix platforms" , |
| 99 | inconvertibleErrorCode()); |
| 100 | #elif !LLVM_ENABLE_THREADS |
| 101 | // Out of process mode using SimpleRemoteEPC depends on threads. |
| 102 | return make_error<StringError>( |
| 103 | "-" + ExecutablePath + |
| 104 | " requires threads, but LLVM was built with " |
| 105 | "LLVM_ENABLE_THREADS=Off" , |
| 106 | inconvertibleErrorCode()); |
| 107 | #else |
| 108 | |
| 109 | if (!sys::fs::can_execute(Path: ExecutablePath)) |
| 110 | return make_error<StringError>( |
| 111 | Args: formatv(Fmt: "Specified executor invalid: {0}" , Vals&: ExecutablePath), |
| 112 | Args: inconvertibleErrorCode()); |
| 113 | |
| 114 | constexpr int ReadEnd = 0; |
| 115 | constexpr int WriteEnd = 1; |
| 116 | |
| 117 | // Pipe FDs. |
| 118 | int ToExecutor[2]; |
| 119 | int FromExecutor[2]; |
| 120 | |
| 121 | pid_t ChildPID; |
| 122 | |
| 123 | // Create pipes to/from the executor.. |
| 124 | if (pipe(pipedes: ToExecutor) != 0 || pipe(pipedes: FromExecutor) != 0) |
| 125 | return make_error<StringError>(Args: "Unable to create pipe for executor" , |
| 126 | Args: inconvertibleErrorCode()); |
| 127 | |
| 128 | ChildPID = fork(); |
| 129 | |
| 130 | if (ChildPID == 0) { |
| 131 | // In the child... |
| 132 | |
| 133 | // Close the parent ends of the pipes |
| 134 | close(fd: ToExecutor[WriteEnd]); |
| 135 | close(fd: FromExecutor[ReadEnd]); |
| 136 | |
| 137 | // Execute the child process. |
| 138 | std::unique_ptr<char[]> ExecutorPath, FDSpecifier; |
| 139 | { |
| 140 | ExecutorPath = std::make_unique<char[]>(num: ExecutablePath.size() + 1); |
| 141 | strcpy(dest: ExecutorPath.get(), src: ExecutablePath.data()); |
| 142 | |
| 143 | std::string FDSpecifierStr("filedescs=" ); |
| 144 | FDSpecifierStr += utostr(X: ToExecutor[ReadEnd]); |
| 145 | FDSpecifierStr += ','; |
| 146 | FDSpecifierStr += utostr(X: FromExecutor[WriteEnd]); |
| 147 | FDSpecifier = std::make_unique<char[]>(num: FDSpecifierStr.size() + 1); |
| 148 | strcpy(dest: FDSpecifier.get(), src: FDSpecifierStr.c_str()); |
| 149 | } |
| 150 | |
| 151 | char *const Args[] = {ExecutorPath.get(), FDSpecifier.get(), nullptr}; |
| 152 | int RC = execvp(file: ExecutorPath.get(), argv: Args); |
| 153 | if (RC != 0) { |
| 154 | errs() << "unable to launch out-of-process executor \"" |
| 155 | << ExecutorPath.get() << "\"\n" ; |
| 156 | exit(status: 1); |
| 157 | } |
| 158 | } |
| 159 | // else we're the parent... |
| 160 | |
| 161 | // Close the child ends of the pipes |
| 162 | close(fd: ToExecutor[ReadEnd]); |
| 163 | close(fd: FromExecutor[WriteEnd]); |
| 164 | |
| 165 | SimpleRemoteEPC::Setup S = SimpleRemoteEPC::Setup(); |
| 166 | if (UseSharedMemory) |
| 167 | S.CreateMemoryManager = [SlabAllocateSizeString](SimpleRemoteEPC &EPC) { |
| 168 | return createSharedMemoryManager(SREPC&: EPC, SlabAllocateSizeString); |
| 169 | }; |
| 170 | |
| 171 | return SimpleRemoteEPC::Create<FDSimpleRemoteEPCTransport>( |
| 172 | D: std::make_unique<DynamicThreadPoolTaskDispatcher>(args: std::nullopt), |
| 173 | S: std::move(S), TransportTCtorArgs&: FromExecutor[ReadEnd], TransportTCtorArgs&: ToExecutor[WriteEnd]); |
| 174 | #endif |
| 175 | } |
| 176 | |
| 177 | #if LLVM_ON_UNIX && LLVM_ENABLE_THREADS |
| 178 | |
| 179 | static Expected<int> connectTCPSocketImpl(std::string Host, |
| 180 | std::string PortStr) { |
| 181 | addrinfo *AI; |
| 182 | addrinfo Hints{}; |
| 183 | Hints.ai_family = AF_INET; |
| 184 | Hints.ai_socktype = SOCK_STREAM; |
| 185 | Hints.ai_flags = AI_NUMERICSERV; |
| 186 | |
| 187 | if (int EC = getaddrinfo(name: Host.c_str(), service: PortStr.c_str(), req: &Hints, pai: &AI)) |
| 188 | return make_error<StringError>( |
| 189 | Args: formatv(Fmt: "address resolution failed ({0})" , Vals: gai_strerror(ecode: EC)), |
| 190 | Args: inconvertibleErrorCode()); |
| 191 | // Cycle through the returned addrinfo structures and connect to the first |
| 192 | // reachable endpoint. |
| 193 | int SockFD; |
| 194 | addrinfo *Server; |
| 195 | for (Server = AI; Server != nullptr; Server = Server->ai_next) { |
| 196 | // socket might fail, e.g. if the address family is not supported. Skip to |
| 197 | // the next addrinfo structure in such a case. |
| 198 | if ((SockFD = socket(domain: AI->ai_family, type: AI->ai_socktype, protocol: AI->ai_protocol)) < 0) |
| 199 | continue; |
| 200 | |
| 201 | // If connect returns null, we exit the loop with a working socket. |
| 202 | if (connect(fd: SockFD, addr: Server->ai_addr, len: Server->ai_addrlen) == 0) |
| 203 | break; |
| 204 | |
| 205 | close(fd: SockFD); |
| 206 | } |
| 207 | freeaddrinfo(ai: AI); |
| 208 | |
| 209 | // If we reached the end of the loop without connecting to a valid endpoint, |
| 210 | // dump the last error that was logged in socket() or connect(). |
| 211 | if (Server == nullptr) |
| 212 | return make_error<StringError>(Args: "invalid hostname" , |
| 213 | Args: inconvertibleErrorCode()); |
| 214 | |
| 215 | return SockFD; |
| 216 | } |
| 217 | #endif |
| 218 | |
| 219 | Expected<std::unique_ptr<SimpleRemoteEPC>> |
| 220 | connectTCPSocket(StringRef NetworkAddress, bool UseSharedMemory, |
| 221 | llvm::StringRef SlabAllocateSizeString) { |
| 222 | #ifndef LLVM_ON_UNIX |
| 223 | // FIXME: Add TCP support for Windows. |
| 224 | return make_error<StringError>("-" + NetworkAddress + |
| 225 | " not supported on non-unix platforms" , |
| 226 | inconvertibleErrorCode()); |
| 227 | #elif !LLVM_ENABLE_THREADS |
| 228 | // Out of process mode using SimpleRemoteEPC depends on threads. |
| 229 | return make_error<StringError>( |
| 230 | "-" + NetworkAddress + |
| 231 | " requires threads, but LLVM was built with " |
| 232 | "LLVM_ENABLE_THREADS=Off" , |
| 233 | inconvertibleErrorCode()); |
| 234 | #else |
| 235 | |
| 236 | auto CreateErr = [NetworkAddress](Twine Details) { |
| 237 | return make_error<StringError>( |
| 238 | Args: formatv(Fmt: "Failed to connect TCP socket '{0}': {1}" , Vals: NetworkAddress, |
| 239 | Vals&: Details), |
| 240 | Args: inconvertibleErrorCode()); |
| 241 | }; |
| 242 | |
| 243 | StringRef Host, PortStr; |
| 244 | std::tie(args&: Host, args&: PortStr) = NetworkAddress.split(Separator: ':'); |
| 245 | if (Host.empty()) |
| 246 | return CreateErr("Host name for -" + NetworkAddress + " can not be empty" ); |
| 247 | if (PortStr.empty()) |
| 248 | return CreateErr("Port number in -" + NetworkAddress + " can not be empty" ); |
| 249 | int Port = 0; |
| 250 | if (PortStr.getAsInteger(Radix: 10, Result&: Port)) |
| 251 | return CreateErr("Port number '" + PortStr + "' is not a valid integer" ); |
| 252 | |
| 253 | Expected<int> SockFD = connectTCPSocketImpl(Host: Host.str(), PortStr: PortStr.str()); |
| 254 | if (!SockFD) |
| 255 | return SockFD.takeError(); |
| 256 | |
| 257 | SimpleRemoteEPC::Setup S = SimpleRemoteEPC::Setup(); |
| 258 | if (UseSharedMemory) |
| 259 | S.CreateMemoryManager = [SlabAllocateSizeString](SimpleRemoteEPC &EPC) { |
| 260 | return createSharedMemoryManager(SREPC&: EPC, SlabAllocateSizeString); |
| 261 | }; |
| 262 | |
| 263 | return SimpleRemoteEPC::Create<FDSimpleRemoteEPCTransport>( |
| 264 | D: std::make_unique<DynamicThreadPoolTaskDispatcher>(args: std::nullopt), |
| 265 | S: std::move(S), TransportTCtorArgs&: *SockFD, TransportTCtorArgs&: *SockFD); |
| 266 | #endif |
| 267 | } |
| 268 | |