[llvm] r180146 - Adding object caching support to MCJIT

Andrew Kaylor andrew.kaylor at intel.com
Tue Apr 23 14:26:38 PDT 2013


Author: akaylor
Date: Tue Apr 23 16:26:38 2013
New Revision: 180146

URL: http://llvm.org/viewvc/llvm-project?rev=180146&view=rev
Log:
Adding object caching support to MCJIT

Added:
    llvm/trunk/include/llvm/ExecutionEngine/ObjectCache.h
    llvm/trunk/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp
Modified:
    llvm/trunk/include/llvm/ExecutionEngine/ExecutionEngine.h
    llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.cpp
    llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.h

Modified: llvm/trunk/include/llvm/ExecutionEngine/ExecutionEngine.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/ExecutionEngine/ExecutionEngine.h?rev=180146&r1=180145&r2=180146&view=diff
==============================================================================
--- llvm/trunk/include/llvm/ExecutionEngine/ExecutionEngine.h (original)
+++ llvm/trunk/include/llvm/ExecutionEngine/ExecutionEngine.h Tue Apr 23 16:26:38 2013
@@ -42,6 +42,7 @@ class JITMemoryManager;
 class MachineCodeInfo;
 class Module;
 class MutexGuard;
+class ObjectCache;
 class DataLayout;
 class Triple;
 class Type;
@@ -371,6 +372,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 by 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

Added: llvm/trunk/include/llvm/ExecutionEngine/ObjectCache.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/ExecutionEngine/ObjectCache.h?rev=180146&view=auto
==============================================================================
--- llvm/trunk/include/llvm/ExecutionEngine/ObjectCache.h (added)
+++ llvm/trunk/include/llvm/ExecutionEngine/ObjectCache.h Tue Apr 23 16:26:38 2013
@@ -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

Modified: llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.cpp?rev=180146&r1=180145&r2=180146&view=diff
==============================================================================
--- llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.cpp (original)
+++ llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.cpp Tue Apr 23 16:26:38 2013
@@ -52,7 +52,7 @@ ExecutionEngine *MCJIT::createJIT(Module
 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,63 @@ 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<ObjectBufferStream> Buffer(new ObjectBufferStream());
+  OwningPtr<ObjectBufferStream> 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) {
+    ObjCache->notifyObjectCompiled(m, CompiledObject->getMemBuffer());
+  }
+
+  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<ObjectBuffer> ObjectToLoad;
+  // Try to load the pre-compiled object from cache if possible
+  if (0 != ObjCache) {
+    OwningPtr<MemoryBuffer> 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 +150,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 +159,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 +188,8 @@ void *MCJIT::getPointerToFunction(Functi
   // 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 +321,8 @@ GenericValue MCJIT::runFunction(Function
 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);

Modified: llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.h?rev=180146&r1=180145&r2=180146&view=diff
==============================================================================
--- llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.h (original)
+++ llvm/trunk/lib/ExecutionEngine/MCJIT/MCJIT.h Tue Apr 23 16:26:38 2013
@@ -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<JITEventListener*, 2> EventListeners;
 
   // FIXME: Add support for multiple modules
-  bool isCompiled;
+  bool IsLoaded;
   Module *M;
   OwningPtr<ObjectImage> 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);

Added: llvm/trunk/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp?rev=180146&view=auto
==============================================================================
--- llvm/trunk/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp (added)
+++ llvm/trunk/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp Tue Apr 23 16:26:38 2013
@@ -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<MemoryBuffer *, 2>::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<const MemoryBuffer *>::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<const MemoryBuffer *> ObjMap;
+  StringSet<>                     ModulesLookedUp;
+  SmallVector<MemoryBuffer *, 2>  AllocatedBuffers;
+  bool                            DuplicateInserted;
+};
+
+class MCJITObjectCacheTest : public testing::Test, public MCJITTestBase {
+protected:
+
+  enum {
+    OriginalRC = 6,
+    ReplacementRC = 7
+  };
+
+  virtual void SetUp() {
+    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(0 != TheJIT);
+    ASSERT_TRUE(0 != Main);
+
+    TheJIT->finalizeObject();
+    void *vPtr = TheJIT->getPointerToFunction(Main);
+
+    static_cast<SectionMemoryManager*>(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<TestObjectCache>  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<TestObjectCache>  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>"));
+  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<TestObjectCache>  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("<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(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
+





More information about the llvm-commits mailing list