From ced4e8ff6eed256e41183b0ee8698960d799f01e Mon Sep 17 00:00:00 2001 From: Andrew Kaylor Date: Thu, 25 Apr 2013 21:02:36 +0000 Subject: [PATCH] Re-enabling MCJIT object caching with memory leak fixed llvm-svn: 180575 --- .../llvm/ExecutionEngine/ExecutionEngine.h | 7 + .../llvm/ExecutionEngine/ObjectCache.h | 54 ++++ llvm/lib/ExecutionEngine/MCJIT/MCJIT.cpp | 72 ++++-- llvm/lib/ExecutionEngine/MCJIT/MCJIT.h | 14 +- .../ExecutionEngine/MCJIT/CMakeLists.txt | 1 + .../MCJIT/MCJITObjectCacheTest.cpp | 240 ++++++++++++++++++ 6 files changed, 370 insertions(+), 18 deletions(-) create mode 100644 llvm/include/llvm/ExecutionEngine/ObjectCache.h create mode 100644 llvm/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp diff --git a/llvm/include/llvm/ExecutionEngine/ExecutionEngine.h b/llvm/include/llvm/ExecutionEngine/ExecutionEngine.h index 733e64af0347..980075948404 100644 --- a/llvm/include/llvm/ExecutionEngine/ExecutionEngine.h +++ b/llvm/include/llvm/ExecutionEngine/ExecutionEngine.h @@ -44,6 +44,7 @@ class JITMemoryManager; class MachineCodeInfo; class Module; class MutexGuard; +class ObjectCache; class DataLayout; class Triple; class Type; @@ -373,6 +374,12 @@ public: virtual void RegisterJITEventListener(JITEventListener *) {} virtual void UnregisterJITEventListener(JITEventListener *) {} + /// Sets the pre-compiled object cache. The ownership of the ObjectCache is + /// not changed. Supported by MCJIT but not JIT. + virtual void setObjectCache(ObjectCache *) { + llvm_unreachable("No support for an object cache"); + } + /// DisableLazyCompilation - When lazy compilation is off (the default), the /// JIT will eagerly compile every function reachable from the argument to /// getPointerToFunction. If lazy compilation is turned on, the JIT will only diff --git a/llvm/include/llvm/ExecutionEngine/ObjectCache.h b/llvm/include/llvm/ExecutionEngine/ObjectCache.h new file mode 100644 index 000000000000..0bee86161bb7 --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/ObjectCache.h @@ -0,0 +1,54 @@ +//===-- ObjectCache.h - Class definition for the ObjectCache -----C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_EXECUTIONENGINE_OBJECTCACHE_H +#define LLVM_LIB_EXECUTIONENGINE_OBJECTCACHE_H + +#include "llvm/Support/MemoryBuffer.h" + +namespace llvm { + +class Module; + +/// This is the base ObjectCache type which can be provided to an +/// ExecutionEngine for the purpose of avoiding compilation for Modules that +/// have already been compiled and an object file is available. +class ObjectCache { +public: + ObjectCache() { } + + virtual ~ObjectCache() { } + + /// notifyObjectCompiled - Provides a pointer to compiled code for Module M. + virtual void notifyObjectCompiled(const Module *M, const MemoryBuffer *Obj) = 0; + + /// getObjectCopy - Returns a pointer to a newly allocated MemoryBuffer that + /// contains the object which corresponds with Module M, or 0 if an object is + /// not available. The caller owns the MemoryBuffer returned by this function. + MemoryBuffer* getObjectCopy(const Module* M) { + const MemoryBuffer* Obj = getObject(M); + if (Obj) + return MemoryBuffer::getMemBufferCopy(Obj->getBuffer()); + else + return 0; + } + +protected: + /// getObject - Returns a pointer to a MemoryBuffer that contains an object + /// that corresponds with Module M, or 0 if an object is not available. + /// The pointer returned by this function is not suitable for loading because + /// the memory is read-only and owned by the ObjectCache. To retrieve an + /// owning pointer to a MemoryBuffer (which is suitable for calling + /// RuntimeDyld::loadObject() with) use getObjectCopy() instead. + virtual const MemoryBuffer* getObject(const Module* M) = 0; +}; + +} + +#endif diff --git a/llvm/lib/ExecutionEngine/MCJIT/MCJIT.cpp b/llvm/lib/ExecutionEngine/MCJIT/MCJIT.cpp index fee10e194355..9e42cd0441a9 100644 --- a/llvm/lib/ExecutionEngine/MCJIT/MCJIT.cpp +++ b/llvm/lib/ExecutionEngine/MCJIT/MCJIT.cpp @@ -52,7 +52,7 @@ ExecutionEngine *MCJIT::createJIT(Module *M, MCJIT::MCJIT(Module *m, TargetMachine *tm, RTDyldMemoryManager *MM, bool AllocateGVsWithCode) : ExecutionEngine(m), TM(tm), Ctx(0), MemMgr(MM), Dyld(MM), - isCompiled(false), M(m) { + IsLoaded(false), M(m), ObjCache(0) { setDataLayout(TM->getDataLayout()); } @@ -64,7 +64,11 @@ MCJIT::~MCJIT() { delete TM; } -void MCJIT::emitObject(Module *m) { +void MCJIT::setObjectCache(ObjectCache* NewCache) { + ObjCache = NewCache; +} + +ObjectBufferStream* MCJIT::emitObject(Module *m) { /// Currently, MCJIT only supports a single module and the module passed to /// this function call is expected to be the contained module. The module /// is passed as a parameter here to prepare for multiple module support in @@ -77,30 +81,66 @@ void MCJIT::emitObject(Module *m) { // FIXME: Track compilation state on a per-module basis when multiple modules // are supported. // Re-compilation is not supported - if (isCompiled) - return; + assert(!IsLoaded); PassManager PM; PM.add(new DataLayout(*TM->getDataLayout())); // The RuntimeDyld will take ownership of this shortly - OwningPtr Buffer(new ObjectBufferStream()); + OwningPtr CompiledObject(new ObjectBufferStream()); // Turn the machine code intermediate representation into bytes in memory // that may be executed. - if (TM->addPassesToEmitMC(PM, Ctx, Buffer->getOStream(), false)) { + if (TM->addPassesToEmitMC(PM, Ctx, CompiledObject->getOStream(), false)) { report_fatal_error("Target does not support MC emission!"); } // Initialize passes. PM.run(*m); // Flush the output buffer to get the generated code into memory - Buffer->flush(); + CompiledObject->flush(); + + // If we have an object cache, tell it about the new object. + // Note that we're using the compiled image, not the loaded image (as below). + if (ObjCache) { + // MemoryBuffer is a thin wrapper around the actual memory, so it's OK + // to create a temporary object here and delete it after the call. + OwningPtr MB(CompiledObject->getMemBuffer()); + ObjCache->notifyObjectCompiled(m, MB.get()); + } + + return CompiledObject.take(); +} + +void MCJIT::loadObject(Module *M) { + + // Get a thread lock to make sure we aren't trying to load multiple times + MutexGuard locked(lock); + + // FIXME: Track compilation state on a per-module basis when multiple modules + // are supported. + // Re-compilation is not supported + if (IsLoaded) + return; + + OwningPtr ObjectToLoad; + // Try to load the pre-compiled object from cache if possible + if (0 != ObjCache) { + OwningPtr PreCompiledObject(ObjCache->getObjectCopy(M)); + if (0 != PreCompiledObject.get()) + ObjectToLoad.reset(new ObjectBuffer(PreCompiledObject.take())); + } + + // If the cache did not contain a suitable object, compile the object + if (!ObjectToLoad) { + ObjectToLoad.reset(emitObject(M)); + assert(ObjectToLoad.get() && "Compilation did not produce an object."); + } // Load the object into the dynamic linker. // handing off ownership of the buffer - LoadedObject.reset(Dyld.loadObject(Buffer.take())); + LoadedObject.reset(Dyld.loadObject(ObjectToLoad.take())); if (!LoadedObject) report_fatal_error(Dyld.getErrorString()); @@ -113,7 +153,7 @@ void MCJIT::emitObject(Module *m) { NotifyObjectEmitted(*LoadedObject); // FIXME: Add support for per-module compilation state - isCompiled = true; + IsLoaded = true; } // FIXME: Add a parameter to identify which object is being finalized when @@ -122,10 +162,10 @@ void MCJIT::emitObject(Module *m) { // protection in the interface. void MCJIT::finalizeObject() { // If the module hasn't been compiled, just do that. - if (!isCompiled) { - // If the call to Dyld.resolveRelocations() is removed from emitObject() + if (!IsLoaded) { + // If the call to Dyld.resolveRelocations() is removed from loadObject() // we'll need to do that here. - emitObject(M); + loadObject(M); // Set page permissions. MemMgr->applyPermissions(); @@ -151,8 +191,8 @@ void *MCJIT::getPointerToFunction(Function *F) { // dies. // FIXME: Add support for per-module compilation state - if (!isCompiled) - emitObject(M); + if (!IsLoaded) + loadObject(M); if (F->isDeclaration() || F->hasAvailableExternallyLinkage()) { bool AbortOnFailure = !F->hasExternalWeakLinkage(); @@ -284,8 +324,8 @@ GenericValue MCJIT::runFunction(Function *F, void *MCJIT::getPointerToNamedFunction(const std::string &Name, bool AbortOnFailure) { // FIXME: Add support for per-module compilation state - if (!isCompiled) - emitObject(M); + if (!IsLoaded) + loadObject(M); if (!isSymbolSearchingDisabled() && MemMgr) { void *ptr = MemMgr->getPointerToNamedFunction(Name, false); diff --git a/llvm/lib/ExecutionEngine/MCJIT/MCJIT.h b/llvm/lib/ExecutionEngine/MCJIT/MCJIT.h index 283a8e528118..8c4bf6e1dbc7 100644 --- a/llvm/lib/ExecutionEngine/MCJIT/MCJIT.h +++ b/llvm/lib/ExecutionEngine/MCJIT/MCJIT.h @@ -12,6 +12,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ExecutionEngine/ExecutionEngine.h" +#include "llvm/ExecutionEngine/ObjectCache.h" #include "llvm/ExecutionEngine/RuntimeDyld.h" #include "llvm/PassManager.h" @@ -34,16 +35,23 @@ class MCJIT : public ExecutionEngine { SmallVector EventListeners; // FIXME: Add support for multiple modules - bool isCompiled; + bool IsLoaded; Module *M; OwningPtr LoadedObject; + // An optional ObjectCache to be notified of compiled objects and used to + // perform lookup of pre-compiled code to avoid re-compilation. + ObjectCache *ObjCache; + public: ~MCJIT(); /// @name ExecutionEngine interface implementation /// @{ + /// Sets the object manager that MCJIT should use to avoid compilation. + virtual void setObjectCache(ObjectCache *manager); + virtual void finalizeObject(); virtual void *getPointerToBasicBlock(BasicBlock *BB); @@ -102,7 +110,9 @@ protected: /// this function call is expected to be the contained module. The module /// is passed as a parameter here to prepare for multiple module support in /// the future. - void emitObject(Module *M); + ObjectBufferStream* emitObject(Module *M); + + void loadObject(Module *M); void NotifyObjectEmitted(const ObjectImage& Obj); void NotifyFreeingObject(const ObjectImage& Obj); diff --git a/llvm/unittests/ExecutionEngine/MCJIT/CMakeLists.txt b/llvm/unittests/ExecutionEngine/MCJIT/CMakeLists.txt index c6b1f77e3e8b..9ffe6138ad76 100644 --- a/llvm/unittests/ExecutionEngine/MCJIT/CMakeLists.txt +++ b/llvm/unittests/ExecutionEngine/MCJIT/CMakeLists.txt @@ -10,6 +10,7 @@ set(LLVM_LINK_COMPONENTS set(MCJITTestsSources MCJITTest.cpp MCJITMemoryManagerTest.cpp + MCJITObjectCacheTest.cpp ) if(MSVC) diff --git a/llvm/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp b/llvm/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp new file mode 100644 index 000000000000..0061e30e7a54 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp @@ -0,0 +1,240 @@ +//===- 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 "llvm/ADT/OwningPtr.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/ExecutionEngine/JIT.h" +#include "llvm/ExecutionEngine/MCJIT.h" +#include "llvm/ExecutionEngine/ObjectCache.h" +#include "llvm/ExecutionEngine/SectionMemoryManager.h" +#include "MCJITTestBase.h" +#include "gtest/gtest.h" + +using namespace llvm; + +namespace { + +class TestObjectCache : public ObjectCache { +public: + TestObjectCache() : DuplicateInserted(false) { } + + virtual ~TestObjectCache() { + // Free any buffers we've allocated. + SmallVector::iterator it, end; + end = AllocatedBuffers.end(); + for (it = AllocatedBuffers.begin(); it != end; ++it) { + delete *it; + } + AllocatedBuffers.clear(); + } + + virtual void notifyObjectCompiled(const Module *M, const MemoryBuffer *Obj) { + // 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); + } + + // 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::iterator it = ObjMap.find(ModuleID); + if (it == ObjMap.end()) + return 0; + return it->second; + } + +protected: + virtual const MemoryBuffer* getObject(const Module* M) { + const MemoryBuffer* BufferFound = getObjectInternal(M); + ModulesLookedUp.insert(M->getModuleIdentifier()); + return BufferFound; + } + +private: + MemoryBuffer *copyBuffer(const MemoryBuffer *Buf) { + // Create a local copy of the buffer. + MemoryBuffer *NewBuffer = MemoryBuffer::getMemBufferCopy(Buf->getBuffer()); + AllocatedBuffers.push_back(NewBuffer); + return NewBuffer; + } + + StringMap ObjMap; + StringSet<> ModulesLookedUp; + SmallVector AllocatedBuffers; + bool DuplicateInserted; +}; + +class MCJITObjectCacheTest : public testing::Test, public MCJITTestBase { +protected: + + enum { + OriginalRC = 6, + ReplacementRC = 7 + }; + + virtual void SetUp() { + M.reset(createEmptyModule("
")); + Main = insertMainFunction(M.get(), OriginalRC); + } + + void compileAndRun(int ExpectedRC = OriginalRC) { + // This function shouldn't be called until after SetUp. + ASSERT_TRUE(0 != TheJIT); + ASSERT_TRUE(0 != Main); + + TheJIT->finalizeObject(); + void *vPtr = TheJIT->getPointerToFunction(Main); + + static_cast(MM)->invalidateInstructionCache(); + + EXPECT_TRUE(0 != vPtr) + << "Unable to get pointer to main() from JIT"; + + int (*FuncPtr)(void) = (int(*)(void))(intptr_t)vPtr; + int returnCode = FuncPtr(); + EXPECT_EQ(returnCode, ExpectedRC); + } + + Function *Main; +}; + +TEST_F(MCJITObjectCacheTest, SetNullObjectCache) { + SKIP_UNSUPPORTED_PLATFORM; + + createJIT(M.take()); + + TheJIT->setObjectCache(NULL); + + compileAndRun(); +} + + +TEST_F(MCJITObjectCacheTest, VerifyBasicObjectCaching) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr Cache(new TestObjectCache); + + // Save a copy of the module pointer before handing it off to MCJIT. + const Module * SavedModulePointer = M.get(); + + createJIT(M.take()); + + TheJIT->setObjectCache(Cache.get()); + + // Verify that our object cache does not contain the module yet. + const MemoryBuffer *ObjBuffer = Cache->getObjectInternal(SavedModulePointer); + EXPECT_EQ(0, 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(0 != ObjBuffer); + + // Verify that the cache was only notified once. + EXPECT_FALSE(Cache->wereDuplicatesInserted()); +} + +TEST_F(MCJITObjectCacheTest, VerifyLoadFromCache) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr Cache(new TestObjectCache); + + // Compile this module with an MCJIT engine + createJIT(M.take()); + TheJIT->setObjectCache(Cache.get()); + TheJIT->finalizeObject(); + + // Destroy the MCJIT engine we just used + TheJIT.reset(); + + // Create a new memory manager. + MM = 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 = insertMainFunction(M.get(), ReplacementRC); + const Module * SecondModulePointer = M.get(); + + // Create a new MCJIT instance to load this module then execute it. + createJIT(M.take()); + 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; + + OwningPtr Cache(new TestObjectCache); + + // Compile this module with an MCJIT engine + createJIT(M.take()); + TheJIT->setObjectCache(Cache.get()); + TheJIT->finalizeObject(); + + // Destroy the MCJIT engine we just used + TheJIT.reset(); + + // Create a new memory manager. + MM = 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("")); + Main = insertMainFunction(M.get(), ReplacementRC); + const Module * SecondModulePointer = M.get(); + + // Create a new MCJIT instance to load this module then execute it. + createJIT(M.take()); + TheJIT->setObjectCache(Cache.get()); + + // Verify that our object cache does not contain the module yet. + const MemoryBuffer *ObjBuffer = Cache->getObjectInternal(SecondModulePointer); + EXPECT_EQ(0, 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(0 != ObjBuffer); + + // Verify that MCJIT didn't try to cache this again. + EXPECT_FALSE(Cache->wereDuplicatesInserted()); +} + +} // Namespace +