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