From b3c0055c172be9ed4115cf894306a2016c8857bd Mon Sep 17 00:00:00 2001 From: Lang Hames Date: Thu, 27 Jul 2023 14:58:32 -0700 Subject: [PATCH] [JITLink] Don't try to abandon non-existent allocations. If JITLinkGeneric::linkPhase2 receives an Error rather than an InFlightAlloc then we need to call JITLinkContext::notifyFailed, rather than calling abandonAllocAndBailOut -- the latter asserts that there is an allocation to abandon, and this was turning allocation errors into assertion failures in debug mode. --- .../JITLink/JITLinkGeneric.cpp | 2 +- .../ExecutionEngine/JITLink/CMakeLists.txt | 2 + .../ExecutionEngine/JITLink/JITLinkMocks.cpp | 72 ++++++ .../ExecutionEngine/JITLink/JITLinkMocks.h | 228 ++++++++++++++++++ .../JITLink/MemoryManagerErrorTests.cpp | 46 ++++ 5 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 llvm/unittests/ExecutionEngine/JITLink/JITLinkMocks.cpp create mode 100644 llvm/unittests/ExecutionEngine/JITLink/JITLinkMocks.h create mode 100644 llvm/unittests/ExecutionEngine/JITLink/MemoryManagerErrorTests.cpp diff --git a/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.cpp b/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.cpp index feaa0fb6a58c3..5361272ae79ef 100644 --- a/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/JITLinkGeneric.cpp @@ -65,7 +65,7 @@ void JITLinkerBase::linkPhase2(std::unique_ptr Self, if (AR) Alloc = std::move(*AR); else - return abandonAllocAndBailOut(std::move(Self), AR.takeError()); + return Ctx->notifyFailed(AR.takeError()); LLVM_DEBUG({ dbgs() << "Link graph \"" << G->getName() diff --git a/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt b/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt index 978914c748c63..5f71cd03b66f9 100644 --- a/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt +++ b/llvm/unittests/ExecutionEngine/JITLink/CMakeLists.txt @@ -10,7 +10,9 @@ set(LLVM_LINK_COMPONENTS add_llvm_unittest(JITLinkTests AArch32Tests.cpp EHFrameSupportTests.cpp + JITLinkMocks.cpp LinkGraphTests.cpp + MemoryManagerErrorTests.cpp ) target_link_libraries(JITLinkTests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/ExecutionEngine/JITLink/JITLinkMocks.cpp b/llvm/unittests/ExecutionEngine/JITLink/JITLinkMocks.cpp new file mode 100644 index 0000000000000..158333b106222 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/JITLink/JITLinkMocks.cpp @@ -0,0 +1,72 @@ +//===--------- JITLinkMocks.cpp - Mock APIs for JITLink unit tests --------===// +// +// 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 "JITLinkMocks.h" +#include "llvm/ExecutionEngine/JITLink/MachO_x86_64.h" + +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::orc; +using namespace llvm::jitlink; + +void lookupResolveEverythingToNull( + const llvm::jitlink::JITLinkContext::LookupMap &Symbols, + std::unique_ptr LC) { + llvm::orc::ExecutorAddr Null; + llvm::jitlink::AsyncLookupResult Result; + for (auto &KV : Symbols) + Result[KV.first] = {Null, llvm::JITSymbolFlags::Exported}; + LC->run(std::move(Result)); +} + +void lookupErrorOut( + const llvm::jitlink::JITLinkContext::LookupMap &Symbols, + std::unique_ptr LC) { + LC->run(llvm::make_error("Lookup failed", + llvm::inconvertibleErrorCode())); +} + +std::unique_ptr makeMockContext( + llvm::unique_function HandleFailed, + llvm::unique_function SetupMemMgr, + llvm::unique_function SetupContext) { + auto MemMgr = std::make_unique(); + SetupMemMgr(*MemMgr); + auto Ctx = std::make_unique(std::move(MemMgr), + std::move(HandleFailed)); + SetupContext(*Ctx); + return Ctx; +} + +void defaultMemMgrSetup(MockJITLinkMemoryManager &) {} +void defaultCtxSetup(MockJITLinkContext &) {} + +TEST(JITLinkMocks, SmokeTest) { + // Check that the testing infrastructure defaults can "link" a graph + // successfully. + auto G = std::make_unique("foo", Triple("x86_64-apple-darwin"), 8, + support::little, getGenericEdgeKindName); + + ArrayRef Content = "hello, world!"; + auto &Sec = + G->createSection("__data", orc::MemProt::Read | orc::MemProt::Write); + orc::ExecutorAddr B1Addr(0x1000); + auto &B = G->createContentBlock(Sec, Content, B1Addr, 8, 0); + G->addDefinedSymbol(B, 4, "S", 4, Linkage::Strong, Scope::Default, false, + false); + + Error Err = Error::success(); + auto Ctx = + makeMockContext(JoinErrorsInto(Err), defaultMemMgrSetup, defaultCtxSetup); + + link_MachO_x86_64(std::move(G), std::move(Ctx)); + + EXPECT_THAT_ERROR(std::move(Err), Succeeded()); +} diff --git a/llvm/unittests/ExecutionEngine/JITLink/JITLinkMocks.h b/llvm/unittests/ExecutionEngine/JITLink/JITLinkMocks.h new file mode 100644 index 0000000000000..8c1e3ff2c77db --- /dev/null +++ b/llvm/unittests/ExecutionEngine/JITLink/JITLinkMocks.h @@ -0,0 +1,228 @@ +//===----- JITLinkMocks.h - Mock APIs for JITLink unit tests ----*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Mock APIs for JITLink unit tests. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_UNITTESTS_EXECUTIONENGINE_JITLINK_JITLINKMOCKS_H +#define LLVM_UNITTESTS_EXECUTIONENGINE_JITLINK_JITLINKMOCKS_H + +#include "llvm/ExecutionEngine/JITLink/JITLink.h" + +class MockJITLinkMemoryManager : public llvm::jitlink::JITLinkMemoryManager { +public: + class Alloc { + public: + virtual ~Alloc() {} + }; + + class SimpleAlloc : public Alloc { + public: + SimpleAlloc(const llvm::jitlink::JITLinkDylib *JD, + llvm::jitlink::LinkGraph &G) { + for (auto *B : G.blocks()) + (void)B->getMutableContent(G); + } + }; + + class MockInFlightAlloc : public InFlightAlloc { + public: + MockInFlightAlloc(MockJITLinkMemoryManager &MJMM, std::unique_ptr A) + : MJMM(MJMM), A(std::move(A)) {} + + void abandon(OnAbandonedFunction OnAbandoned) override { + OnAbandoned(MJMM.Abandon(std::move(A))); + } + + void finalize(OnFinalizedFunction OnFinalized) override { + OnFinalized(MJMM.Finalize(std::move(A))); + } + + private: + MockJITLinkMemoryManager &MJMM; + std::unique_ptr A; + }; + + MockJITLinkMemoryManager() { + Allocate = [this](const llvm::jitlink::JITLinkDylib *JD, + llvm::jitlink::LinkGraph &G) { + return defaultAllocate(JD, G); + }; + + Deallocate = [this](std::vector Allocs) { + return defaultDeallocate(std::move(Allocs)); + }; + + Abandon = [this](std::unique_ptr A) { + return defaultAbandon(std::move(A)); + }; + + Finalize = [this](std::unique_ptr A) { + return defaultFinalize(std::move(A)); + }; + } + + void allocate(const llvm::jitlink::JITLinkDylib *JD, + llvm::jitlink::LinkGraph &G, + OnAllocatedFunction OnAllocated) override { + auto A = Allocate(JD, G); + if (!A) + OnAllocated(A.takeError()); + else + OnAllocated(std::make_unique(*this, std::move(*A))); + } + + void deallocate(std::vector Allocs, + OnDeallocatedFunction OnDeallocated) override { + OnDeallocated(Deallocate(std::move(Allocs))); + } + + using JITLinkMemoryManager::allocate; + using JITLinkMemoryManager::deallocate; + + llvm::Expected> + defaultAllocate(const llvm::jitlink::JITLinkDylib *JD, + llvm::jitlink::LinkGraph &G) { + return std::make_unique(JD, G); + } + + llvm::Error defaultDeallocate(std::vector Allocs) { + for (auto &A : Allocs) + delete A.release().toPtr(); + return llvm::Error::success(); + } + + llvm::Error defaultAbandon(std::unique_ptr A) { + return llvm::Error::success(); + } + + llvm::Expected defaultFinalize(std::unique_ptr A) { + return FinalizedAlloc(llvm::orc::ExecutorAddr::fromPtr(A.release())); + } + + llvm::unique_function>( + const llvm::jitlink::JITLinkDylib *, llvm::jitlink::LinkGraph &)> + Allocate; + llvm::unique_function)> Deallocate; + llvm::unique_function)> Abandon; + llvm::unique_function(std::unique_ptr)> + Finalize; +}; + +void lookupResolveEverythingToNull( + const llvm::jitlink::JITLinkContext::LookupMap &Symbols, + std::unique_ptr LC); + +void lookupErrorOut( + const llvm::jitlink::JITLinkContext::LookupMap &Symbols, + std::unique_ptr LC); + +class MockJITLinkContext : public llvm::jitlink::JITLinkContext { +public: + using HandleFailedFn = llvm::unique_function; + + MockJITLinkContext(std::unique_ptr MJMM, + HandleFailedFn HandleFailed) + : JITLinkContext(&JD), MJMM(std::move(MJMM)), + HandleFailed(std::move(HandleFailed)) {} + + ~MockJITLinkContext() { + if (auto Err = MJMM->deallocate(std::move(FinalizedAllocs))) + notifyFailed(std::move(Err)); + } + + llvm::jitlink::JITLinkMemoryManager &getMemoryManager() override { + return *MJMM; + } + + void notifyFailed(llvm::Error Err) override { HandleFailed(std::move(Err)); } + + void lookup(const LookupMap &Symbols, + std::unique_ptr LC) + override { + Lookup(Symbols, std::move(LC)); + } + + llvm::Error notifyResolved(llvm::jitlink::LinkGraph &G) override { + return NotifyResolved ? NotifyResolved(G) : llvm::Error::success(); + } + + void notifyFinalized( + llvm::jitlink::JITLinkMemoryManager::FinalizedAlloc Alloc) override { + if (NotifyFinalized) + NotifyFinalized(std::move(Alloc)); + else + FinalizedAllocs.push_back(std::move(Alloc)); + } + + bool shouldAddDefaultTargetPasses(const llvm::Triple &TT) const override { + return true; + } + + llvm::jitlink::LinkGraphPassFunction + getMarkLivePass(const llvm::Triple &TT) const override { + return MarkLivePass ? llvm::jitlink::LinkGraphPassFunction( + [this](llvm::jitlink::LinkGraph &G) { + return MarkLivePass(G); + }) + : llvm::jitlink::LinkGraphPassFunction( + [](llvm::jitlink::LinkGraph &G) { + return markAllSymbolsLive(G); + }); + } + + llvm::Error + modifyPassConfig(llvm::jitlink::LinkGraph &G, + llvm::jitlink::PassConfiguration &Config) override { + if (ModifyPassConfig) + return ModifyPassConfig(G, Config); + return llvm::Error::success(); + } + + llvm::jitlink::JITLinkDylib JD{"JD"}; + std::unique_ptr MJMM; + HandleFailedFn HandleFailed; + llvm::unique_function)> + Lookup; + llvm::unique_function NotifyResolved; + llvm::unique_function + NotifyFinalized; + mutable llvm::unique_function + MarkLivePass; + llvm::unique_function + ModifyPassConfig; + + std::vector + FinalizedAllocs; +}; + +std::unique_ptr makeMockContext( + llvm::unique_function HandleFailed, + llvm::unique_function SetupMemMgr, + llvm::unique_function SetupContext); + +void defaultMemMgrSetup(MockJITLinkMemoryManager &); +void defaultCtxSetup(MockJITLinkContext &); + +class JoinErrorsInto { +public: + JoinErrorsInto(llvm::Error &Err) : Err(Err) {} + void operator()(llvm::Error E2) { + Err = llvm::joinErrors(std::move(Err), std::move(E2)); + } + +private: + llvm::Error &Err; +}; + +#endif // LLVM_UNITTESTS_EXECUTIONENGINE_JITLINK_JITLINKMOCKS_H diff --git a/llvm/unittests/ExecutionEngine/JITLink/MemoryManagerErrorTests.cpp b/llvm/unittests/ExecutionEngine/JITLink/MemoryManagerErrorTests.cpp new file mode 100644 index 0000000000000..c6f4b962a002a --- /dev/null +++ b/llvm/unittests/ExecutionEngine/JITLink/MemoryManagerErrorTests.cpp @@ -0,0 +1,46 @@ +//===---- MemoryManagerErrorTests.cpp - Test memory manager error paths ---===// +// +// 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 "JITLinkMocks.h" +#include "llvm/ExecutionEngine/JITLink/MachO_x86_64.h" + +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::orc; +using namespace llvm::jitlink; + +TEST(MemoryManagerErrorTest, ErrorOnFirstAllocate) { + // Check that we can get addresses for blocks, symbols, and edges. + auto G = std::make_unique("foo", Triple("x86_64-apple-darwin"), 8, + support::little, getGenericEdgeKindName); + + ArrayRef Content = "hello, world!"; + auto &Sec = + G->createSection("__data", orc::MemProt::Read | orc::MemProt::Write); + orc::ExecutorAddr B1Addr(0x1000); + auto &B = G->createContentBlock(Sec, Content, B1Addr, 8, 0); + G->addDefinedSymbol(B, 4, "S", 4, Linkage::Strong, Scope::Default, false, + false); + + Error Err = Error::success(); + auto Ctx = makeMockContext( + JoinErrorsInto(Err), + [](MockJITLinkMemoryManager &MemMgr) { + MemMgr.Allocate = [](const JITLinkDylib *JD, LinkGraph &G) { + return make_error("Failed to allocate", + inconvertibleErrorCode()); + }; + }, + defaultCtxSetup); + + link_MachO_x86_64(std::move(G), std::move(Ctx)); + + EXPECT_THAT_ERROR(std::move(Err), Failed()); +}