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 | |