| 1 | //===--- IncrementalExecutor.cpp - Incremental Execution --------*- 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 | // This has the implementation of the base facilities for incremental execution. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "clang/Interpreter/IncrementalExecutor.h" |
| 14 | #include "OrcIncrementalExecutor.h" |
| 15 | #ifdef __EMSCRIPTEN__ |
| 16 | #include "Wasm.h" |
| 17 | #endif // __EMSCRIPTEN__ |
| 18 | |
| 19 | #include "clang/Basic/TargetInfo.h" |
| 20 | #include "clang/Driver/Compilation.h" |
| 21 | #include "clang/Driver/Driver.h" |
| 22 | #include "clang/Driver/ToolChain.h" |
| 23 | |
| 24 | #include "llvm/ADT/SmallString.h" |
| 25 | #include "llvm/ADT/SmallVector.h" |
| 26 | #include "llvm/ADT/StringRef.h" |
| 27 | #include "llvm/ADT/Twine.h" |
| 28 | |
| 29 | #include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h" |
| 30 | #include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h" |
| 31 | #include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" |
| 32 | #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" |
| 33 | #include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" |
| 34 | #include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" |
| 35 | #include "llvm/ExecutionEngine/Orc/LLJIT.h" |
| 36 | #include "llvm/ExecutionEngine/Orc/MapperJITLinkMemoryManager.h" |
| 37 | #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" |
| 38 | #include "llvm/ExecutionEngine/Orc/Shared/SimpleRemoteEPCUtils.h" |
| 39 | #include "llvm/ExecutionEngine/Orc/SimpleRemoteEPC.h" |
| 40 | |
| 41 | #include "llvm/Support/Error.h" |
| 42 | #include "llvm/Support/FileSystem.h" |
| 43 | #include "llvm/Support/FormatVariadic.h" |
| 44 | #include "llvm/Support/Path.h" |
| 45 | #include "llvm/Support/raw_ostream.h" |
| 46 | |
| 47 | #include "llvm/TargetParser/Host.h" |
| 48 | |
| 49 | #include <array> |
| 50 | #include <functional> |
| 51 | #include <memory> |
| 52 | #include <optional> |
| 53 | #include <string> |
| 54 | #include <utility> |
| 55 | |
| 56 | #ifdef LLVM_ON_UNIX |
| 57 | #include <netdb.h> |
| 58 | #include <netinet/in.h> |
| 59 | #include <sys/socket.h> |
| 60 | #include <unistd.h> |
| 61 | #endif |
| 62 | |
| 63 | namespace clang { |
| 64 | IncrementalExecutorBuilder::~IncrementalExecutorBuilder() = default; |
| 65 | |
| 66 | static llvm::Expected<llvm::orc::JITTargetMachineBuilder> |
| 67 | createJITTargetMachineBuilder(const llvm::Triple &TT) { |
| 68 | if (TT.getTriple() == llvm::sys::getProcessTriple()) |
| 69 | // This fails immediately if the target backend is not registered |
| 70 | return llvm::orc::JITTargetMachineBuilder::detectHost(); |
| 71 | |
| 72 | // If the target backend is not registered, LLJITBuilder::create() will fail |
| 73 | return llvm::orc::JITTargetMachineBuilder(TT); |
| 74 | } |
| 75 | |
| 76 | static llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>> |
| 77 | createDefaultJITBuilder(llvm::orc::JITTargetMachineBuilder JTMB) { |
| 78 | auto JITBuilder = std::make_unique<llvm::orc::LLJITBuilder>(); |
| 79 | JITBuilder->setJITTargetMachineBuilder(std::move(JTMB)); |
| 80 | JITBuilder->setPrePlatformSetup([](llvm::orc::LLJIT &J) { |
| 81 | // Try to enable debugging of JIT'd code (only works with JITLink for |
| 82 | // ELF and MachO). |
| 83 | consumeError(Err: llvm::orc::enableDebuggerSupport(J)); |
| 84 | return llvm::Error::success(); |
| 85 | }); |
| 86 | return std::move(JITBuilder); |
| 87 | } |
| 88 | |
| 89 | Expected<std::unique_ptr<llvm::jitlink::JITLinkMemoryManager>> |
| 90 | createSharedMemoryManager(llvm::orc::SimpleRemoteEPC &SREPC, |
| 91 | unsigned SlabAllocateSize) { |
| 92 | llvm::orc::SharedMemoryMapper::SymbolAddrs SAs; |
| 93 | if (auto Err = SREPC.getBootstrapSymbols( |
| 94 | Pairs: {{SAs.Instance, |
| 95 | llvm::orc::rt::ExecutorSharedMemoryMapperServiceInstanceName}, |
| 96 | {SAs.Reserve, |
| 97 | llvm::orc::rt::ExecutorSharedMemoryMapperServiceReserveWrapperName}, |
| 98 | {SAs.Initialize, |
| 99 | llvm::orc::rt:: |
| 100 | ExecutorSharedMemoryMapperServiceInitializeWrapperName}, |
| 101 | {SAs.Deinitialize, |
| 102 | llvm::orc::rt:: |
| 103 | ExecutorSharedMemoryMapperServiceDeinitializeWrapperName}, |
| 104 | {SAs.Release, |
| 105 | llvm::orc::rt:: |
| 106 | ExecutorSharedMemoryMapperServiceReleaseWrapperName}})) |
| 107 | return std::move(Err); |
| 108 | |
| 109 | size_t SlabSize; |
| 110 | if (llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()) |
| 111 | SlabSize = 1024 * 1024; |
| 112 | else |
| 113 | SlabSize = 1024 * 1024 * 1024; |
| 114 | |
| 115 | if (SlabAllocateSize > 0) |
| 116 | SlabSize = SlabAllocateSize; |
| 117 | |
| 118 | return llvm::orc::MapperJITLinkMemoryManager::CreateWithMapper< |
| 119 | llvm::orc::SharedMemoryMapper>(ReservationGranularity: SlabSize, A&: SREPC, A&: SAs); |
| 120 | } |
| 121 | |
| 122 | static llvm::Expected< |
| 123 | std::pair<std::unique_ptr<llvm::orc::SimpleRemoteEPC>, uint32_t>> |
| 124 | launchExecutor(llvm::StringRef ExecutablePath, bool UseSharedMemory, |
| 125 | unsigned SlabAllocateSize, std::function<void()> CustomizeFork) { |
| 126 | #ifndef LLVM_ON_UNIX |
| 127 | // FIXME: Add support for Windows. |
| 128 | return llvm::make_error<llvm::StringError>( |
| 129 | "-" + ExecutablePath + " not supported on non-unix platforms" , |
| 130 | llvm::inconvertibleErrorCode()); |
| 131 | #elif !LLVM_ENABLE_THREADS |
| 132 | // Out of process mode using SimpleRemoteEPC depends on threads. |
| 133 | return llvm::make_error<llvm::StringError>( |
| 134 | "-" + ExecutablePath + |
| 135 | " requires threads, but LLVM was built with " |
| 136 | "LLVM_ENABLE_THREADS=Off" , |
| 137 | llvm::inconvertibleErrorCode()); |
| 138 | #else |
| 139 | |
| 140 | if (!llvm::sys::fs::can_execute(Path: ExecutablePath)) |
| 141 | return llvm::make_error<llvm::StringError>( |
| 142 | Args: llvm::formatv(Fmt: "Specified executor invalid: {0}" , Vals&: ExecutablePath), |
| 143 | Args: llvm::inconvertibleErrorCode()); |
| 144 | |
| 145 | constexpr int ReadEnd = 0; |
| 146 | constexpr int WriteEnd = 1; |
| 147 | |
| 148 | // Pipe FDs. |
| 149 | int ToExecutor[2]; |
| 150 | int FromExecutor[2]; |
| 151 | |
| 152 | uint32_t ChildPID; |
| 153 | |
| 154 | // Create pipes to/from the executor.. |
| 155 | if (pipe(pipedes: ToExecutor) != 0 || pipe(pipedes: FromExecutor) != 0) |
| 156 | return llvm::make_error<llvm::StringError>( |
| 157 | Args: "Unable to create pipe for executor" , Args: llvm::inconvertibleErrorCode()); |
| 158 | |
| 159 | ChildPID = fork(); |
| 160 | |
| 161 | if (ChildPID == 0) { |
| 162 | // In the child... |
| 163 | |
| 164 | // Close the parent ends of the pipes |
| 165 | close(fd: ToExecutor[WriteEnd]); |
| 166 | close(fd: FromExecutor[ReadEnd]); |
| 167 | |
| 168 | if (CustomizeFork) |
| 169 | CustomizeFork(); |
| 170 | |
| 171 | // Execute the child process. |
| 172 | std::unique_ptr<char[]> ExecutorPath, FDSpecifier; |
| 173 | { |
| 174 | ExecutorPath = std::make_unique<char[]>(num: ExecutablePath.size() + 1); |
| 175 | strcpy(dest: ExecutorPath.get(), src: ExecutablePath.data()); |
| 176 | |
| 177 | std::string FDSpecifierStr("filedescs=" ); |
| 178 | FDSpecifierStr += llvm::utostr(X: ToExecutor[ReadEnd]); |
| 179 | FDSpecifierStr += ','; |
| 180 | FDSpecifierStr += llvm::utostr(X: FromExecutor[WriteEnd]); |
| 181 | FDSpecifier = std::make_unique<char[]>(num: FDSpecifierStr.size() + 1); |
| 182 | strcpy(dest: FDSpecifier.get(), src: FDSpecifierStr.c_str()); |
| 183 | } |
| 184 | |
| 185 | char *const Args[] = {ExecutorPath.get(), FDSpecifier.get(), nullptr}; |
| 186 | int RC = execvp(file: ExecutorPath.get(), argv: Args); |
| 187 | if (RC != 0) { |
| 188 | llvm::errs() << "unable to launch out-of-process executor \"" |
| 189 | << ExecutorPath.get() << "\"\n" ; |
| 190 | exit(status: 1); |
| 191 | } |
| 192 | } |
| 193 | // else we're the parent... |
| 194 | |
| 195 | // Close the child ends of the pipes |
| 196 | close(fd: ToExecutor[ReadEnd]); |
| 197 | close(fd: FromExecutor[WriteEnd]); |
| 198 | |
| 199 | llvm::orc::SimpleRemoteEPC::Setup S = llvm::orc::SimpleRemoteEPC::Setup(); |
| 200 | if (UseSharedMemory) |
| 201 | S.CreateMemoryManager = |
| 202 | [SlabAllocateSize](llvm::orc::SimpleRemoteEPC &EPC) { |
| 203 | return createSharedMemoryManager(SREPC&: EPC, SlabAllocateSize); |
| 204 | }; |
| 205 | |
| 206 | auto EPCOrErr = |
| 207 | llvm::orc::SimpleRemoteEPC::Create<llvm::orc::FDSimpleRemoteEPCTransport>( |
| 208 | D: std::make_unique<llvm::orc::DynamicThreadPoolTaskDispatcher>( |
| 209 | args: std::nullopt), |
| 210 | S: std::move(S), TransportTCtorArgs&: FromExecutor[ReadEnd], TransportTCtorArgs&: ToExecutor[WriteEnd]); |
| 211 | if (!EPCOrErr) |
| 212 | return EPCOrErr.takeError(); |
| 213 | return std::make_pair(x: std::move(*EPCOrErr), y&: ChildPID); |
| 214 | #endif |
| 215 | } |
| 216 | |
| 217 | #if LLVM_ON_UNIX && LLVM_ENABLE_THREADS |
| 218 | |
| 219 | static Expected<int> connectTCPSocketImpl(std::string Host, |
| 220 | std::string PortStr) { |
| 221 | addrinfo *AI; |
| 222 | addrinfo Hints{}; |
| 223 | Hints.ai_family = AF_INET; |
| 224 | Hints.ai_socktype = SOCK_STREAM; |
| 225 | Hints.ai_flags = AI_NUMERICSERV; |
| 226 | |
| 227 | if (int EC = getaddrinfo(name: Host.c_str(), service: PortStr.c_str(), req: &Hints, pai: &AI)) |
| 228 | return llvm::make_error<llvm::StringError>( |
| 229 | Args: llvm::formatv(Fmt: "address resolution failed ({0})" , Vals: strerror(errnum: EC)), |
| 230 | Args: llvm::inconvertibleErrorCode()); |
| 231 | // Cycle through the returned addrinfo structures and connect to the first |
| 232 | // reachable endpoint. |
| 233 | int SockFD; |
| 234 | addrinfo *Server; |
| 235 | for (Server = AI; Server != nullptr; Server = Server->ai_next) { |
| 236 | // socket might fail, e.g. if the address family is not supported. Skip to |
| 237 | // the next addrinfo structure in such a case. |
| 238 | if ((SockFD = socket(domain: AI->ai_family, type: AI->ai_socktype, protocol: AI->ai_protocol)) < 0) |
| 239 | continue; |
| 240 | |
| 241 | // If connect returns null, we exit the loop with a working socket. |
| 242 | if (connect(fd: SockFD, addr: Server->ai_addr, len: Server->ai_addrlen) == 0) |
| 243 | break; |
| 244 | |
| 245 | close(fd: SockFD); |
| 246 | } |
| 247 | freeaddrinfo(ai: AI); |
| 248 | |
| 249 | // If we reached the end of the loop without connecting to a valid endpoint, |
| 250 | // dump the last error that was logged in socket() or connect(). |
| 251 | if (Server == nullptr) |
| 252 | return llvm::make_error<llvm::StringError>(Args: "invalid hostname" , |
| 253 | Args: llvm::inconvertibleErrorCode()); |
| 254 | |
| 255 | return SockFD; |
| 256 | } |
| 257 | |
| 258 | static llvm::Expected<std::unique_ptr<llvm::orc::SimpleRemoteEPC>> |
| 259 | connectTCPSocket(llvm::StringRef NetworkAddress, bool UseSharedMemory, |
| 260 | unsigned SlabAllocateSize) { |
| 261 | #ifndef LLVM_ON_UNIX |
| 262 | // FIXME: Add TCP support for Windows. |
| 263 | return llvm::make_error<llvm::StringError>( |
| 264 | "-" + NetworkAddress + " not supported on non-unix platforms" , |
| 265 | llvm::inconvertibleErrorCode()); |
| 266 | #elif !LLVM_ENABLE_THREADS |
| 267 | // Out of process mode using SimpleRemoteEPC depends on threads. |
| 268 | return llvm::make_error<llvm::StringError>( |
| 269 | "-" + NetworkAddress + |
| 270 | " requires threads, but LLVM was built with " |
| 271 | "LLVM_ENABLE_THREADS=Off" , |
| 272 | llvm::inconvertibleErrorCode()); |
| 273 | #else |
| 274 | |
| 275 | auto CreateErr = [NetworkAddress](Twine Details) { |
| 276 | return llvm::make_error<llvm::StringError>( |
| 277 | Args: formatv(Fmt: "Failed to connect TCP socket '{0}': {1}" , Vals: NetworkAddress, |
| 278 | Vals&: Details), |
| 279 | Args: llvm::inconvertibleErrorCode()); |
| 280 | }; |
| 281 | |
| 282 | StringRef Host, PortStr; |
| 283 | std::tie(args&: Host, args&: PortStr) = NetworkAddress.split(Separator: ':'); |
| 284 | if (Host.empty()) |
| 285 | return CreateErr("Host name for -" + NetworkAddress + " can not be empty" ); |
| 286 | if (PortStr.empty()) |
| 287 | return CreateErr("Port number in -" + NetworkAddress + " can not be empty" ); |
| 288 | int Port = 0; |
| 289 | if (PortStr.getAsInteger(Radix: 10, Result&: Port)) |
| 290 | return CreateErr("Port number '" + PortStr + "' is not a valid integer" ); |
| 291 | |
| 292 | Expected<int> SockFD = connectTCPSocketImpl(Host: Host.str(), PortStr: PortStr.str()); |
| 293 | if (!SockFD) |
| 294 | return SockFD.takeError(); |
| 295 | |
| 296 | llvm::orc::SimpleRemoteEPC::Setup S = llvm::orc::SimpleRemoteEPC::Setup(); |
| 297 | if (UseSharedMemory) |
| 298 | S.CreateMemoryManager = |
| 299 | [SlabAllocateSize](llvm::orc::SimpleRemoteEPC &EPC) { |
| 300 | return createSharedMemoryManager(SREPC&: EPC, SlabAllocateSize); |
| 301 | }; |
| 302 | |
| 303 | return llvm::orc::SimpleRemoteEPC::Create< |
| 304 | llvm::orc::FDSimpleRemoteEPCTransport>( |
| 305 | D: std::make_unique<llvm::orc::DynamicThreadPoolTaskDispatcher>( |
| 306 | args: std::nullopt), |
| 307 | S: std::move(S), TransportTCtorArgs&: *SockFD, TransportTCtorArgs&: *SockFD); |
| 308 | #endif |
| 309 | } |
| 310 | #endif // _WIN32 |
| 311 | |
| 312 | static llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>> |
| 313 | createLLJITBuilder(std::unique_ptr<llvm::orc::ExecutorProcessControl> EPC, |
| 314 | llvm::StringRef OrcRuntimePath) { |
| 315 | auto JTMB = createJITTargetMachineBuilder(TT: EPC->getTargetTriple()); |
| 316 | if (!JTMB) |
| 317 | return JTMB.takeError(); |
| 318 | auto JB = createDefaultJITBuilder(JTMB: std::move(*JTMB)); |
| 319 | if (!JB) |
| 320 | return JB.takeError(); |
| 321 | |
| 322 | (*JB)->setExecutorProcessControl(std::move(EPC)); |
| 323 | (*JB)->setPlatformSetUp( |
| 324 | llvm::orc::ExecutorNativePlatform(OrcRuntimePath.str())); |
| 325 | |
| 326 | return std::move(*JB); |
| 327 | } |
| 328 | |
| 329 | static llvm::Expected< |
| 330 | std::pair<std::unique_ptr<llvm::orc::LLJITBuilder>, uint32_t>> |
| 331 | outOfProcessJITBuilder(const IncrementalExecutorBuilder &IncrExecutorBuilder) { |
| 332 | std::unique_ptr<llvm::orc::ExecutorProcessControl> EPC; |
| 333 | uint32_t childPid = -1; |
| 334 | if (!IncrExecutorBuilder.OOPExecutor.empty()) { |
| 335 | // Launch an out-of-process executor locally in a child process. |
| 336 | auto ResultOrErr = launchExecutor(ExecutablePath: IncrExecutorBuilder.OOPExecutor, |
| 337 | UseSharedMemory: IncrExecutorBuilder.UseSharedMemory, |
| 338 | SlabAllocateSize: IncrExecutorBuilder.SlabAllocateSize, |
| 339 | CustomizeFork: IncrExecutorBuilder.CustomizeFork); |
| 340 | if (!ResultOrErr) |
| 341 | return ResultOrErr.takeError(); |
| 342 | childPid = ResultOrErr->second; |
| 343 | auto EPCOrErr = std::move(ResultOrErr->first); |
| 344 | EPC = std::move(EPCOrErr); |
| 345 | } else if (IncrExecutorBuilder.OOPExecutorConnect != "" ) { |
| 346 | #if LLVM_ON_UNIX && LLVM_ENABLE_THREADS |
| 347 | auto EPCOrErr = connectTCPSocket(NetworkAddress: IncrExecutorBuilder.OOPExecutorConnect, |
| 348 | UseSharedMemory: IncrExecutorBuilder.UseSharedMemory, |
| 349 | SlabAllocateSize: IncrExecutorBuilder.SlabAllocateSize); |
| 350 | if (!EPCOrErr) |
| 351 | return EPCOrErr.takeError(); |
| 352 | EPC = std::move(*EPCOrErr); |
| 353 | #else |
| 354 | return llvm::make_error<llvm::StringError>( |
| 355 | "Out-of-process JIT over TCP is not supported on this platform" , |
| 356 | std::error_code()); |
| 357 | #endif |
| 358 | } |
| 359 | |
| 360 | std::unique_ptr<llvm::orc::LLJITBuilder> JB; |
| 361 | if (EPC) { |
| 362 | auto JBOrErr = |
| 363 | createLLJITBuilder(EPC: std::move(EPC), OrcRuntimePath: IncrExecutorBuilder.OrcRuntimePath); |
| 364 | if (!JBOrErr) |
| 365 | return JBOrErr.takeError(); |
| 366 | JB = std::move(*JBOrErr); |
| 367 | } |
| 368 | |
| 369 | return std::make_pair(x: std::move(JB), y&: childPid); |
| 370 | } |
| 371 | |
| 372 | llvm::Expected<std::unique_ptr<IncrementalExecutor>> |
| 373 | IncrementalExecutorBuilder::create(llvm::orc::ThreadSafeContext &TSC, |
| 374 | const clang::TargetInfo &TI) { |
| 375 | if (IE) |
| 376 | return std::move(IE); |
| 377 | llvm::Triple TT = TI.getTriple(); |
| 378 | if (!TT.isOSWindows() && IsOutOfProcess) { |
| 379 | if (!JITBuilder) { |
| 380 | auto ResOrErr = outOfProcessJITBuilder(IncrExecutorBuilder: *this); |
| 381 | if (!ResOrErr) |
| 382 | return ResOrErr.takeError(); |
| 383 | JITBuilder = std::move(ResOrErr->first); |
| 384 | ExecutorPID = ResOrErr->second; |
| 385 | } |
| 386 | if (!JITBuilder) |
| 387 | return llvm::make_error<llvm::StringError>( |
| 388 | Args: "Operation failed. No LLJITBuilder for out-of-process JIT" , |
| 389 | Args: std::error_code()); |
| 390 | } |
| 391 | |
| 392 | if (!JITBuilder) { |
| 393 | auto JTMB = createJITTargetMachineBuilder(TT); |
| 394 | if (!JTMB) |
| 395 | return JTMB.takeError(); |
| 396 | if (CM) |
| 397 | JTMB->setCodeModel(CM); |
| 398 | auto JB = createDefaultJITBuilder(JTMB: std::move(*JTMB)); |
| 399 | if (!JB) |
| 400 | return JB.takeError(); |
| 401 | JITBuilder = std::move(*JB); |
| 402 | } |
| 403 | |
| 404 | llvm::Error Err = llvm::Error::success(); |
| 405 | std::unique_ptr<IncrementalExecutor> Executor; |
| 406 | #ifdef __EMSCRIPTEN__ |
| 407 | Executor = std::make_unique<WasmIncrementalExecutor>(Err); |
| 408 | #else |
| 409 | Executor = std::make_unique<OrcIncrementalExecutor>(args&: TSC, args&: *JITBuilder, args&: Err); |
| 410 | #endif |
| 411 | |
| 412 | if (Err) |
| 413 | return std::move(Err); |
| 414 | |
| 415 | return std::move(Executor); |
| 416 | } |
| 417 | |
| 418 | llvm::Error IncrementalExecutorBuilder::UpdateOrcRuntimePath( |
| 419 | const clang::driver::Compilation &C) { |
| 420 | if (!IsOutOfProcess) |
| 421 | return llvm::Error::success(); |
| 422 | |
| 423 | const clang::driver::Driver &D = C.getDriver(); |
| 424 | const clang::driver::ToolChain &TC = C.getDefaultToolChain(); |
| 425 | |
| 426 | llvm::SmallVector<std::string, 2> OrcRTLibNames; |
| 427 | |
| 428 | // Get canonical compiler-rt path |
| 429 | std::string CompilerRTPath = TC.getCompilerRT(Args: C.getArgs(), Component: "orc_rt" ); |
| 430 | llvm::StringRef CanonicalFilename = llvm::sys::path::filename(path: CompilerRTPath); |
| 431 | |
| 432 | if (CanonicalFilename.empty()) { |
| 433 | return llvm::make_error<llvm::StringError>( |
| 434 | Args: "Could not determine OrcRuntime filename from ToolChain" , |
| 435 | Args: llvm::inconvertibleErrorCode()); |
| 436 | } |
| 437 | |
| 438 | OrcRTLibNames.push_back(Elt: CanonicalFilename.str()); |
| 439 | |
| 440 | // Derive legacy spelling (libclang_rt.orc_rt -> orc_rt) |
| 441 | llvm::StringRef LegacySuffix = CanonicalFilename; |
| 442 | if (LegacySuffix.consume_front(Prefix: "libclang_rt." )) { |
| 443 | OrcRTLibNames.push_back(Elt: ("lib" + LegacySuffix).str()); |
| 444 | } |
| 445 | |
| 446 | // Extract directory |
| 447 | llvm::SmallString<256> OrcRTDir(CompilerRTPath); |
| 448 | llvm::sys::path::remove_filename(path&: OrcRTDir); |
| 449 | |
| 450 | llvm::SmallVector<std::string, 8> triedPaths; |
| 451 | |
| 452 | auto findInDir = [&](llvm::StringRef Dir) -> std::optional<std::string> { |
| 453 | for (const auto &LibName : OrcRTLibNames) { |
| 454 | llvm::SmallString<256> FullPath = Dir; |
| 455 | llvm::sys::path::append(path&: FullPath, a: LibName); |
| 456 | if (llvm::sys::fs::exists(Path: FullPath)) |
| 457 | return std::string(FullPath.str()); |
| 458 | triedPaths.push_back(Elt: std::string(FullPath.str())); |
| 459 | } |
| 460 | return std::nullopt; |
| 461 | }; |
| 462 | |
| 463 | // Try the primary directory first |
| 464 | if (auto Found = findInDir(OrcRTDir)) { |
| 465 | OrcRuntimePath = *Found; |
| 466 | return llvm::Error::success(); |
| 467 | } |
| 468 | |
| 469 | // We want to find the relative path from the Driver to the OrcRTDir |
| 470 | // to replicate that structure elsewhere if needed. |
| 471 | llvm::StringRef Rel = OrcRTDir.str(); |
| 472 | if (!Rel.consume_front(Prefix: llvm::sys::path::parent_path(path: D.Dir))) { |
| 473 | return llvm::make_error<llvm::StringError>( |
| 474 | Args: llvm::formatv(Fmt: "OrcRuntime library path ({0}) is not located within the " |
| 475 | "Clang resource directory ({1}). Check your installation " |
| 476 | "or provide an explicit path via -resource-dir." , |
| 477 | Vals&: OrcRTDir, Vals: D.Dir) |
| 478 | .str(), |
| 479 | Args: llvm::inconvertibleErrorCode()); |
| 480 | } |
| 481 | |
| 482 | // Generic Backward Search (Climbing the tree) |
| 483 | // This is useful for unit tests or relocated toolchains |
| 484 | llvm::SmallString<256> Cursor(D.Dir); // Start from the driver directory |
| 485 | while (llvm::sys::path::has_parent_path(path: Cursor)) { |
| 486 | Cursor = llvm::sys::path::parent_path(path: Cursor).str(); |
| 487 | llvm::SmallString<256> Candidate = Cursor; |
| 488 | llvm::sys::path::append(path&: Candidate, a: Rel); |
| 489 | |
| 490 | if (auto Found = findInDir(Candidate)) { |
| 491 | OrcRuntimePath = *Found; |
| 492 | return llvm::Error::success(); |
| 493 | } |
| 494 | |
| 495 | // Safety check |
| 496 | if (triedPaths.size() > 32) |
| 497 | break; |
| 498 | } |
| 499 | |
| 500 | // Build a helpful error string |
| 501 | std::string Joined; |
| 502 | for (size_t i = 0; i < triedPaths.size(); ++i) { |
| 503 | if (i > 0) |
| 504 | Joined += "\n " ; |
| 505 | Joined += triedPaths[i]; |
| 506 | } |
| 507 | |
| 508 | return llvm::make_error<llvm::StringError>( |
| 509 | Args: llvm::formatv(Fmt: "OrcRuntime library not found. Checked: {0}" , |
| 510 | Vals: Joined.empty() ? "<none>" : Joined) |
| 511 | .str(), |
| 512 | Args: std::make_error_code(e: std::errc::no_such_file_or_directory)); |
| 513 | } |
| 514 | |
| 515 | } // end namespace clang |
| 516 | |