| //===- MCJITObjectCacheTest.cpp - Unit tests for MCJIT object caching -----===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "MCJITTestBase.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringSet.h" |
| #include "llvm/ExecutionEngine/MCJIT.h" |
| #include "llvm/ExecutionEngine/ObjectCache.h" |
| #include "llvm/ExecutionEngine/SectionMemoryManager.h" |
| #include "gtest/gtest.h" |
| |
| using namespace llvm; |
| |
| namespace { |
| |
| class TestObjectCache : public ObjectCache { |
| public: |
| TestObjectCache() : DuplicateInserted(false) { } |
| |
| void notifyObjectCompiled(const Module *M, MemoryBufferRef Obj) override { |
| // If we've seen this module before, note that. |
| const std::string ModuleID = M->getModuleIdentifier(); |
| if (ObjMap.find(ModuleID) != ObjMap.end()) |
| DuplicateInserted = true; |
| // Store a copy of the buffer in our map. |
| ObjMap[ModuleID] = copyBuffer(Obj); |
| } |
| |
| std::unique_ptr<MemoryBuffer> getObject(const Module *M) override { |
| const MemoryBuffer* BufferFound = getObjectInternal(M); |
| ModulesLookedUp.insert(M->getModuleIdentifier()); |
| if (!BufferFound) |
| return nullptr; |
| // Our test cache wants to maintain ownership of its object buffers |
| // so we make a copy here for the execution engine. |
| return MemoryBuffer::getMemBufferCopy(BufferFound->getBuffer()); |
| } |
| |
| // Test-harness-specific functions |
| bool wereDuplicatesInserted() { return DuplicateInserted; } |
| |
| bool wasModuleLookedUp(const Module *M) { |
| return ModulesLookedUp.find(M->getModuleIdentifier()) |
| != ModulesLookedUp.end(); |
| } |
| |
| const MemoryBuffer* getObjectInternal(const Module* M) { |
| // Look for the module in our map. |
| const std::string ModuleID = M->getModuleIdentifier(); |
| StringMap<const MemoryBuffer *>::iterator it = ObjMap.find(ModuleID); |
| if (it == ObjMap.end()) |
| return nullptr; |
| return it->second; |
| } |
| |
| private: |
| MemoryBuffer *copyBuffer(MemoryBufferRef Buf) { |
| // Create a local copy of the buffer. |
| std::unique_ptr<MemoryBuffer> NewBuffer = |
| MemoryBuffer::getMemBufferCopy(Buf.getBuffer()); |
| MemoryBuffer *Ret = NewBuffer.get(); |
| AllocatedBuffers.push_back(std::move(NewBuffer)); |
| return Ret; |
| } |
| |
| StringMap<const MemoryBuffer *> ObjMap; |
| StringSet<> ModulesLookedUp; |
| SmallVector<std::unique_ptr<MemoryBuffer>, 2> AllocatedBuffers; |
| bool DuplicateInserted; |
| }; |
| |
| class MCJITObjectCacheTest : public testing::Test, public MCJITTestBase { |
| protected: |
| enum { |
| OriginalRC = 6, |
| ReplacementRC = 7 |
| }; |
| |
| void SetUp() override { |
| M.reset(createEmptyModule("<main>")); |
| Main = insertMainFunction(M.get(), OriginalRC); |
| } |
| |
| void compileAndRun(int ExpectedRC = OriginalRC) { |
| // This function shouldn't be called until after SetUp. |
| ASSERT_TRUE(bool(TheJIT)); |
| ASSERT_TRUE(nullptr != Main); |
| |
| // We may be using a null cache, so ensure compilation is valid. |
| TheJIT->finalizeObject(); |
| void *vPtr = TheJIT->getPointerToFunction(Main); |
| |
| EXPECT_TRUE(nullptr != vPtr) |
| << "Unable to get pointer to main() from JIT"; |
| |
| int (*FuncPtr)() = (int(*)())(intptr_t)vPtr; |
| int returnCode = FuncPtr(); |
| EXPECT_EQ(returnCode, ExpectedRC); |
| } |
| |
| Function *Main; |
| }; |
| |
| TEST_F(MCJITObjectCacheTest, SetNullObjectCache) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| createJIT(std::move(M)); |
| |
| TheJIT->setObjectCache(nullptr); |
| |
| compileAndRun(); |
| } |
| |
| TEST_F(MCJITObjectCacheTest, VerifyBasicObjectCaching) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| std::unique_ptr<TestObjectCache> Cache(new TestObjectCache); |
| |
| // Save a copy of the module pointer before handing it off to MCJIT. |
| const Module * SavedModulePointer = M.get(); |
| |
| createJIT(std::move(M)); |
| |
| TheJIT->setObjectCache(Cache.get()); |
| |
| // Verify that our object cache does not contain the module yet. |
| const MemoryBuffer *ObjBuffer = Cache->getObjectInternal(SavedModulePointer); |
| EXPECT_EQ(nullptr, ObjBuffer); |
| |
| compileAndRun(); |
| |
| // Verify that MCJIT tried to look-up this module in the cache. |
| EXPECT_TRUE(Cache->wasModuleLookedUp(SavedModulePointer)); |
| |
| // Verify that our object cache now contains the module. |
| ObjBuffer = Cache->getObjectInternal(SavedModulePointer); |
| EXPECT_TRUE(nullptr != ObjBuffer); |
| |
| // Verify that the cache was only notified once. |
| EXPECT_FALSE(Cache->wereDuplicatesInserted()); |
| } |
| |
| TEST_F(MCJITObjectCacheTest, VerifyLoadFromCache) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| std::unique_ptr<TestObjectCache> Cache(new TestObjectCache); |
| |
| // Compile this module with an MCJIT engine |
| createJIT(std::move(M)); |
| TheJIT->setObjectCache(Cache.get()); |
| TheJIT->finalizeObject(); |
| |
| // Destroy the MCJIT engine we just used |
| TheJIT.reset(); |
| |
| // Create a new memory manager. |
| MM.reset(new SectionMemoryManager()); |
| |
| // Create a new module and save it. Use a different return code so we can |
| // tell if MCJIT compiled this module or used the cache. |
| M.reset(createEmptyModule("<main>")); |
| Main = insertMainFunction(M.get(), ReplacementRC); |
| const Module * SecondModulePointer = M.get(); |
| |
| // Create a new MCJIT instance to load this module then execute it. |
| createJIT(std::move(M)); |
| TheJIT->setObjectCache(Cache.get()); |
| compileAndRun(); |
| |
| // Verify that MCJIT tried to look-up this module in the cache. |
| EXPECT_TRUE(Cache->wasModuleLookedUp(SecondModulePointer)); |
| |
| // Verify that MCJIT didn't try to cache this again. |
| EXPECT_FALSE(Cache->wereDuplicatesInserted()); |
| } |
| |
| TEST_F(MCJITObjectCacheTest, VerifyNonLoadFromCache) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| std::unique_ptr<TestObjectCache> Cache(new TestObjectCache); |
| |
| // Compile this module with an MCJIT engine |
| createJIT(std::move(M)); |
| TheJIT->setObjectCache(Cache.get()); |
| TheJIT->finalizeObject(); |
| |
| // Destroy the MCJIT engine we just used |
| TheJIT.reset(); |
| |
| // Create a new memory manager. |
| MM.reset(new SectionMemoryManager()); |
| |
| // Create a new module and save it. Use a different return code so we can |
| // tell if MCJIT compiled this module or used the cache. Note that we use |
| // a new module name here so the module shouldn't be found in the cache. |
| M.reset(createEmptyModule("<not-main>")); |
| Main = insertMainFunction(M.get(), ReplacementRC); |
| const Module * SecondModulePointer = M.get(); |
| |
| // Create a new MCJIT instance to load this module then execute it. |
| createJIT(std::move(M)); |
| TheJIT->setObjectCache(Cache.get()); |
| |
| // Verify that our object cache does not contain the module yet. |
| const MemoryBuffer *ObjBuffer = Cache->getObjectInternal(SecondModulePointer); |
| EXPECT_EQ(nullptr, ObjBuffer); |
| |
| // Run the function and look for the replacement return code. |
| compileAndRun(ReplacementRC); |
| |
| // Verify that MCJIT tried to look-up this module in the cache. |
| EXPECT_TRUE(Cache->wasModuleLookedUp(SecondModulePointer)); |
| |
| // Verify that our object cache now contains the module. |
| ObjBuffer = Cache->getObjectInternal(SecondModulePointer); |
| EXPECT_TRUE(nullptr != ObjBuffer); |
| |
| // Verify that MCJIT didn't try to cache this again. |
| EXPECT_FALSE(Cache->wereDuplicatesInserted()); |
| } |
| |
| } // end anonymous namespace |