llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-llvm-support Author: Anutosh Bhat (anutosh491) <details> <summary>Changes</summary> Please read: [RFC: Embeddable LLVM tool drivers for long-lived hosts](https://discourse.llvm.org/t/rfc-embeddable-llvm-tool-drivers-for-long-lived-hosts/91754) Depends on #<!-- -->221996 and #<!-- -->222531. This is the third small patch in the embeddable-tools series. After #<!-- -->222531 allows session-owned Clang to execute multiple `cc1` jobs, this patch allows Clang to dispatch a registered linker through the same `ToolSession`. Clang continues to construct its normal jobs. A linker job runs in-process only when its executable is registered and belongs to the current session; unrelated external linkers retain the normal subprocess path. This enables commands such as: ```bash clang++ --target=wasm32-unknown-emscripten add.cpp main.cpp -o project.wasm ``` to execute as: ```text ToolSession └── clang ├── cc1(add.cpp) ├── cc1(main.cpp) └── wasm-ld(add.o, main.o) ``` The mechanism is target-independent. The tests cover in-process WebAssembly and AArch64/ELF links, ensure an unrelated external `ld` is not intercepted, and exercise repeated LLD execution. Session-owned LLD uses its existing reusable cleanup path before returning to Clang. Standalone LLD keeps its current fast path. --- Patch is 31.63 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/224023.diff 17 Files Affected: - (modified) clang/include/clang/Driver/Job.h (+20) - (modified) clang/lib/Driver/Job.cpp (+27) - (modified) clang/test/Driver/cc1-spawnprocess.c (+2) - (modified) clang/test/Driver/clang-translation.c (-2) - (added) clang/test/Driver/in-process-link.c (+48) - (added) clang/test/Driver/in-process-multiple-cc1.c (+36) - (modified) clang/tools/driver/driver.cpp (+35) - (modified) lld/tools/lld/lld.cpp (+9-5) - (modified) llvm/include/llvm/Support/LLVMDriver.h (+73-1) - (modified) llvm/lib/Support/CMakeLists.txt (+1) - (added) llvm/lib/Support/LLVMToolSession.cpp (+131) - (modified) llvm/test/tools/llvm-driver/passthrough-lld.test (+3) - (added) llvm/test/tools/llvm-driver/session-dispatch.test (+9) - (modified) llvm/tools/llvm-driver/llvm-driver.cpp (+19-47) - (modified) llvm/unittests/Support/CMakeLists.txt (+1) - (added) llvm/unittests/Support/LLVMToolSession/CMakeLists.txt (+20) - (added) llvm/unittests/Support/LLVMToolSession/LLVMToolSessionTest.cpp (+133) ``````````diff diff --git a/clang/include/clang/Driver/Job.h b/clang/include/clang/Driver/Job.h index 03779be5b5a6a..03db9c9cc236a 100644 --- a/clang/include/clang/Driver/Job.h +++ b/clang/include/clang/Driver/Job.h @@ -24,6 +24,10 @@ #include <utility> #include <vector> +namespace llvm { +class ToolContext; +} + namespace clang { namespace driver { @@ -151,6 +155,10 @@ class Command { /// Information on executable run provided by OS. mutable std::optional<llvm::sys::ProcessStatistics> ProcStat; + /// Non-owning host context used to invoke this command without spawning a + /// process. + const llvm::ToolContext *InProcessToolContext = nullptr; + /// The bound architecture for this command (e.g. "arm64", "gfx90a"). std::string BoundArchStr; @@ -177,6 +185,9 @@ class Command { /// Whether the command will be executed in this process or not. bool InProcess = false; + /// Whether this command accepts -disable-free. + bool SupportsDisableFree = false; + Command(const Action &Source, const Tool &Creator, ResponseFileSupport ResponseSupport, const char *Executable, const llvm::opt::ArgStringList &Arguments, ArrayRef<InputInfo> Inputs, @@ -238,6 +249,15 @@ class Command { void replaceExecutable(const char *Exe) { Executable = Exe; } + /// Execute this command through a tool registered with the host session. + void setInProcessToolContext(const llvm::ToolContext &Context) { + InProcessToolContext = &Context; + InProcess = true; + } + + /// Ensure that this command frees memory before returning to its caller. + void enableFree(); + const char *getExecutable() const { return Executable; } const llvm::opt::ArgStringList &getArguments() const { return Arguments; } diff --git a/clang/lib/Driver/Job.cpp b/clang/lib/Driver/Job.cpp index da7a1f2e07e90..6cbe7e0252721 100644 --- a/clang/lib/Driver/Job.cpp +++ b/clang/lib/Driver/Job.cpp @@ -13,6 +13,7 @@ #include "clang/Driver/Tool.h" #include "clang/Driver/ToolChain.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" @@ -22,6 +23,7 @@ #include "llvm/Support/CrashRecoveryContext.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/IOSandbox.h" +#include "llvm/Support/LLVMDriver.h" #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Program.h" @@ -205,6 +207,9 @@ rewriteIncludes(const llvm::ArrayRef<const char *> &Args, size_t Idx, void Command::Print(raw_ostream &OS, const char *Terminator, bool Quote, CrashReportInfo *CrashInfo) const { + if (InProcessToolContext) + OS << " (in-process)\n"; + // Always quote the exe. OS << ' '; llvm::sys::printArg(OS, Executable, /*Quote=*/true); @@ -367,6 +372,21 @@ int Command::Execute(ArrayRef<std::optional<StringRef>> Redirects, auto Args = llvm::toStringRefArray(Argv.data()); + if (InProcessToolContext) { + llvm::ErrorOr<int> Result = InProcessToolContext->callTool( + ArrayRef<const char *>(Argv).drop_back()); + if (!Result) { + if (ErrMsg) + *ErrMsg = Result.getError().message(); + if (ExecutionFailed) + *ExecutionFailed = true; + return -1; + } + if (ExecutionFailed) + *ExecutionFailed = false; + return *Result; + } + // Use Job-specific redirect files if they are present. if (!RedirectFiles.empty()) { std::vector<std::optional<StringRef>> RedirectFilesOptional; @@ -387,6 +407,12 @@ int Command::Execute(ArrayRef<std::optional<StringRef>> Redirects, ErrMsg, ExecutionFailed, &ProcStat); } +void Command::enableFree() { + llvm::erase_if(Arguments, [](const char *Arg) { + return StringRef(Arg) == "-disable-free"; + }); +} + CC1Command::CC1Command(const Action &Source, const Tool &Creator, ResponseFileSupport ResponseSupport, const char *Executable, @@ -396,6 +422,7 @@ CC1Command::CC1Command(const Action &Source, const Tool &Creator, : Command(Source, Creator, ResponseSupport, Executable, Arguments, Inputs, Outputs, PrependArg) { InProcess = true; + SupportsDisableFree = true; } void CC1Command::Print(raw_ostream &OS, const char *Terminator, bool Quote, diff --git a/clang/test/Driver/cc1-spawnprocess.c b/clang/test/Driver/cc1-spawnprocess.c index a6ff7d148604e..deb774e3a0cfa 100644 --- a/clang/test/Driver/cc1-spawnprocess.c +++ b/clang/test/Driver/cc1-spawnprocess.c @@ -1,3 +1,5 @@ +// UNSUPPORTED: llvm-driver + // If a toolchain uses an external assembler, the test would fail because using // an external assember would increase job counts. Most toolchains in tree // use integrated assembler, but we still support external assembler. diff --git a/clang/test/Driver/clang-translation.c b/clang/test/Driver/clang-translation.c index 5ec052a7aaa11..53ea9d98330dd 100644 --- a/clang/test/Driver/clang-translation.c +++ b/clang/test/Driver/clang-translation.c @@ -2,7 +2,6 @@ // I386: "-triple" "i386-unknown-unknown" // I386: "-Os" // I386: "-S" -// I386: "-disable-free" // I386: "-mrelocation-model" "static" // I386: "-mframe-pointer=all" // I386: "-funwind-tables=2" @@ -11,7 +10,6 @@ // I386: clang-translation // RUN: %clang -target i386-unknown-unknown -### -S %s -o %t.s -Xclang -no-disable-free 2>&1 | FileCheck -check-prefix=FREE %s -// FREE: "-disable-free" // FREE: "-no-disable-free" // RUN: %clang -target i386-unknown-unknown -### -S %s -fasynchronous-unwind-tables -fno-unwind-tables 2>&1 | FileCheck --check-prefix=UNWIND-TABLES %s --implicit-check-not=warning: diff --git a/clang/test/Driver/in-process-link.c b/clang/test/Driver/in-process-link.c new file mode 100644 index 0000000000000..1b3bacf66532c --- /dev/null +++ b/clang/test/Driver/in-process-link.c @@ -0,0 +1,48 @@ +// REQUIRES: llvm-driver, lld, aarch64-registered-target +// REQUIRES: webassembly-registered-target + +// Clang and LLD are registered in one ToolSession. Verify that frontend and +// linker jobs can execute in-process for both WebAssembly and native targets. +// RUN: split-file %s %t +// RUN: %clang --target=wasm32-unknown-unknown -nostdlib -fuse-ld=lld \ +// RUN: -Wl,--no-entry -### %t/add.c %t/sub.c 2>&1 \ +// RUN: | FileCheck %s --check-prefix=PRINT +// PRINT-COUNT-3: (in-process) +// PRINT: {{.*}}wasm-ld + +// The mechanism is target-independent: a registered ELF linker is also +// dispatched in-process. +// RUN: %clang --target=aarch64-unknown-linux-gnu -nostdlib -fuse-ld=lld \ +// RUN: -Wl,-e,add -### %t/add.c 2>&1 \ +// RUN: | FileCheck %s --check-prefix=ELF-PRINT +// ELF-PRINT-COUNT-2: (in-process) +// ELF-PRINT: {{.*}}ld.lld + +// An unrelated external linker with a registered name remains out-of-process. +// RUN: %if !system-windows %{ mkdir -p %t.external && touch %t.external/ld && \ +// RUN: chmod +x %t.external/ld %} +// RUN: %if !system-windows %{ %clang --target=aarch64-unknown-linux-gnu \ +// RUN: -nostdlib -B%t.external -### %t/add.c 2>&1 \ +// RUN: | FileCheck %s --check-prefix=EXTERNAL %} +// EXTERNAL: (in-process) +// EXTERNAL-NOT: (in-process) +// EXTERNAL: {{.*}}external{{[/\\]}}ld + +// RUN: env LLD_IN_TEST=2 %clang --target=aarch64-unknown-linux-gnu \ +// RUN: -nostdlib -fuse-ld=lld -Wl,-e,add %t/add.c -o %t.elf +// RUN: llvm-readobj --file-headers %t.elf \ +// RUN: | FileCheck %s --check-prefix=ELF +// ELF: Format: elf64-littleaarch64 + +// RUN: env LLD_IN_TEST=2 %clang --target=wasm32-unknown-unknown \ +// RUN: -nostdlib -fuse-ld=lld \ +// RUN: -Wl,--no-entry -Wl,--export=add -Wl,--export=sub \ +// RUN: %t/add.c %t/sub.c -o %t.wasm +// RUN: llvm-readobj --file-headers %t.wasm | FileCheck %s --check-prefix=WASM +// WASM: Format: WASM + +//--- add.c +int add(int lhs, int rhs) { return lhs + rhs; } + +//--- sub.c +int sub(int lhs, int rhs) { return lhs - rhs; } diff --git a/clang/test/Driver/in-process-multiple-cc1.c b/clang/test/Driver/in-process-multiple-cc1.c new file mode 100644 index 0000000000000..286405c96b8ce --- /dev/null +++ b/clang/test/Driver/in-process-multiple-cc1.c @@ -0,0 +1,36 @@ +// REQUIRES: llvm-driver, webassembly-registered-target + +// A Clang invocation owned by ToolSession can execute multiple cc1 jobs +// in-process. Each job must free its CompilerInstance before returning. +// RUN: split-file %s %t +// RUN: cd %t && %clang --target=wasm32-unknown-unknown -c -### \ +// RUN: first.c second.c 2>&1 \ +// RUN: | FileCheck %s --check-prefix=COMMANDS \ +// RUN: --implicit-check-not='"-disable-free"' +// COMMANDS-COUNT-2: (in-process) + +// The same cleanup rule applies to a single cc1 job because the session stays +// alive after the top-level Clang invocation returns. +// RUN: cd %t && %clang --target=wasm32-unknown-unknown -c -### first.c 2>&1 \ +// RUN: | FileCheck %s --check-prefix=SINGLE \ +// RUN: --implicit-check-not='"-disable-free"' +// SINGLE: (in-process) + +// An explicit request for a separate cc1 process remains authoritative. +// RUN: cd %t && %clang --target=wasm32-unknown-unknown \ +// RUN: -fno-integrated-cc1 -c -### first.c second.c 2>&1 \ +// RUN: | FileCheck %s --check-prefix=SPAWN \ +// RUN: --implicit-check-not='(in-process)' +// SPAWN-COUNT-2: "-disable-free" + +// Exercise the jobs and verify that both objects were emitted successfully. +// RUN: cd %t && %clang --target=wasm32-unknown-unknown -c first.c second.c +// RUN: llvm-readobj --file-headers %t/first.o %t/second.o \ +// RUN: | FileCheck %s --check-prefix=OBJECTS +// OBJECTS-COUNT-2: Format: WASM + +//--- first.c +int first(void) { return 1; } + +//--- second.c +int second(void) { return 2; } diff --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp index d4d913a8977a4..633f2b1bde0bd 100644 --- a/clang/tools/driver/driver.cpp +++ b/clang/tools/driver/driver.cpp @@ -16,6 +16,7 @@ #include "clang/Basic/HeaderInclude.h" #include "clang/Basic/Stack.h" #include "clang/Config/config.h" +#include "clang/Driver/Action.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/ToolChain.h" @@ -63,6 +64,14 @@ using namespace clang; using namespace clang::driver; using namespace llvm::opt; +static bool isSessionOwnedTool(const llvm::ToolContext &Context, + llvm::StringRef Executable) { + // A bare name means that Clang did not find an external executable. A path + // is session-owned only when it names the session's multicall binary. + return llvm::sys::path::parent_path(Executable).empty() || + llvm::sys::fs::equivalent(Executable, Context.Path); +} + std::string GetExecutablePath(const char *Argv0, bool CanonicalPrefixes) { if (!CanonicalPrefixes) { SmallString<128> ExecutablePath(Argv0); @@ -387,6 +396,32 @@ int clang_main(int Argc, char **Argv, const llvm::ToolContext &ToolContext) { std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(Args)); + // A long-lived host cannot rely on process exit to reclaim memory after a + // cc1 invocation. Run each cc1 job through Clang's existing callback and + // ensure that it destroys its CompilerInstance before returning. + if (ToolContext.hasSession() && !UseNewCC1Process && + !TheDriver.CCPrintProcessStats) { + for (Command &Job : C->getJobs()) { + if (!Job.SupportsDisableFree) + continue; + Job.enableFree(); + Job.InProcess = true; + } + } + + // A host session may provide the linker as a callable tool. Other external + // commands retain the subprocess path. + if (ToolContext.hasSession()) { + for (Command &Job : C->getJobs()) { + if (!isa<LinkJobAction>(Job.getSource())) + continue; + if (!isSessionOwnedTool(ToolContext, Job.getExecutable())) + continue; + if (ToolContext.getCallableTool(Job.getExecutable())) + Job.setInProcessToolContext(ToolContext); + } + } + Driver::ReproLevel ReproLevel = Driver::ReproLevel::OnCrash; if (Arg *A = C->getArgs().getLastArg(options::OPT_gen_reproducer_eq)) { auto Level = diff --git a/lld/tools/lld/lld.cpp b/lld/tools/lld/lld.cpp index d6800fa1eea4b..711fab35d646f 100644 --- a/lld/tools/lld/lld.cpp +++ b/lld/tools/lld/lld.cpp @@ -72,7 +72,7 @@ LLD_HAS_DRIVER(mingw) LLD_HAS_DRIVER(macho) LLD_HAS_DRIVER(wasm) -int lld_main(int argc, char **argv, const llvm::ToolContext &) { +int lld_main(int argc, char **argv, const llvm::ToolContext &ToolContext) { sys::Process::UseANSIEscapeCodes(true); if (::getenv("FORCE_LLD_DIAGNOSTICS_CRASH")) { @@ -83,9 +83,10 @@ int lld_main(int argc, char **argv, const llvm::ToolContext &) { ArrayRef<const char *> args(argv, argv + argc); - // Not running in lit tests, just take the shortest codepath with global - // exception handling and no memory cleanup on exit. - if (!inTestVerbosity()) { + // A standalone invocation can take the shortest path and let process exit + // reclaim its resources. A session-owned invocation must clean up before + // returning to its host. + if (!ToolContext.hasSession() && !inTestVerbosity()) { int r = lld::unsafeLldMain(args, llvm::outs(), llvm::errs(), LLD_ALL_DRIVERS, /*exitEarly=*/true); @@ -94,8 +95,11 @@ int lld_main(int argc, char **argv, const llvm::ToolContext &) { std::optional<int> mainRet; CrashRecoveryContext::Enable(); + unsigned Iterations = inTestVerbosity(); + if (!Iterations) + Iterations = 1; - for (unsigned i = inTestVerbosity(); i > 0; --i) { + for (unsigned i = Iterations; i > 0; --i) { // Disable stdout/stderr for all iterations but the last one. inTestOutputDisabled = (i != 1); diff --git a/llvm/include/llvm/Support/LLVMDriver.h b/llvm/include/llvm/Support/LLVMDriver.h index 0b2e265d50b42..3660f03b3977b 100644 --- a/llvm/include/llvm/Support/LLVMDriver.h +++ b/llvm/include/llvm/Support/LLVMDriver.h @@ -9,9 +9,36 @@ #ifndef LLVM_SUPPORT_LLVMDRIVER_H #define LLVM_SUPPORT_LLVMDRIVER_H +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/ErrorOr.h" + +#include <functional> +#include <memory> + namespace llvm { -struct ToolContext { +class ToolSession; +class ToolContext; + +using ToolMainFn = std::function<int(int, char **, const ToolContext &)>; + +/// An LLVM command-line tool that can be invoked without creating a process. +struct CallableTool { + StringRef Name; + ToolMainFn Main; + + explicit operator bool() const { return static_cast<bool>(Main); } +}; + +/// Describes how a tool was invoked and provides access to its host session. +class ToolContext { + ToolSession *Session = nullptr; + + friend class ToolSession; + +public: const char *Path; const char *PrependArg; // PrependArg will be added unconditionally by the llvm-driver, but @@ -20,6 +47,51 @@ struct ToolContext { // point to the llvm-driver executable, where PrependArg will be needed to // invoke the correct tool. bool NeedsPrependArg; + + ToolContext(const char *Path, const char *PrependArg, bool NeedsPrependArg) + : Path(Path), PrependArg(PrependArg), NeedsPrependArg(NeedsPrependArg) {} + + /// Finds a tool registered with the session that owns this context. + LLVM_ABI ErrorOr<CallableTool> getCallableTool(StringRef Name) const; + + /// Invokes another tool registered with the same host session. + LLVM_ABI ErrorOr<int> callTool(ArrayRef<const char *> Args) const; + + /// Returns true when this invocation is owned by a tool session. + bool hasSession() const { return Session != nullptr; } +}; + +/// Owns LLVM process initialization and an in-process tool registry. +/// +/// A long-lived host constructs one session and uses it for every embedded +/// tool invocation. The individual tools borrow a ToolContext and therefore do +/// not initialize or shut down LLVM themselves. +/// +/// LLVM tools may use process-global state. Tool invocations must be externally +/// serialized; concurrent calls are not supported. +class LLVM_ABI ToolSession { +public: + ToolSession(int &Argc, char **&Argv, ArrayRef<CallableTool> Tools, + bool InstallPipeSignalExitHandler = true, + bool NeedsPOSIXUtilitySignalHandling = false); + ~ToolSession(); + + ToolSession(const ToolSession &) = delete; + ToolSession &operator=(const ToolSession &) = delete; + + /// Invokes the tool named by Args[0]. Args may instead contain a + /// process-style argv beginning with the session executable or an LLVM + /// multicall name. + ErrorOr<int> callTool(ArrayRef<const char *> Args); + +private: + struct Impl; + std::unique_ptr<Impl> PImpl; + + ErrorOr<CallableTool> findTool(StringRef Name) const; + ToolContext makeContext(StringRef InvokedName, const char *PrependArg); + + friend class ToolContext; }; } // namespace llvm diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt index e7dfcb0dcd891..2b2e6516e54f2 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -227,6 +227,7 @@ add_llvm_component_library(LLVMSupport KnownFPClass.cpp LEB128.cpp LineIterator.cpp + LLVMToolSession.cpp Locale.cpp LockFileManager.cpp ManagedStatic.cpp diff --git a/llvm/lib/Support/LLVMToolSession.cpp b/llvm/lib/Support/LLVMToolSession.cpp new file mode 100644 index 0000000000000..93704610af38c --- /dev/null +++ b/llvm/lib/Support/LLVMToolSession.cpp @@ -0,0 +1,131 @@ +//===-- LLVMToolSession.cpp ----------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/LLVMDriver.h" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/Path.h" + +#include <cassert> +#include <string> +#include <system_error> +#include <utility> +#include <vector> + +using namespace llvm; + +namespace { + +bool matchesToolName(StringRef RegisteredName, StringRef InvokedName) { + StringRef Stem = sys::path::stem(InvokedName); + StringRef Filename = sys::path::filename(InvokedName); + auto Matches = [RegisteredName](StringRef Candidate) { + size_t Position = Candidate.rfind_insensitive(RegisteredName); + return Position != StringRef::npos && + (Position + RegisteredName.size() == Candidate.size() || + !llvm::isAlnum(Candidate[Position + RegisteredName.size()])); + }; + return Matches(Stem) || Matches(Filename); +} + +bool isMulticallName(StringRef Name) { return matchesToolName("llvm", Name); } + +} // namespace + +struct ToolSession::Impl { + InitLLVM Initialization; + std::string ExecutablePath; + std::vector<std::pair<std::string, ToolMainFn>> Tools; + + Impl(int &Argc, char **&Argv, ArrayRef<CallableTool> RegisteredTools, + bool InstallPipeSignalExitHandler, bool NeedsPOSIXUtilitySignalHandling) + : Initialization(Argc, Argv, InstallPipeSignalExitHandler, + NeedsPOSIXUtilitySignalHandling), + ExecutablePath(Argv[0]) { + Tools.reserve(RegisteredTools.size()); + for (const CallableTool &Tool : RegisteredTools) + Tools.emplace_back(Tool.Name.str(), Tool.Main); + } +}; + +ToolSession::ToolSession(int &Argc, char **&Argv, ArrayRef<CallableTool> Tools, + bool InstallPipeSignalExitHandler, + bool NeedsPOSIXUtilitySignalHandling) { + assert(Argc > 0 && Argv && Argv[0] && "ToolSession requires a valid argv[0]"); + PImpl = + std::make_unique<Impl>(Argc, Argv, Tools, InstallPipeSignalExitHandler, + NeedsPOSIXUtilitySignalHandling); +} + +ToolSession::~ToolSession() = default; + +ErrorOr<CallableTool> ToolSession::findTool(StringRef Name) const { + StringRef Stem = sys::path::stem(Name); + StringRef Filename = sys::path::filename(Name); + for (const auto &[RegisteredName, Main] : PImpl->Tools) + if (Stem.equals_insensitive(RegisteredName) || + Filename.equals_insensitive(RegisteredName)) + return CallableTool{RegisteredName, Main}; + + for (const a... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/224023 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
