[llvm-commits] [llvm] [Patch] Add RuntimeDyld.invalidateEmittedSectionsCache()

Jim Grosbach grosbach at apple.com
Wed May 16 09:37:27 PDT 2012


On May 15, 2012, at 8:30 PM, "Kaylor, Andrew" <andrew.kaylor at intel.com> wrote:
>  
> Can you clarify what you expect to be the future lifespan of the current DefaultMemoryManager?  Are you planning for it to go away completely or just lose the legacy JIT parts?
>  

At least at the moment, I'm thinking that it'll just lose the legacy parts. I think it's still useful for providing a very simple just-good-enough-for-lli implementation. I'm a bit uncomfortable with how smart it tries to be already, honestly. Eventually, when the old JIT goes away, we'll be able to merge the JITMemoryManager and the MCJITMemoryManager. Right now, it's basically a translation layer. During that process we'll be able to sweep away a lot of the old APIs and such.

One organizational change I'm thinking about, in no small part as a result of thinking about this patch and related issues, is moving the DefaultMemoryManager out of JITMemoryManager.cpp and into lli itself, or at least into a separate implementation file. I'm concerned that as-is, the organization implies that the default memory manager is more than it really is. More on that in a moment.

> We’re about to start work on some code to be able to set permissions correctly on a section-by-section basis (i.e. making code sections read-only after they’re written and not making them executable until they’re written, supporting read-only data sections, etc.).  The allocation scheme of the current DefaultMemoryManager is fairly complex and I’d rather not touch it if it’s going away, but if it’s sticking around I suppose it’s the place to start working.

Mostly off the cuff here, so take with a large grain of salt, but that strikes me as being more feature-rich than the default memory manager should be. The idea is that the DefaultMemoryManager provides just enough functionality so things will work in very simple environments (so lli can run tests and such). Anything fancier should probably implement a separate manager that knows how to be smarter. That said, I think it's a good idea to have a more feature-rich memory manager in the LLVM and to make it available to lli via an option, much like we can currently switch between mcjit and the old jit in lli, or fast-isel vs. selectiondag in codegen.

In general, the low level memory management aspects of the JIT are an application concern. Allocating memory, twiddling the permissions, injecting sections into the target memory space, etc.. are all things that are going to vary widely depending on the client. The JITMemoryManager is just a set of client hooks the JIT uses to tell the client what's happening. The less actual logic that's going on in the core JITMemoryManager, the better, other than perhaps some common bookkeeping. The DefaultMemoryManager implements those hooks enough to support a simple hosted lli and mainly serves as an example. I'm leery of it being used in any real application and want to organize the code to imply that.

This patch is a good example of how all of this is incredibly confusing. It's completely non-obvious based on the current code organization where things belong. Part of that is needing the MCJITMemoryManager interface. Once we get everything moved over and the old JIT goes away, this new class in lli can have its functionality merged with the default memory manager implementation directly (which does assume a hosted environment), simplifying things greatly.

> Regarding the review below, wanting to manage permissions on a per-section basis makes me uncomfortable with putting the memory immediately following an allocated code block back in the available pool, as we might prefer to claim everything to the end of the last page with the allocation.  I suppose that doesn’t matter at the moment, and even in the long run a given memory manager could choose to ignore the permission setting requests.  I just wanted to raise this object now while the code is still fresh. 

Yeah, the code currently makes pretty significant simplifying assumptions about its environment like being able to have RWX memory at all and such. It doesn't consider things like making sure things w/ different permission requirements being in separate pages.

> Finally, the changes I’m talking about for permission setting will probably require some additions to the JITMemoryManager interface.  Should I open a discussion on the main LLVM Dev list about that?
>  

Sure, if you're at  a point where external feedback would be useful to you, that sounds great.

Regards,

   Jim


> -Andy
>  
> From: llvm-commits-bounces at cs.uiuc.edu [mailto:llvm-commits-bounces at cs.uiuc.edu] On Behalf Of Jim Grosbach
> Sent: Tuesday, May 15, 2012 2:01 PM
> To: Danil Malyshev
> Cc: llvm-commits at cs.uiuc.edu
> Subject: Re: [llvm-commits] [llvm] [Patch] Add RuntimeDyld.invalidateEmittedSectionsCache()
>  
> Hi Danil,
>  
> OK. This is definitely much more consistent with what we're looking to do. It's using features of the primary JIT memory managers that will (probably) go away when we remove the old JIT, but that's fine for now. Moving the logic into this memory manager will be part of that process and will provide a good example for what's expected of other applications in that regard.
>  
> More specific comments below. Mostly just style and format nitpicks.
>  
> With these changes, this is good for commit. Sorry again for the initial confusion. Entirely my fault.
>  
> -Jim
>  
> 
> 
> Index: lib/ExecutionEngine/MCJIT/MCJITMemoryManager.h
> ===================================================================
> --- lib/ExecutionEngine/MCJIT/MCJITMemoryManager.h   (revision 156826)
> +++ lib/ExecutionEngine/MCJIT/MCJITMemoryManager.h            (working copy)
> @@ -34,12 +34,12 @@
>  
>    uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
>                                 unsigned SectionID) {
> -    return JMM->allocateSpace(Size, Alignment);
> +    return JMM->allocateDataSection(Size, Alignment, SectionID);
>    }
>  
>    uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
>                                 unsigned SectionID) {
> -    return JMM->allocateSpace(Size, Alignment);
> +    return JMM->allocateCodeSection(Size, Alignment, SectionID);
>    }
>  
>    virtual void *getPointerToNamedFunction(const std::string &Name,
> Index: tools/lli/lli.cpp
> ===================================================================
> --- tools/lli/lli.cpp         (revision 156826)
> +++ tools/lli/lli.cpp      (working copy)
> @@ -35,8 +35,18 @@
>  #include "llvm/Support/Process.h"
>  #include "llvm/Support/Signals.h"
>  #include "llvm/Support/TargetSelect.h"
> +#include "llvm/Support/DynamicLibrary.h"
> +#include "llvm/Support/Memory.h"
>  #include <cerrno>
>  
> +#ifdef __linux__
> +#ifdef HAVE_SYS_STAT_H
> +#include <sys/stat.h>
> +#endif
> +#include <fcntl.h>
> +#include <unistd.h>
> +#endif
> +
>  
> This is so we get declarations for the specially handled symbols below? Assuming so, a comment to that effect would be good. We generally try to wrap platform specific stuff in llvm/Support/*, but given the very specific nature of this glibc trickery, I'm not sure that works well here, so we just need to comment about it.
>  
>  #ifdef __CYGWIN__
>  #include <cygwin/version.h>
>  #if defined(CYGWIN_VERSION_DLL_MAJOR) && CYGWIN_VERSION_DLL_MAJOR<1007
> @@ -175,6 +185,157 @@
>  #endif
>  }
>  
> +// Memory manager for MCJIT
> +class LLIMCJITMemoryManager : public JITMemoryManager {
> +public:
> +  SmallVector<sys::MemoryBlock, 16> AllocatedDataMem;
> +  SmallVector<sys::MemoryBlock, 16> AllocatedCodeMem;
> +  SmallVector<sys::MemoryBlock, 16> FreeCodeMem;
> +
> +  LLIMCJITMemoryManager() { };
> +  ~LLIMCJITMemoryManager();
> +
> +  virtual uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment,
> +                                       unsigned SectionID);
> +
> +  virtual uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment,
> +                                       unsigned SectionID);
> +
> +  virtual void *getPointerToNamedFunction(const std::string &Name,
> +                                          bool AbortOnFailure = true);
> +
> +  // We need do it before execute on some platforms.
> +  virtual void invalidateInstructionCache();
> +
> +  // The MCJITMemoryManager doesn't use the following functions, so we don't
> +  // need implement them.
> +  virtual void setMemoryWritable() {};
> +  virtual void setMemoryExecutable() {};
> +  virtual void setPoisonMemory(bool poison) {};
> +  virtual void AllocateGOT() {};
> +  virtual uint8_t *getGOTBase() const { return 0; };
> +  virtual uint8_t *startFunctionBody(const Function *F,
> +                                     uintptr_t &ActualSize){ return 0; };
> +  virtual uint8_t *allocateStub(const GlobalValue* F, unsigned StubSize,
> +                                unsigned Alignment) { return 0; };
> +  virtual void endFunctionBody(const Function *F, uint8_t *FunctionStart,
> +                               uint8_t *FunctionEnd) { };
> +  virtual uint8_t *allocateSpace(intptr_t Size, unsigned Alignment) { return 0; };
> +  virtual uint8_t *allocateGlobal(uintptr_t Size, unsigned Alignment) { return 0; };
> +  virtual void deallocateFunctionBody(void *Body) { };
> +  virtual uint8_t* startExceptionTable(const Function* F,
> +                                       uintptr_t &ActualSize) { return 0; };
> +  virtual void endExceptionTable(const Function *F, uint8_t *TableStart,
> +                                 uint8_t *TableEnd, uint8_t* FrameRegister) { };
> +  virtual void deallocateExceptionTable(void *ET){ };
>  
> If these are unused, we should put an llvm_unreachable() inside them, or an assert() at least. Since we don't expect them to be called, we want the system to complain loudly if anything breaks our assumptions.
>  
> If they're used, but we have nothing useful to do in them, then the comment should be updated to be more clear on that point.
> 
> 
> It's not clear to me which is which from what's here. Since lli is often used as a reference point for others developing their own JIT drivers, it's helpful to spell things out as explicitly as we can both what we're doing and why.
> 
> 
> +};
> +
> +uint8_t *LLIMCJITMemoryManager::allocateDataSection(uintptr_t Size,
> +                                                   unsigned Alignment,
> +                                                   unsigned SectionID) {
>  
> Indentation on the following arguments is off by a space for this and the next function.
> 
> 
> +  if (!Alignment)
> +    Alignment = 1;
>  
> We want alignment, for certain. The sections need to be aligned to the greatest alignment of anything contained in them. One would hope that the caller (via whatever created the object file) would know that and would be passing in a non-zero alignment. I'm not sure that's true in all cases, though, so using a generous default alignment is a reasonable fallback. I believe 16-byte alignment is typical for lots of targets, and is larger than those that differ, so that's probably a reasonable default.
> 
> 
> +  uint8_t *Addr = (uint8_t*)calloc((Size+Alignment-1)/Alignment,Alignment);
> +  AllocatedDataMem.push_back(sys::MemoryBlock(Addr, Size));
> +  return Addr;
> +}
> +
> +uint8_t *LLIMCJITMemoryManager::allocateCodeSection(uintptr_t Size,
> +                                                   unsigned Alignment,
> +                                                   unsigned SectionID) {
> +  if (!Alignment)
> +    Alignment = 1;
>  
> We could probably have a smaller default alignment for code than for data, but there's really not a lot of point to being that picky. I'd just use 16 here as well for simplicity.
>  
> +
> +  unsigned NeedAllocate = Alignment*((Size+Alignment-1)/Alignment+1);
> +  uintptr_t Addr = 0;
> +  // Lookup free memory block with the enough size.
>  
> I suggest a bit more verbose comment here. Something like:
>  
> "Look in the list of free code memory regions and use a block there if one is available."
> 
> 
> +  for (int i = 0, e = FreeCodeMem.size(); i != e; ++i) {
> +    sys::MemoryBlock &MB = FreeCodeMem[i];
> +    if (MB.size() >= NeedAllocate) {
> +      Addr = (uintptr_t)MB.base();
> +      uintptr_t EndOfBlock = Addr + MB.size();
> +      // Allign the address.
>  
> s/Allign/Align/
> 
> 
> +      Addr = (Addr + Alignment-1) & ~(uintptr_t)(Alignment-1);
> +      // Store cutted free memory block.
> +      FreeCodeMem[i] = sys::MemoryBlock((void*)(Addr+Size),
> +                                    EndOfBlock-Addr-Size);
> +      break;
>  
> Just return the address directly here rather than breaking out of the loop.
> 
> 
> +    }
> +  }
> +  if (!Addr) {
>  
> With the early return above when we find a free block, we don't need this conditional at all.
> 
> 
> +    // If existed block not found, allocate new block.
>  
> I'd expand this a touch. Something like, "No pre-allocated free block was large enough. Allocate a new memory region."
> 
> 
> +    sys::MemoryBlock MB = sys::Memory::AllocateRWX(NeedAllocate, 0, 0);
> +
> +    AllocatedCodeMem.push_back(MB);
> +    Addr = (uintptr_t)MB.base();
> +    uintptr_t EndOfBlock = Addr + MB.size();
> +    // Allign the address.
> +    Addr = (Addr + Alignment-1) & ~(uintptr_t)(Alignment-1);
> +    // The AllocateRWX may allocate much more memory than we need. In this
> +    // case we will store unused memory as free memory block.
>  
> Grammar tweak:
>  
> "In this case, we store the unused memory as a free memory block."
> 
> 
> +    unsigned FreeSize = EndOfBlock-Addr-Size;
> +    if (FreeSize > 16) {
> +      FreeCodeMem.push_back(sys::MemoryBlock((void*)(Addr+Size), FreeSize));
> +    }
>  
> No need for enclosing compound statement braces here since the body is a single statement.
> 
> 
> +  }
> +  // Return alligned address
> +  return (uint8_t*)Addr;
> +}
> +
> +void LLIMCJITMemoryManager::invalidateInstructionCache() {
> +  for (int i = 0, e = AllocatedCodeMem.size(); i != e; ++i) {
> +    sys::Memory::InvalidateInstructionCache(AllocatedCodeMem[i].base(),
> +                                            AllocatedCodeMem[i].size());
> +  }
>  
> No need for enclosing compound statement braces.
> 
> 
> +}
> +
> +void *LLIMCJITMemoryManager::getPointerToNamedFunction(const std::string &Name,
> +                                                         bool AbortOnFailure) {
> +#if defined(__linux__)
> +  //===--------------------------------------------------------------------===//
> +  // Function stubs that are invoked instead of certain library calls
> +  //
> +  // Force the following functions to be linked in to anything that uses the
> +  // JIT. This is a hack designed to work around the all-too-clever Glibc
> +  // strategy of making these functions work differently when inlined vs. when
> +  // not inlined, and hiding their real definitions in a separate archive file
> +  // that the dynamic linker can't see. For more info, search for
> +  // 'libc_nonshared.a' on Google, or read http://llvm.org/PR274.
> +  if (Name == "stat") return (void*)(intptr_t)&stat;
> +  if (Name == "fstat") return (void*)(intptr_t)&fstat;
> +  if (Name == "lstat") return (void*)(intptr_t)&lstat;
> +  if (Name == "stat64") return (void*)(intptr_t)&stat64;
> +  if (Name == "fstat64") return (void*)(intptr_t)&fstat64;
> +  if (Name == "lstat64") return (void*)(intptr_t)&lstat64;
> +  if (Name == "atexit") return (void*)(intptr_t)&atexit;
> +  if (Name == "mknod") return (void*)(intptr_t)&mknod;
> +#endif // __linux__
> +
> +  const char *NameStr = Name.c_str();
> +  void *Ptr = sys::DynamicLibrary::SearchForAddressOfSymbol(NameStr);
> +  if (Ptr) return Ptr;
> +
> +  // If it wasn't found and if it starts with an underscore ('_') character,
> +  // try again without the underscore.
> +  if (NameStr[0] == '_') {
> +    Ptr = sys::DynamicLibrary::SearchForAddressOfSymbol(NameStr+1);
> +    if (Ptr) return Ptr;
> +  }
> +
> +  if (AbortOnFailure) {
>  
> No need for enclosing compound statement braces.
> 
> 
> +    report_fatal_error("Program used external function '"+Name+
> 
> Spaces around the "+" operators.
> 
> 
> +                      "' which could not be resolved!");
> +  }
> +  return 0;
> +}
> +
> +LLIMCJITMemoryManager::~LLIMCJITMemoryManager() {
> +  for (unsigned i = 0, e = AllocatedCodeMem.size(); i != e; ++i)
> +    sys::Memory::ReleaseRWX(AllocatedCodeMem[i]);
> +  for (unsigned i = 0, e = AllocatedDataMem.size(); i != e; ++i)
> +    free(AllocatedDataMem[i].base());
> +}
> +
>  //===----------------------------------------------------------------------===//
>  // main Driver function
>  //
> @@ -233,8 +394,11 @@
>      Mod->setTargetTriple(Triple::normalize(TargetTriple));
>  
>    // Enable MCJIT if desired.
> +  LLIMCJITMemoryManager *JMM = 0;
>    if (UseMCJIT && !ForceInterpreter) {
>      builder.setUseMCJIT(true);
> +    JMM = new LLIMCJITMemoryManager();
> +    builder.setJITMemoryManager(JMM);
>    }
>  
>    CodeGenOpt::Level OLvl = CodeGenOpt::Default;
> @@ -265,6 +429,9 @@
>      exit(1);
>    }
>  
> +  if (JMM)
> +    JMM->invalidateInstructionCache();
>  
> A comment explaining why we need to do this would be good.
> 
> 
> 
> +
>    // The following functions have no effect if their respective profiling
>    // support wasn't enabled in the build configuration.
>    EE->RegisterJITEventListener(
> Index: tools/llvm-rtdyld/llvm-rtdyld.cpp
> ===================================================================
> --- tools/llvm-rtdyld/llvm-rtdyld.cpp  (revision 156826)
> +++ tools/llvm-rtdyld/llvm-rtdyld.cpp           (working copy)
> @@ -63,20 +63,37 @@
>      return 0;
>    }
>  
> +  virtual void clearCache();
>  
> A comment explaining why we need to do this would be good.
> 
> 
> 
> +
>  };
>  
>  uint8_t *TrivialMemoryManager::allocateCodeSection(uintptr_t Size,
>                                                     unsigned Alignment,
>                                                     unsigned SectionID) {
> -  return (uint8_t*)sys::Memory::AllocateRWX(Size, 0, 0).base();
> +  sys::MemoryBlock MB = sys::Memory::AllocateRWX(Size, 0, 0);
> +  FunctionMemory.push_back(MB);
> +  return (uint8_t*)MB.base();
>  }
>  
>  uint8_t *TrivialMemoryManager::allocateDataSection(uintptr_t Size,
>                                                     unsigned Alignment,
>                                                     unsigned SectionID) {
> -  return (uint8_t*)sys::Memory::AllocateRWX(Size, 0, 0).base();
> +  sys::MemoryBlock MB = sys::Memory::AllocateRWX(Size, 0, 0);
> +  DataMemory.push_back(MB);
> +  return (uint8_t*)MB.base();
>  }
>  
> +void TrivialMemoryManager::clearCache() {
> +  for (int i = 0, e = FunctionMemory.size(); i != e; ++i) {
> +    sys::Memory::InvalidateInstructionCache(FunctionMemory[i].base(),
> +                                            FunctionMemory[i].size());
> +  }
> +  for (int i = 0, e = DataMemory.size(); i != e; ++i) {
> +    sys::Memory::InvalidateInstructionCache(DataMemory[i].base(),
> +                                            DataMemory[i].size());
> +  }
> No need for enclosing compound statement braces here since the body is a single statement.
> 
> 
> +}
> +
>  static const char *ProgramName;
>  
>  static void Message(const char *Type, const Twine &Msg) {
> @@ -113,6 +130,8 @@
>  
>    // Resolve all the relocations we can.
>    Dyld.resolveRelocations();
> +  // Invalidate cache before execute.
> +  MemMgr->clearCache();
>  
>    // FIXME: Error out if there are unresolved relocations.
>  
>  
>  
>  
>  
>  
> On May 15, 2012, at 11:50 AM, Danil Malyshev <dmalyshev at accesssoftek.com> wrote:
> 
> 
> Hi Jim,
>  
> I see, it's should be in the JITMemoryManager instead of the RTDyldMemoryManager.
>  
> Please review attached the patch. It's adds LLIMCJITMemoryManager to lli.
> The another way is add the clear cache to the DefaultJITMemoryManager, but attached way is more flexible for MCJIT tasks.
>  
>  
> Regards,
> Danil
>  
> From: Jim Grosbach [grosbach at apple.com]
> Sent: Monday, May 14, 2012 9:55 AM
> To: Danil Malyshev
> Cc: llvm-commits at cs.uiuc.edu
> Subject: Re: [llvm-commits] [llvm] [Patch] Add RuntimeDyld.invalidateEmittedSectionsCache()
> 
> Hi Danil,
>  
> I'm not sure what additional information you're looking for. The logic you're wanting to add belongs in the memory manager of the client application, not the MCJIT. For example, in the case of lli, it should be part of the TrivialMemoryManager there.
>  
> Note that the old JIT did this differently, as it assumed it wasn't ever going to deal with a remote target. It could, and did, make lots of assumptions about that. As such, be wary of using its code paths as a guide for how the MCJIT should work.
>  
> -Jim
>  
>  
> On May 14, 2012, at 9:49 AM, Danil Malyshev <dmalyshev at accesssoftek.com> wrote:
> 
> 
> Ping.
>  
>  
> Regards,
> Danil
>  
>  
> From: llvm-commits-bounces at cs.uiuc.edu [llvm-commits-bounces at cs.uiuc.edu] On Behalf Of Danil Malyshev [dmalyshev at accesssoftek.com]
> Sent: Tuesday, May 08, 2012 5:50 PM
> To: Jim Grosbach
> Cc: llvm-commits at cs.uiuc.edu
> Subject: Re: [llvm-commits] [llvm] [Patch] Add RuntimeDyld.invalidateEmittedSectionsCache()
> 
> Hi Jim,
>  
> Yes, I agree that it's client application job. But the MCJIT hard linked with hosted platform. It loads hosted library, it executes code on the host machine and etc. JIT and MCJIT was hidden from appication by EngineBuilder, their memory managers hidden too. The JIT does it for own code, I believe the MCJIT should do it too.
> The remote JIT will not be based by MCJIT, it's will be something new, with own memory manager inherited from RTDyldMemoryManager and in this case the remote client application will be cares about their cache.
>  
> Regards,
> Danil
>  
> From: Jim Grosbach [grosbach at apple.com]
> Sent: Tuesday, May 08, 2012 4:24 PM
> To: Danil Malyshev
> Cc: llvm-commits at cs.uiuc.edu
> Subject: Re: [llvm-commits] [llvm] [Patch] Add RuntimeDyld.invalidateEmittedSectionsCache()
> 
> Hi Danil,
>  
> This is closer, but not quite. By the client's memory manager, I mean the application. This functionality shouldn't be in the core MC-JIT at all. For example, it belongs in the TrivialMemoryManager in llvm-rtdyld.cpp. That includes keeping the list of allocations and sections. Consider a remote client. The MemoryBlock map you're creating here will refer to the local copy, not the memory that's actually on the target where it'll be executed. The code that knows how to map between the two is the client's memory manager, so that's where this logic belongs as well.
>  
> -Jim
>  
> On May 8, 2012, at 4:09 PM, Danil Malyshev <dmalyshev at accesssoftek.com> wrote:
> 
> 
> Hi,
>  
> Please review changed the patch.
> This patch adds finalize() to RTDyldMemoryManager, and implements it for MCJITMemoryManager as invalidate instruction cache.
>  
>  
> Regards,
> Danil
>  
>  
> From: Jim Grosbach [grosbach at apple.com]
> Sent: Monday, May 07, 2012 4:16 PM
> To: Danil Malyshev
> Cc: llvm-commits at cs.uiuc.edu
> Subject: Re: [llvm-commits] [llvm] [Patch] Add RuntimeDyld.invalidateEmittedSectionsCache()
> 
> Hi Danil,
>  
> That's not the RuntimeDyld's job. That functionality belongs in elsewhere, probably in the client's memory manager implementation where it's hooked into copying the compiled code into the target's address space. For a hosted platform, something similar just w/o the copying.
>  
> -Jim
>  
> On May 7, 2012, at 2:09 PM, Danil Malyshev <dmalyshev at accesssoftek.com> wrote:
> 
> 
> Hi everyone,
>  
> Please review attached the patch.
> This patch adds invalidateEmmittedSectionsCache() to the RuntimeDyld and uses it in the MCJIT after resolve relocations.
> The MCJIT works unstable on the ARM platforms without it.
> This patch haven't any tests because I want to commit the common ExecutionEngine/MCJIT tests after attached the patch will be committed. These tests will cover it.
> Regards,
> Danil
> <RuntimeDyld_invalidateEmittedSectionsCache-01.patch>_______________________________________________
> llvm-commits mailing list
> llvm-commits at cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/llvm-commits
>  
> <RTDyldMemoryManager_finalize-01.patch>
>  
> <LLIMCJITMemoryManager.patch>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20120516/795bb270/attachment.html>


More information about the llvm-commits mailing list