diff options
-rw-r--r-- | include/llvm/CodeGen/JITCodeEmitter.h | 5 | ||||
-rw-r--r-- | include/llvm/ExecutionEngine/ExecutionEngine.h | 20 | ||||
-rw-r--r-- | include/llvm/ExecutionEngine/JITMemoryManager.h | 11 | ||||
-rw-r--r-- | lib/ExecutionEngine/ExecutionEngine.cpp | 7 | ||||
-rw-r--r-- | lib/ExecutionEngine/Interpreter/Interpreter.cpp | 4 | ||||
-rw-r--r-- | lib/ExecutionEngine/Interpreter/Interpreter.h | 3 | ||||
-rw-r--r-- | lib/ExecutionEngine/JIT/JIT.cpp | 78 | ||||
-rw-r--r-- | lib/ExecutionEngine/JIT/JIT.h | 21 | ||||
-rw-r--r-- | lib/ExecutionEngine/JIT/JITEmitter.cpp | 10 | ||||
-rw-r--r-- | lib/ExecutionEngine/JIT/JITMemoryManager.cpp | 73 | ||||
-rw-r--r-- | lib/ExecutionEngine/JIT/TargetSelect.cpp | 5 | ||||
-rw-r--r-- | unittests/ExecutionEngine/JIT/JITTest.cpp | 126 |
12 files changed, 291 insertions, 72 deletions
diff --git a/include/llvm/CodeGen/JITCodeEmitter.h b/include/llvm/CodeGen/JITCodeEmitter.h index 73197af..cb9a0a1 100644 --- a/include/llvm/CodeGen/JITCodeEmitter.h +++ b/include/llvm/CodeGen/JITCodeEmitter.h @@ -267,6 +267,11 @@ public: return Result; } + /// allocateGlobal - Allocate memory for a global. Unlike allocateSpace, + /// this method does not allocate memory in the current output buffer, + /// because a global may live longer than the current function. + virtual void *allocateGlobal(uintptr_t Size, unsigned Alignment) = 0; + /// StartMachineBasicBlock - This should be called by the target when a new /// basic block is about to be emitted. This way the MCE knows where the /// start of the block is, and can implement getMachineBasicBlockAddress. diff --git a/include/llvm/ExecutionEngine/ExecutionEngine.h b/include/llvm/ExecutionEngine/ExecutionEngine.h index 84cfd23..b0f0d07 100644 --- a/include/llvm/ExecutionEngine/ExecutionEngine.h +++ b/include/llvm/ExecutionEngine/ExecutionEngine.h @@ -87,7 +87,8 @@ protected: // libraries, the JIT and Interpreter set these functions to ctor pointers // at startup time if they are linked in. typedef ExecutionEngine *(*EECtorFn)(ModuleProvider*, std::string*, - CodeGenOpt::Level OptLevel); + CodeGenOpt::Level OptLevel, + bool GVsWithCode); static EECtorFn JITCtor, InterpCtor; /// LazyFunctionCreator - If an unknown function is needed, this function @@ -118,8 +119,18 @@ public: bool ForceInterpreter = false, std::string *ErrorStr = 0, CodeGenOpt::Level OptLevel = - CodeGenOpt::Default); - + CodeGenOpt::Default, + // Allocating globals with code breaks + // freeMachineCodeForFunction and is probably + // unsafe and bad for performance. However, + // we have clients who depend on this + // behavior, so we must support it. + // Eventually, when we're willing to break + // some backwards compatability, this flag + // should be flipped to false, so that by + // default freeMachineCodeForFunction works. + bool GVsWithCode = true); + /// create - This is the factory method for creating an execution engine which /// is appropriate for the current machine. This takes ownership of the /// module. @@ -132,7 +143,8 @@ public: std::string *ErrorStr = 0, JITMemoryManager *JMM = 0, CodeGenOpt::Level OptLevel = - CodeGenOpt::Default); + CodeGenOpt::Default, + bool GVsWithCode = true); /// addModuleProvider - Add a ModuleProvider to the list of modules that we /// can JIT from. Note that this takes ownership of the ModuleProvider: when diff --git a/include/llvm/ExecutionEngine/JITMemoryManager.h b/include/llvm/ExecutionEngine/JITMemoryManager.h index 688a162..02ec1c3 100644 --- a/include/llvm/ExecutionEngine/JITMemoryManager.h +++ b/include/llvm/ExecutionEngine/JITMemoryManager.h @@ -28,6 +28,7 @@ protected: bool HasGOT; bool SizeRequired; public: + JITMemoryManager() : HasGOT(false), SizeRequired(false) {} virtual ~JITMemoryManager(); @@ -43,6 +44,11 @@ public: /// start execution, the code pages may need permissions changed. virtual void setMemoryExecutable(void) = 0; + /// setPoisonMemory - Setting this flag to true makes the memory manager + /// garbage values over freed memory. This is useful for testing and + /// debugging, and is be turned on by default in debug mode. + virtual void setPoisonMemory(bool poison) = 0; + //===--------------------------------------------------------------------===// // Global Offset Table Management //===--------------------------------------------------------------------===// @@ -114,7 +120,10 @@ public: /// allocateSpace - Allocate a memory block of the given size. virtual uint8_t *allocateSpace(intptr_t Size, unsigned Alignment) = 0; - + + /// allocateGlobal - Allocate memory for a global. + virtual uint8_t *allocateGlobal(uintptr_t Size, unsigned Alignment) = 0; + /// deallocateMemForFunction - Free JIT memory for the specified function. /// This is never called when the JIT is currently emitting a function. virtual void deallocateMemForFunction(const Function *F) = 0; diff --git a/lib/ExecutionEngine/ExecutionEngine.cpp b/lib/ExecutionEngine/ExecutionEngine.cpp index 69518b6..5ff7eb1 100644 --- a/lib/ExecutionEngine/ExecutionEngine.cpp +++ b/lib/ExecutionEngine/ExecutionEngine.cpp @@ -384,7 +384,8 @@ int ExecutionEngine::runFunctionAsMain(Function *Fn, ExecutionEngine *ExecutionEngine::create(ModuleProvider *MP, bool ForceInterpreter, std::string *ErrorStr, - CodeGenOpt::Level OptLevel) { + CodeGenOpt::Level OptLevel, + bool GVsWithCode) { ExecutionEngine *EE = 0; // Make sure we can resolve symbols in the program as well. The zero arg @@ -394,11 +395,11 @@ ExecutionEngine *ExecutionEngine::create(ModuleProvider *MP, // Unless the interpreter was explicitly selected, try making a JIT. if (!ForceInterpreter && JITCtor) - EE = JITCtor(MP, ErrorStr, OptLevel); + EE = JITCtor(MP, ErrorStr, OptLevel, GVsWithCode); // If we can't make a JIT, make an interpreter instead. if (EE == 0 && InterpCtor) - EE = InterpCtor(MP, ErrorStr, OptLevel); + EE = InterpCtor(MP, ErrorStr, OptLevel, GVsWithCode); return EE; } diff --git a/lib/ExecutionEngine/Interpreter/Interpreter.cpp b/lib/ExecutionEngine/Interpreter/Interpreter.cpp index d7f38ef..902f676 100644 --- a/lib/ExecutionEngine/Interpreter/Interpreter.cpp +++ b/lib/ExecutionEngine/Interpreter/Interpreter.cpp @@ -34,7 +34,8 @@ extern "C" void LLVMLinkInInterpreter() { } /// create - Create a new interpreter object. This can never fail. /// ExecutionEngine *Interpreter::create(ModuleProvider *MP, std::string* ErrStr, - CodeGenOpt::Level OptLevel /*unused*/) { + CodeGenOpt::Level OptLevel, /*unused*/ + bool GVsWithCode /* unused */) { // Tell this ModuleProvide to materialize and release the module if (!MP->materializeModule(ErrStr)) // We got an error, just return 0 @@ -98,4 +99,3 @@ Interpreter::runFunction(Function *F, return ExitValue; } - diff --git a/lib/ExecutionEngine/Interpreter/Interpreter.h b/lib/ExecutionEngine/Interpreter/Interpreter.h index 6b13c90..d56161f 100644 --- a/lib/ExecutionEngine/Interpreter/Interpreter.h +++ b/lib/ExecutionEngine/Interpreter/Interpreter.h @@ -108,7 +108,8 @@ public: /// create - Create an interpreter ExecutionEngine. This can never fail. /// static ExecutionEngine *create(ModuleProvider *M, std::string *ErrorStr = 0, - CodeGenOpt::Level = CodeGenOpt::Default); + CodeGenOpt::Level = CodeGenOpt::Default, + bool GVsWithCode = true); /// run - Start execution with the specified function and arguments. /// diff --git a/lib/ExecutionEngine/JIT/JIT.cpp b/lib/ExecutionEngine/JIT/JIT.cpp index 785b5b9..55ce16c 100644 --- a/lib/ExecutionEngine/JIT/JIT.cpp +++ b/lib/ExecutionEngine/JIT/JIT.cpp @@ -197,8 +197,10 @@ void DarwinRegisterFrame(void* FrameBegin) { ExecutionEngine *ExecutionEngine::createJIT(ModuleProvider *MP, std::string *ErrorStr, JITMemoryManager *JMM, - CodeGenOpt::Level OptLevel) { - ExecutionEngine *EE = JIT::createJIT(MP, ErrorStr, JMM, OptLevel); + CodeGenOpt::Level OptLevel, + bool GVsWithCode) { + ExecutionEngine *EE = JIT::createJIT(MP, ErrorStr, JMM, OptLevel, + GVsWithCode); if (!EE) return 0; // Make sure we can resolve symbols in the program as well. The zero arg @@ -208,8 +210,8 @@ ExecutionEngine *ExecutionEngine::createJIT(ModuleProvider *MP, } JIT::JIT(ModuleProvider *MP, TargetMachine &tm, TargetJITInfo &tji, - JITMemoryManager *JMM, CodeGenOpt::Level OptLevel) - : ExecutionEngine(MP), TM(tm), TJI(tji) { + JITMemoryManager *JMM, CodeGenOpt::Level OptLevel, bool GVsWithCode) + : ExecutionEngine(MP), TM(tm), TJI(tji), AllocateGVsWithCode(GVsWithCode) { setTargetData(TM.getTargetData()); jitstate = new JITState(MP); @@ -677,37 +679,11 @@ void *JIT::getOrEmitGlobalVariable(const GlobalVariable *GV) { } addGlobalMapping(GV, Ptr); } else { - // GlobalVariable's which are not "constant" will cause trouble in a server - // situation. It's returned in the same block of memory as code which may - // not be writable. - if (isGVCompilationDisabled() && !GV->isConstant()) { - cerr << "Compilation of non-internal GlobalValue is disabled!\n"; - abort(); - } // If the global hasn't been emitted to memory yet, allocate space and - // emit it into memory. It goes in the same array as the generated - // code, jump tables, etc. - const Type *GlobalType = GV->getType()->getElementType(); - size_t S = getTargetData()->getTypeAllocSize(GlobalType); - size_t A = getTargetData()->getPreferredAlignment(GV); - if (GV->isThreadLocal()) { - MutexGuard locked(lock); - Ptr = TJI.allocateThreadLocalMemory(S); - } else if (TJI.allocateSeparateGVMemory()) { - if (A <= 8) { - Ptr = malloc(S); - } else { - // Allocate S+A bytes of memory, then use an aligned pointer within that - // space. - Ptr = malloc(S+A); - unsigned MisAligned = ((intptr_t)Ptr & (A-1)); - Ptr = (char*)Ptr + (MisAligned ? (A-MisAligned) : 0); - } - } else { - Ptr = JCE->allocateSpace(S, A); - } + // emit it into memory. + Ptr = getMemoryForGV(GV); addGlobalMapping(GV, Ptr); - EmitGlobalVariable(GV); + EmitGlobalVariable(GV); // Initialize the variable. } return Ptr; } @@ -742,14 +718,42 @@ void *JIT::recompileAndRelinkFunction(Function *F) { /// on the target. /// char* JIT::getMemoryForGV(const GlobalVariable* GV) { - const Type *ElTy = GV->getType()->getElementType(); - size_t GVSize = (size_t)getTargetData()->getTypeAllocSize(ElTy); + char *Ptr; + + // GlobalVariable's which are not "constant" will cause trouble in a server + // situation. It's returned in the same block of memory as code which may + // not be writable. + if (isGVCompilationDisabled() && !GV->isConstant()) { + cerr << "Compilation of non-internal GlobalValue is disabled!\n"; + abort(); + } + + // Some applications require globals and code to live together, so they may + // be allocated into the same buffer, but in general globals are allocated + // through the memory manager which puts them near the code but not in the + // same buffer. + const Type *GlobalType = GV->getType()->getElementType(); + size_t S = getTargetData()->getTypeAllocSize(GlobalType); + size_t A = getTargetData()->getPreferredAlignment(GV); if (GV->isThreadLocal()) { MutexGuard locked(lock); - return TJI.allocateThreadLocalMemory(GVSize); + Ptr = TJI.allocateThreadLocalMemory(S); + } else if (TJI.allocateSeparateGVMemory()) { + if (A <= 8) { + Ptr = (char*)malloc(S); + } else { + // Allocate S+A bytes of memory, then use an aligned pointer within that + // space. + Ptr = (char*)malloc(S+A); + unsigned MisAligned = ((intptr_t)Ptr & (A-1)); + Ptr = Ptr + (MisAligned ? (A-MisAligned) : 0); + } + } else if (AllocateGVsWithCode) { + Ptr = (char*)JCE->allocateSpace(S, A); } else { - return new char[GVSize]; + Ptr = (char*)JCE->allocateGlobal(S, A); } + return Ptr; } void JIT::addPendingFunction(Function *F) { diff --git a/lib/ExecutionEngine/JIT/JIT.h b/lib/ExecutionEngine/JIT/JIT.h index 66417a7..c992509 100644 --- a/lib/ExecutionEngine/JIT/JIT.h +++ b/lib/ExecutionEngine/JIT/JIT.h @@ -55,10 +55,16 @@ class JIT : public ExecutionEngine { JITCodeEmitter *JCE; // JCE object std::vector<JITEventListener*> EventListeners; + /// AllocateGVsWithCode - Some applications require that global variables and + /// code be allocated into the same region of memory, in which case this flag + /// should be set to true. Doing so breaks freeMachineCodeForFunction. + bool AllocateGVsWithCode; + JITState *jitstate; - JIT(ModuleProvider *MP, TargetMachine &tm, TargetJITInfo &tji, - JITMemoryManager *JMM, CodeGenOpt::Level OptLevel); + JIT(ModuleProvider *MP, TargetMachine &tm, TargetJITInfo &tji, + JITMemoryManager *JMM, CodeGenOpt::Level OptLevel, + bool AllocateGVsWithCode); public: ~JIT(); @@ -75,8 +81,9 @@ public: /// static ExecutionEngine *create(ModuleProvider *MP, std::string *Err, CodeGenOpt::Level OptLevel = - CodeGenOpt::Default) { - return createJIT(MP, Err, 0, OptLevel); + CodeGenOpt::Default, + bool AllocateGVsWithCode = true) { + return createJIT(MP, Err, 0, OptLevel, AllocateGVsWithCode); } virtual void addModuleProvider(ModuleProvider *MP); @@ -151,9 +158,11 @@ public: /// getCodeEmitter - Return the code emitter this JIT is emitting into. JITCodeEmitter *getCodeEmitter() const { return JCE; } - static ExecutionEngine *createJIT(ModuleProvider *MP, std::string *Err, + static ExecutionEngine *createJIT(ModuleProvider *MP, + std::string *Err, JITMemoryManager *JMM, - CodeGenOpt::Level OptLevel); + CodeGenOpt::Level OptLevel, + bool AllocateGVsWithCode); // Run the JIT on F and return information about the generated code diff --git a/lib/ExecutionEngine/JIT/JITEmitter.cpp b/lib/ExecutionEngine/JIT/JITEmitter.cpp index 8fe7ab8..cfc3680 100644 --- a/lib/ExecutionEngine/JIT/JITEmitter.cpp +++ b/lib/ExecutionEngine/JIT/JITEmitter.cpp @@ -527,6 +527,11 @@ namespace { /// allocate a new one of the given size. virtual void *allocateSpace(uintptr_t Size, unsigned Alignment); + /// allocateGlobal - Allocate memory for a global. Unlike allocateSpace, + /// this method does not allocate memory in the current output buffer, + /// because a global may live longer than the current function. + virtual void *allocateGlobal(uintptr_t Size, unsigned Alignment); + virtual void addRelocation(const MachineRelocation &MR) { Relocations.push_back(MR); } @@ -1161,6 +1166,11 @@ void* JITEmitter::allocateSpace(uintptr_t Size, unsigned Alignment) { return CurBufferPtr; } +void* JITEmitter::allocateGlobal(uintptr_t Size, unsigned Alignment) { + // Delegate this call through the memory manager. + return MemMgr->allocateGlobal(Size, Alignment); +} + void JITEmitter::emitConstantPool(MachineConstantPool *MCP) { if (TheJIT->getJITInfo().hasCustomConstantPool()) return; diff --git a/lib/ExecutionEngine/JIT/JITMemoryManager.cpp b/lib/ExecutionEngine/JIT/JITMemoryManager.cpp index 70ccdcc..9820493 100644 --- a/lib/ExecutionEngine/JIT/JITMemoryManager.cpp +++ b/lib/ExecutionEngine/JIT/JITMemoryManager.cpp @@ -251,6 +251,8 @@ namespace { /// middle of emitting a function, and we don't know how large the function we /// are emitting is. class VISIBILITY_HIDDEN DefaultJITMemoryManager : public JITMemoryManager { + bool PoisonMemory; // Whether to poison freed memory. + std::vector<sys::MemoryBlock> Blocks; // Memory blocks allocated by the JIT FreeRangeHeader *FreeMemoryList; // Circular list of free blocks. @@ -258,6 +260,7 @@ namespace { MemoryRangeHeader *CurBlock; uint8_t *CurStubPtr, *StubBase; + uint8_t *CurGlobalPtr, *GlobalEnd; uint8_t *GOTBase; // Target Specific reserved memory void *DlsymTable; // Stub external symbol information @@ -324,7 +327,7 @@ namespace { CurBlock = FreeMemoryList; FreeMemoryList = FreeMemoryList->AllocateBlock(); - uint8_t *result = (uint8_t *)CurBlock+1; + uint8_t *result = (uint8_t *)(CurBlock + 1); if (Alignment == 0) Alignment = 1; result = (uint8_t*)(((intptr_t)result+Alignment-1) & @@ -336,6 +339,30 @@ namespace { return result; } + /// allocateGlobal - Allocate memory for a global. Unlike allocateSpace, + /// this method does not touch the current block and can be called at any + /// time. + uint8_t *allocateGlobal(uintptr_t Size, unsigned Alignment) { + uint8_t *Result = CurGlobalPtr; + + // Align the pointer. + if (Alignment == 0) Alignment = 1; + Result = (uint8_t*)(((uintptr_t)Result + Alignment-1) & + ~(uintptr_t)(Alignment-1)); + + // Move the current global pointer forward. + CurGlobalPtr += Result - CurGlobalPtr + Size; + + // Check for overflow. + if (CurGlobalPtr > GlobalEnd) { + // FIXME: Allocate more memory. + fprintf(stderr, "JIT ran out of memory for globals!\n"); + abort(); + } + + return Result; + } + /// startExceptionTable - Use startFunctionBody to allocate memory for the /// function's exception table. uint8_t* startExceptionTable(const Function* F, uintptr_t &ActualSize) { @@ -375,12 +402,12 @@ namespace { // Find the block that is allocated for this function. MemoryRangeHeader *MemRange = I->second; assert(MemRange->ThisAllocated && "Block isn't allocated!"); - + // Fill the buffer with garbage! -#ifndef NDEBUG - memset(MemRange+1, 0xCD, MemRange->BlockSize-sizeof(*MemRange)); -#endif - + if (PoisonMemory) { + memset(MemRange+1, 0xCD, MemRange->BlockSize-sizeof(*MemRange)); + } + // Free the memory. FreeMemoryList = MemRange->FreeBlock(FreeMemoryList); @@ -393,12 +420,12 @@ namespace { // Find the block that is allocated for this function. MemRange = I->second; assert(MemRange->ThisAllocated && "Block isn't allocated!"); - + // Fill the buffer with garbage! -#ifndef NDEBUG - memset(MemRange+1, 0xCD, MemRange->BlockSize-sizeof(*MemRange)); -#endif - + if (PoisonMemory) { + memset(MemRange+1, 0xCD, MemRange->BlockSize-sizeof(*MemRange)); + } + // Free the memory. FreeMemoryList = MemRange->FreeBlock(FreeMemoryList); @@ -420,10 +447,22 @@ namespace { for (unsigned i = 0, e = Blocks.size(); i != e; ++i) sys::Memory::setExecutable(Blocks[i]); } + + /// setPoisonMemory - Controls whether we write garbage over freed memory. + /// + void setPoisonMemory(bool poison) { + PoisonMemory = poison; + } }; } DefaultJITMemoryManager::DefaultJITMemoryManager() { +#ifdef NDEBUG + PoisonMemory = true; +#else + PoisonMemory = false; +#endif + // Allocate a 16M block of memory for functions. #if defined(__APPLE__) && defined(__arm__) sys::MemoryBlock MemBlock = getNewMemoryBlock(4 << 20); @@ -433,11 +472,13 @@ DefaultJITMemoryManager::DefaultJITMemoryManager() { uint8_t *MemBase = static_cast<uint8_t*>(MemBlock.base()); - // Allocate stubs backwards from the base, allocate functions forward - // from the base. + // Allocate stubs backwards to the base, globals forward from the stubs, and + // functions forward after globals. StubBase = MemBase; CurStubPtr = MemBase + 512*1024; // Use 512k for stubs, working backwards. - + CurGlobalPtr = CurStubPtr; // Use 2M for globals, working forwards. + GlobalEnd = CurGlobalPtr + 2*1024*1024; + // We set up the memory chunk with 4 mem regions, like this: // [ START // [ Free #0 ] -> Large space to allocate functions from. @@ -474,7 +515,7 @@ DefaultJITMemoryManager::DefaultJITMemoryManager() { // Add a FreeRangeHeader to the start of the function body region, indicating // that the space is free. Mark the previous block allocated so we never look // at it. - FreeRangeHeader *Mem0 = (FreeRangeHeader*)CurStubPtr; + FreeRangeHeader *Mem0 = (FreeRangeHeader*)GlobalEnd; Mem0->ThisAllocated = 0; Mem0->PrevAllocated = 1; Mem0->BlockSize = (char*)Mem1-(char*)Mem0; @@ -522,7 +563,7 @@ uint8_t *DefaultJITMemoryManager::allocateStub(const GlobalValue* F, sys::MemoryBlock DefaultJITMemoryManager::getNewMemoryBlock(unsigned size) { // Allocate a new block close to the last one. - const sys::MemoryBlock *BOld = Blocks.empty() ? 0 : &Blocks.front(); + const sys::MemoryBlock *BOld = Blocks.empty() ? 0 : &Blocks.back(); std::string ErrMsg; sys::MemoryBlock B = sys::Memory::AllocateRWX(size, BOld, &ErrMsg); if (B.base() == 0) { diff --git a/lib/ExecutionEngine/JIT/TargetSelect.cpp b/lib/ExecutionEngine/JIT/TargetSelect.cpp index 0f20819..24dd013 100644 --- a/lib/ExecutionEngine/JIT/TargetSelect.cpp +++ b/lib/ExecutionEngine/JIT/TargetSelect.cpp @@ -43,7 +43,8 @@ MAttrs("mattr", /// ExecutionEngine *JIT::createJIT(ModuleProvider *MP, std::string *ErrorStr, JITMemoryManager *JMM, - CodeGenOpt::Level OptLevel) { + CodeGenOpt::Level OptLevel, + bool AllocateGVsWithCode) { const TargetMachineRegistry::entry *TheArch = MArch; if (TheArch == 0) { std::string Error; @@ -75,7 +76,7 @@ ExecutionEngine *JIT::createJIT(ModuleProvider *MP, std::string *ErrorStr, // If the target supports JIT code generation, return a new JIT now. if (TargetJITInfo *TJ = Target->getJITInfo()) - return new JIT(MP, *Target, *TJ, JMM, OptLevel); + return new JIT(MP, *Target, *TJ, JMM, OptLevel, AllocateGVsWithCode); if (ErrorStr) *ErrorStr = "target does not support JIT code generation"; diff --git a/unittests/ExecutionEngine/JIT/JITTest.cpp b/unittests/ExecutionEngine/JIT/JITTest.cpp new file mode 100644 index 0000000..8f29918 --- /dev/null +++ b/unittests/ExecutionEngine/JIT/JITTest.cpp @@ -0,0 +1,126 @@ +//===- JITEmitter.cpp - Unit tests for the JIT code emitter ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" +#include "llvm/ADT/OwningPtr.h" +#include "llvm/BasicBlock.h" +#include "llvm/Constant.h" +#include "llvm/Constants.h" +#include "llvm/DerivedTypes.h" +#include "llvm/ExecutionEngine/JIT.h" +#include "llvm/ExecutionEngine/JITMemoryManager.h" +#include "llvm/Function.h" +#include "llvm/GlobalValue.h" +#include "llvm/GlobalVariable.h" +#include "llvm/Module.h" +#include "llvm/ModuleProvider.h" +#include "llvm/Support/IRBuilder.h" +#include "llvm/Target/TargetSelect.h" +#include "llvm/Type.h" + +using namespace llvm; + +namespace { + +Function *makeReturnGlobal(std::string Name, GlobalVariable *G, Module *M) { + std::vector<const Type*> params; + const FunctionType *FTy = FunctionType::get(Type::VoidTy, params, false); + Function *F = Function::Create(FTy, GlobalValue::ExternalLinkage, Name, M); + BasicBlock *Entry = BasicBlock::Create("entry", F); + IRBuilder<> builder(Entry); + Value *Load = builder.CreateLoad(G); + const Type *GTy = G->getType()->getElementType(); + Value *Add = builder.CreateAdd(Load, ConstantInt::get(GTy, 1LL)); + builder.CreateStore(Add, G); + builder.CreateRet(Add); + return F; +} + +// Regression test for a bug. The JIT used to allocate globals inside the same +// memory block used for the function, and when the function code was freed, +// the global was left in the same place. This test allocates a function +// that uses and global, deallocates it, and then makes sure that the global +// stays alive after that. +TEST(JIT, GlobalInFunction) { + LLVMContext context; + Module *M = new Module("<main>", context); + ExistingModuleProvider *MP = new ExistingModuleProvider(M); + + JITMemoryManager *MemMgr = JITMemoryManager::CreateDefaultMemManager(); + // Tell the memory manager to poison freed memory so that accessing freed + // memory is more easily tested. + MemMgr->setPoisonMemory(true); + std::string Error; + OwningPtr<ExecutionEngine> JIT(ExecutionEngine::createJIT( + MP, + &Error, + MemMgr, + CodeGenOpt::Default, + false)); // This last argument enables the fix. + ASSERT_EQ(Error, ""); + + // Create a global variable. + const Type *GTy = Type::Int32Ty; + GlobalVariable *G = new GlobalVariable( + *M, + GTy, + false, // Not constant. + GlobalValue::InternalLinkage, + Constant::getNullValue(GTy), + "myglobal"); + + // Make a function that points to a global. + Function *F1 = makeReturnGlobal("F1", G, M); + + // Get the pointer to the native code to force it to JIT the function and + // allocate space for the global. + void (*F1Ptr)(); + // Hack to avoid ISO C++ warning about casting function pointers. + *(void**)(void*)&F1Ptr = JIT->getPointerToFunction(F1); + + // Since F1 was codegen'd, a pointer to G should be available. + int32_t *GPtr = (int32_t*)JIT->getPointerToGlobalIfAvailable(G); + ASSERT_NE((int32_t*)NULL, GPtr); + EXPECT_EQ(0, *GPtr); + + // F1() should increment G. + F1Ptr(); + EXPECT_EQ(1, *GPtr); + + // Make a second function identical to the first, referring to the same + // global. + Function *F2 = makeReturnGlobal("F2", G, M); + // Hack to avoid ISO C++ warning about casting function pointers. + void (*F2Ptr)(); + *(void**)(void*)&F2Ptr = JIT->getPointerToFunction(F2); + + // F2() should increment G. + F2Ptr(); + EXPECT_EQ(2, *GPtr); + + // Deallocate F1. + JIT->freeMachineCodeForFunction(F1); + + // F2() should *still* increment G. + F2Ptr(); + EXPECT_EQ(3, *GPtr); +} + +// TODO(rnk): This seems to only run once for both tests, which is unexpected. +// That works just fine, but we shouldn't duplicate the code. +class JITEnvironment : public testing::Environment { + virtual void SetUp() { + // Required for ExecutionEngine::createJIT to create a JIT. + InitializeNativeTarget(); + } +}; +testing::Environment* const jit_env = + testing::AddGlobalTestEnvironment(new JITEnvironment); + +} |