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
33using namespace llvm;
34using namespace llvm::orc;
35
36Expected<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
59Expected<std::unique_ptr<jitlink::JITLinkMemoryManager>>
60createSharedMemoryManager(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
92Expected<std::unique_ptr<SimpleRemoteEPC>>
93launchExecutor(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
179static 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
219Expected<std::unique_ptr<SimpleRemoteEPC>>
220connectTCPSocket(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