X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=unittests%2FExecutionEngine%2FJIT%2FJITTest.cpp;h=cf995aac7a1a06e8e012400c23443e0dfc64c2c2;hb=e4496548155ba6606f107fbdc10ea17e58fd3401;hp=fce756b04293abe0f82b5c4fdac47b7ab753b278;hpb=0a5372ed3e8cda10d724feda3c1a1c998db05ca0;p=oota-llvm.git diff --git a/unittests/ExecutionEngine/JIT/JITTest.cpp b/unittests/ExecutionEngine/JIT/JITTest.cpp index fce756b0429..cf995aac7a1 100644 --- a/unittests/ExecutionEngine/JIT/JITTest.cpp +++ b/unittests/ExecutionEngine/JIT/JITTest.cpp @@ -1,4 +1,4 @@ -//===- JITEmitter.cpp - Unit tests for the JIT code emitter ---------------===// +//===- JITTest.cpp - Unit tests for the JIT -------------------------------===// // // The LLVM Compiler Infrastructure // @@ -7,42 +7,184 @@ // //===----------------------------------------------------------------------===// -#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/ADT/OwningPtr.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/Assembly/Parser.h" +#include "llvm/Bitcode/ReaderWriter.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" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constant.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/GlobalValue.h" +#include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/TypeBuilder.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/TargetSelect.h" +#include "gtest/gtest.h" +#include using namespace llvm; namespace { +// Tests on ARM, PowerPC and SystemZ disabled as we're running the old jit +#if !defined(__arm__) && !defined(__powerpc__) && !defined(__s390__) + Function *makeReturnGlobal(std::string Name, GlobalVariable *G, Module *M) { - std::vector params; - const FunctionType *FTy = FunctionType::get(G->getType()->getElementType(), + std::vector params; + FunctionType *FTy = FunctionType::get(G->getType()->getElementType(), params, false); Function *F = Function::Create(FTy, GlobalValue::ExternalLinkage, Name, M); - BasicBlock *Entry = BasicBlock::Create("entry", F); + BasicBlock *Entry = BasicBlock::Create(M->getContext(), "entry", F); IRBuilder<> builder(Entry); Value *Load = builder.CreateLoad(G); - const Type *GTy = G->getType()->getElementType(); + Type *GTy = G->getType()->getElementType(); Value *Add = builder.CreateAdd(Load, ConstantInt::get(GTy, 1LL)); builder.CreateStore(Add, G); builder.CreateRet(Add); return F; } +std::string DumpFunction(const Function *F) { + std::string Result; + raw_string_ostream(Result) << "" << *F; + return Result; +} + +class RecordingJITMemoryManager : public JITMemoryManager { + const OwningPtr Base; +public: + RecordingJITMemoryManager() + : Base(JITMemoryManager::CreateDefaultMemManager()) { + stubsAllocated = 0; + } + virtual void *getPointerToNamedFunction(const std::string &Name, + bool AbortOnFailure = true) { + return Base->getPointerToNamedFunction(Name, AbortOnFailure); + } + + virtual void setMemoryWritable() { Base->setMemoryWritable(); } + virtual void setMemoryExecutable() { Base->setMemoryExecutable(); } + virtual void setPoisonMemory(bool poison) { Base->setPoisonMemory(poison); } + virtual void AllocateGOT() { Base->AllocateGOT(); } + virtual uint8_t *getGOTBase() const { return Base->getGOTBase(); } + struct StartFunctionBodyCall { + StartFunctionBodyCall(uint8_t *Result, const Function *F, + uintptr_t ActualSize, uintptr_t ActualSizeResult) + : Result(Result), F(F), F_dump(DumpFunction(F)), + ActualSize(ActualSize), ActualSizeResult(ActualSizeResult) {} + uint8_t *Result; + const Function *F; + std::string F_dump; + uintptr_t ActualSize; + uintptr_t ActualSizeResult; + }; + std::vector startFunctionBodyCalls; + virtual uint8_t *startFunctionBody(const Function *F, + uintptr_t &ActualSize) { + uintptr_t InitialActualSize = ActualSize; + uint8_t *Result = Base->startFunctionBody(F, ActualSize); + startFunctionBodyCalls.push_back( + StartFunctionBodyCall(Result, F, InitialActualSize, ActualSize)); + return Result; + } + int stubsAllocated; + virtual uint8_t *allocateStub(const GlobalValue* F, unsigned StubSize, + unsigned Alignment) { + stubsAllocated++; + return Base->allocateStub(F, StubSize, Alignment); + } + struct EndFunctionBodyCall { + EndFunctionBodyCall(const Function *F, uint8_t *FunctionStart, + uint8_t *FunctionEnd) + : F(F), F_dump(DumpFunction(F)), + FunctionStart(FunctionStart), FunctionEnd(FunctionEnd) {} + const Function *F; + std::string F_dump; + uint8_t *FunctionStart; + uint8_t *FunctionEnd; + }; + std::vector endFunctionBodyCalls; + virtual void endFunctionBody(const Function *F, uint8_t *FunctionStart, + uint8_t *FunctionEnd) { + endFunctionBodyCalls.push_back( + EndFunctionBodyCall(F, FunctionStart, FunctionEnd)); + Base->endFunctionBody(F, FunctionStart, FunctionEnd); + } + virtual uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment, + unsigned SectionID, bool IsReadOnly) { + return Base->allocateDataSection(Size, Alignment, SectionID, IsReadOnly); + } + virtual uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment, + unsigned SectionID) { + return Base->allocateCodeSection(Size, Alignment, SectionID); + } + virtual bool applyPermissions(std::string *ErrMsg) { return false; } + virtual uint8_t *allocateSpace(intptr_t Size, unsigned Alignment) { + return Base->allocateSpace(Size, Alignment); + } + virtual uint8_t *allocateGlobal(uintptr_t Size, unsigned Alignment) { + return Base->allocateGlobal(Size, Alignment); + } + struct DeallocateFunctionBodyCall { + DeallocateFunctionBodyCall(const void *Body) : Body(Body) {} + const void *Body; + }; + std::vector deallocateFunctionBodyCalls; + virtual void deallocateFunctionBody(void *Body) { + deallocateFunctionBodyCalls.push_back(DeallocateFunctionBodyCall(Body)); + Base->deallocateFunctionBody(Body); + } +}; + +bool LoadAssemblyInto(Module *M, const char *assembly) { + SMDiagnostic Error; + bool success = + NULL != ParseAssemblyString(assembly, M, Error, M->getContext()); + std::string errMsg; + raw_string_ostream os(errMsg); + Error.print("", os); + EXPECT_TRUE(success) << os.str(); + return success; +} + +class JITTest : public testing::Test { + protected: + virtual RecordingJITMemoryManager *createMemoryManager() { + return new RecordingJITMemoryManager; + } + + virtual void SetUp() { + M = new Module("
", Context); + RJMM = createMemoryManager(); + RJMM->setPoisonMemory(true); + std::string Error; + TargetOptions Options; + TheJIT.reset(EngineBuilder(M).setEngineKind(EngineKind::JIT) + .setJITMemoryManager(RJMM) + .setErrorStr(&Error) + .setTargetOptions(Options).create()); + ASSERT_TRUE(TheJIT.get() != NULL) << Error; + } + + void LoadAssembly(const char *assembly) { + LoadAssemblyInto(M, assembly); + } + + LLVMContext Context; + Module *M; // Owned by ExecutionEngine. + RecordingJITMemoryManager *RJMM; + OwningPtr TheJIT; +}; + // 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 @@ -51,29 +193,29 @@ Function *makeReturnGlobal(std::string Name, GlobalVariable *G, Module *M) { TEST(JIT, GlobalInFunction) { LLVMContext context; Module *M = new Module("
", 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 JIT(ExecutionEngine::createJIT( - MP, - &Error, - MemMgr, - CodeGenOpt::Default, - false)); // This last argument enables the fix. + OwningPtr JIT(EngineBuilder(M) + .setEngineKind(EngineKind::JIT) + .setErrorStr(&Error) + .setJITMemoryManager(MemMgr) + // The next line enables the fix: + .setAllocateGVsWithCode(false) + .create()); ASSERT_EQ(Error, ""); // Create a global variable. - const Type *GTy = Type::Int32Ty; + Type *GTy = Type::getInt32Ty(context); GlobalVariable *G = new GlobalVariable( *M, GTy, false, // Not constant. GlobalValue::InternalLinkage, - context.getNullValue(GTy), + Constant::getNullValue(GTy), "myglobal"); // Make a function that points to a global. @@ -81,9 +223,8 @@ TEST(JIT, GlobalInFunction) { // 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); + void (*F1Ptr)() = + reinterpret_cast((intptr_t)JIT->getPointerToFunction(F1)); // Since F1 was codegen'd, a pointer to G should be available. int32_t *GPtr = (int32_t*)JIT->getPointerToGlobalIfAvailable(G); @@ -97,9 +238,8 @@ TEST(JIT, GlobalInFunction) { // 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); + void (*F2Ptr)() = + reinterpret_cast((intptr_t)JIT->getPointerToFunction(F2)); // F2() should increment G. F2Ptr(); @@ -113,11 +253,477 @@ TEST(JIT, GlobalInFunction) { 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. +int PlusOne(int arg) { + return arg + 1; +} + +TEST_F(JITTest, FarCallToKnownFunction) { + // x86-64 can only make direct calls to functions within 32 bits of + // the current PC. To call anything farther away, we have to load + // the address into a register and call through the register. The + // current JIT does this by allocating a stub for any far call. + // There was a bug in which the JIT tried to emit a direct call when + // the target was already in the JIT's global mappings and lazy + // compilation was disabled. + + Function *KnownFunction = Function::Create( + TypeBuilder::get(Context), + GlobalValue::ExternalLinkage, "known", M); + TheJIT->addGlobalMapping(KnownFunction, (void*)(intptr_t)PlusOne); + + // int test() { return known(7); } + Function *TestFunction = Function::Create( + TypeBuilder::get(Context), + GlobalValue::ExternalLinkage, "test", M); + BasicBlock *Entry = BasicBlock::Create(Context, "entry", TestFunction); + IRBuilder<> Builder(Entry); + Value *result = Builder.CreateCall( + KnownFunction, + ConstantInt::get(TypeBuilder::get(Context), 7)); + Builder.CreateRet(result); + + TheJIT->DisableLazyCompilation(true); + int (*TestFunctionPtr)() = reinterpret_cast( + (intptr_t)TheJIT->getPointerToFunction(TestFunction)); + // This used to crash in trying to call PlusOne(). + EXPECT_EQ(8, TestFunctionPtr()); +} + +// Test a function C which calls A and B which call each other. +TEST_F(JITTest, NonLazyCompilationStillNeedsStubs) { + TheJIT->DisableLazyCompilation(true); + + FunctionType *Func1Ty = + cast(TypeBuilder::get(Context)); + std::vector arg_types; + arg_types.push_back(Type::getInt1Ty(Context)); + FunctionType *FuncTy = FunctionType::get( + Type::getVoidTy(Context), arg_types, false); + Function *Func1 = Function::Create(Func1Ty, Function::ExternalLinkage, + "func1", M); + Function *Func2 = Function::Create(FuncTy, Function::InternalLinkage, + "func2", M); + Function *Func3 = Function::Create(FuncTy, Function::InternalLinkage, + "func3", M); + BasicBlock *Block1 = BasicBlock::Create(Context, "block1", Func1); + BasicBlock *Block2 = BasicBlock::Create(Context, "block2", Func2); + BasicBlock *True2 = BasicBlock::Create(Context, "cond_true", Func2); + BasicBlock *False2 = BasicBlock::Create(Context, "cond_false", Func2); + BasicBlock *Block3 = BasicBlock::Create(Context, "block3", Func3); + BasicBlock *True3 = BasicBlock::Create(Context, "cond_true", Func3); + BasicBlock *False3 = BasicBlock::Create(Context, "cond_false", Func3); + + // Make Func1 call Func2(0) and Func3(0). + IRBuilder<> Builder(Block1); + Builder.CreateCall(Func2, ConstantInt::getTrue(Context)); + Builder.CreateCall(Func3, ConstantInt::getTrue(Context)); + Builder.CreateRetVoid(); + + // void Func2(bool b) { if (b) { Func3(false); return; } return; } + Builder.SetInsertPoint(Block2); + Builder.CreateCondBr(Func2->arg_begin(), True2, False2); + Builder.SetInsertPoint(True2); + Builder.CreateCall(Func3, ConstantInt::getFalse(Context)); + Builder.CreateRetVoid(); + Builder.SetInsertPoint(False2); + Builder.CreateRetVoid(); + + // void Func3(bool b) { if (b) { Func2(false); return; } return; } + Builder.SetInsertPoint(Block3); + Builder.CreateCondBr(Func3->arg_begin(), True3, False3); + Builder.SetInsertPoint(True3); + Builder.CreateCall(Func2, ConstantInt::getFalse(Context)); + Builder.CreateRetVoid(); + Builder.SetInsertPoint(False3); + Builder.CreateRetVoid(); + + // Compile the function to native code + void (*F1Ptr)() = + reinterpret_cast((intptr_t)TheJIT->getPointerToFunction(Func1)); + + F1Ptr(); +} + +// Regression test for PR5162. This used to trigger an AssertingVH inside the +// JIT's Function to stub mapping. +TEST_F(JITTest, NonLazyLeaksNoStubs) { + TheJIT->DisableLazyCompilation(true); + + // Create two functions with a single basic block each. + FunctionType *FuncTy = + cast(TypeBuilder::get(Context)); + Function *Func1 = Function::Create(FuncTy, Function::ExternalLinkage, + "func1", M); + Function *Func2 = Function::Create(FuncTy, Function::InternalLinkage, + "func2", M); + BasicBlock *Block1 = BasicBlock::Create(Context, "block1", Func1); + BasicBlock *Block2 = BasicBlock::Create(Context, "block2", Func2); + + // The first function calls the second and returns the result + IRBuilder<> Builder(Block1); + Value *Result = Builder.CreateCall(Func2); + Builder.CreateRet(Result); + + // The second function just returns a constant + Builder.SetInsertPoint(Block2); + Builder.CreateRet(ConstantInt::get(TypeBuilder::get(Context),42)); + + // Compile the function to native code + (void)TheJIT->getPointerToFunction(Func1); + + // Free the JIT state for the functions + TheJIT->freeMachineCodeForFunction(Func1); + TheJIT->freeMachineCodeForFunction(Func2); + + // Delete the first function (and show that is has no users) + EXPECT_EQ(Func1->getNumUses(), 0u); + Func1->eraseFromParent(); + + // Delete the second function (and show that it has no users - it had one, + // func1 but that's gone now) + EXPECT_EQ(Func2->getNumUses(), 0u); + Func2->eraseFromParent(); +} + +TEST_F(JITTest, ModuleDeletion) { + TheJIT->DisableLazyCompilation(false); + LoadAssembly("define void @main() { " + " call i32 @computeVal() " + " ret void " + "} " + " " + "define internal i32 @computeVal() { " + " ret i32 0 " + "} "); + Function *func = M->getFunction("main"); + TheJIT->getPointerToFunction(func); + TheJIT->removeModule(M); + delete M; + + SmallPtrSet FunctionsDeallocated; + for (unsigned i = 0, e = RJMM->deallocateFunctionBodyCalls.size(); + i != e; ++i) { + FunctionsDeallocated.insert(RJMM->deallocateFunctionBodyCalls[i].Body); + } + for (unsigned i = 0, e = RJMM->startFunctionBodyCalls.size(); i != e; ++i) { + EXPECT_TRUE(FunctionsDeallocated.count( + RJMM->startFunctionBodyCalls[i].Result)) + << "Function leaked: \n" << RJMM->startFunctionBodyCalls[i].F_dump; + } + EXPECT_EQ(RJMM->startFunctionBodyCalls.size(), + RJMM->deallocateFunctionBodyCalls.size()); +} + +// ARM, MIPS and PPC still emit stubs for calls since the target may be +// too far away to call directly. This #if can probably be removed when +// http://llvm.org/PR5201 is fixed. +#if !defined(__arm__) && !defined(__mips__) && \ + !defined(__powerpc__) && !defined(__ppc__) +typedef int (*FooPtr) (); + +TEST_F(JITTest, NoStubs) { + LoadAssembly("define void @bar() {" + "entry: " + "ret void" + "}" + " " + "define i32 @foo() {" + "entry:" + "call void @bar()" + "ret i32 undef" + "}" + " " + "define i32 @main() {" + "entry:" + "%0 = call i32 @foo()" + "call void @bar()" + "ret i32 undef" + "}"); + Function *foo = M->getFunction("foo"); + uintptr_t tmp = (uintptr_t)(TheJIT->getPointerToFunction(foo)); + FooPtr ptr = (FooPtr)(tmp); + + (ptr)(); + + // We should now allocate no more stubs, we have the code to foo + // and the existing stub for bar. + int stubsBefore = RJMM->stubsAllocated; + Function *func = M->getFunction("main"); + TheJIT->getPointerToFunction(func); + + Function *bar = M->getFunction("bar"); + TheJIT->getPointerToFunction(bar); + + ASSERT_EQ(stubsBefore, RJMM->stubsAllocated); +} +#endif // !ARM && !PPC + +TEST_F(JITTest, FunctionPointersOutliveTheirCreator) { + TheJIT->DisableLazyCompilation(true); + LoadAssembly("define i8()* @get_foo_addr() { " + " ret i8()* @foo " + "} " + " " + "define i8 @foo() { " + " ret i8 42 " + "} "); + Function *F_get_foo_addr = M->getFunction("get_foo_addr"); + + typedef char(*fooT)(); + fooT (*get_foo_addr)() = reinterpret_cast( + (intptr_t)TheJIT->getPointerToFunction(F_get_foo_addr)); + fooT foo_addr = get_foo_addr(); + + // Now free get_foo_addr. This should not free the machine code for foo or + // any call stub returned as foo's canonical address. + TheJIT->freeMachineCodeForFunction(F_get_foo_addr); + + // Check by calling the reported address of foo. + EXPECT_EQ(42, foo_addr()); + + // The reported address should also be the same as the result of a subsequent + // getPointerToFunction(foo). +#if 0 + // Fails until PR5126 is fixed: + Function *F_foo = M->getFunction("foo"); + fooT foo = reinterpret_cast( + (intptr_t)TheJIT->getPointerToFunction(F_foo)); + EXPECT_EQ((intptr_t)foo, (intptr_t)foo_addr); +#endif +} + +// ARM does not have an implementation of replaceMachineCodeForFunction(), +// so recompileAndRelinkFunction doesn't work. +#if !defined(__arm__) +TEST_F(JITTest, FunctionIsRecompiledAndRelinked) { + Function *F = Function::Create(TypeBuilder::get(Context), + GlobalValue::ExternalLinkage, "test", M); + BasicBlock *Entry = BasicBlock::Create(Context, "entry", F); + IRBuilder<> Builder(Entry); + Value *Val = ConstantInt::get(TypeBuilder::get(Context), 1); + Builder.CreateRet(Val); + + TheJIT->DisableLazyCompilation(true); + // Compile the function once, and make sure it works. + int (*OrigFPtr)() = reinterpret_cast( + (intptr_t)TheJIT->recompileAndRelinkFunction(F)); + EXPECT_EQ(1, OrigFPtr()); + + // Now change the function to return a different value. + Entry->eraseFromParent(); + BasicBlock *NewEntry = BasicBlock::Create(Context, "new_entry", F); + Builder.SetInsertPoint(NewEntry); + Val = ConstantInt::get(TypeBuilder::get(Context), 2); + Builder.CreateRet(Val); + // Recompile it, which should produce a new function pointer _and_ update the + // old one. + int (*NewFPtr)() = reinterpret_cast( + (intptr_t)TheJIT->recompileAndRelinkFunction(F)); + + EXPECT_EQ(2, NewFPtr()) + << "The new pointer should call the new version of the function"; + EXPECT_EQ(2, OrigFPtr()) + << "The old pointer's target should now jump to the new version"; +} +#endif // !defined(__arm__) + +} // anonymous namespace +// This variable is intentionally defined differently in the statically-compiled +// program from the IR input to the JIT to assert that the JIT doesn't use its +// definition. +extern "C" int32_t JITTest_AvailableExternallyGlobal; +int32_t JITTest_AvailableExternallyGlobal LLVM_ATTRIBUTE_USED = 42; +namespace { + +TEST_F(JITTest, AvailableExternallyGlobalIsntEmitted) { + TheJIT->DisableLazyCompilation(true); + LoadAssembly("@JITTest_AvailableExternallyGlobal = " + " available_externally global i32 7 " + " " + "define i32 @loader() { " + " %result = load i32* @JITTest_AvailableExternallyGlobal " + " ret i32 %result " + "} "); + Function *loaderIR = M->getFunction("loader"); + + int32_t (*loader)() = reinterpret_cast( + (intptr_t)TheJIT->getPointerToFunction(loaderIR)); + EXPECT_EQ(42, loader()) << "func should return 42 from the external global," + << " not 7 from the IR version."; +} +} // anonymous namespace +// This function is intentionally defined differently in the statically-compiled +// program from the IR input to the JIT to assert that the JIT doesn't use its +// definition. +extern "C" int32_t JITTest_AvailableExternallyFunction() LLVM_ATTRIBUTE_USED; +extern "C" int32_t JITTest_AvailableExternallyFunction() { + return 42; +} +namespace { + +TEST_F(JITTest, AvailableExternallyFunctionIsntCompiled) { + TheJIT->DisableLazyCompilation(true); + LoadAssembly("define available_externally i32 " + " @JITTest_AvailableExternallyFunction() { " + " ret i32 7 " + "} " + " " + "define i32 @func() { " + " %result = tail call i32 " + " @JITTest_AvailableExternallyFunction() " + " ret i32 %result " + "} "); + Function *funcIR = M->getFunction("func"); + + int32_t (*func)() = reinterpret_cast( + (intptr_t)TheJIT->getPointerToFunction(funcIR)); + EXPECT_EQ(42, func()) << "func should return 42 from the static version," + << " not 7 from the IR version."; +} + +TEST_F(JITTest, EscapedLazyStubStillCallable) { + TheJIT->DisableLazyCompilation(false); + LoadAssembly("define internal i32 @stubbed() { " + " ret i32 42 " + "} " + " " + "define i32()* @get_stub() { " + " ret i32()* @stubbed " + "} "); + typedef int32_t(*StubTy)(); + + // Call get_stub() to get the address of @stubbed without actually JITting it. + Function *get_stubIR = M->getFunction("get_stub"); + StubTy (*get_stub)() = reinterpret_cast( + (intptr_t)TheJIT->getPointerToFunction(get_stubIR)); + StubTy stubbed = get_stub(); + // Now get_stubIR is the only reference to stubbed's stub. + get_stubIR->eraseFromParent(); + // Now there are no references inside the JIT, but we've got a pointer outside + // it. The stub should be callable and return the right value. + EXPECT_EQ(42, stubbed()); +} + +// Converts the LLVM assembly to bitcode and returns it in a std::string. An +// empty string indicates an error. +std::string AssembleToBitcode(LLVMContext &Context, const char *Assembly) { + Module TempModule("TempModule", Context); + if (!LoadAssemblyInto(&TempModule, Assembly)) { + return ""; + } + + std::string Result; + raw_string_ostream OS(Result); + WriteBitcodeToFile(&TempModule, OS); + OS.flush(); + return Result; +} + +// Returns a newly-created ExecutionEngine that reads the bitcode in 'Bitcode' +// lazily. The associated Module (owned by the ExecutionEngine) is returned in +// M. Both will be NULL on an error. Bitcode must live at least as long as the +// ExecutionEngine. +ExecutionEngine *getJITFromBitcode( + LLVMContext &Context, const std::string &Bitcode, Module *&M) { + // c_str() is null-terminated like MemoryBuffer::getMemBuffer requires. + MemoryBuffer *BitcodeBuffer = + MemoryBuffer::getMemBuffer(Bitcode, "Bitcode for test"); + std::string errMsg; + M = getLazyBitcodeModule(BitcodeBuffer, Context, &errMsg); + if (M == NULL) { + ADD_FAILURE() << errMsg; + delete BitcodeBuffer; + return NULL; + } + ExecutionEngine *TheJIT = EngineBuilder(M) + .setEngineKind(EngineKind::JIT) + .setErrorStr(&errMsg) + .create(); + if (TheJIT == NULL) { + ADD_FAILURE() << errMsg; + delete M; + M = NULL; + return NULL; + } + return TheJIT; +} + +TEST(LazyLoadedJITTest, MaterializableAvailableExternallyFunctionIsntCompiled) { + LLVMContext Context; + const std::string Bitcode = + AssembleToBitcode(Context, + "define available_externally i32 " + " @JITTest_AvailableExternallyFunction() { " + " ret i32 7 " + "} " + " " + "define i32 @func() { " + " %result = tail call i32 " + " @JITTest_AvailableExternallyFunction() " + " ret i32 %result " + "} "); + ASSERT_FALSE(Bitcode.empty()) << "Assembling failed"; + Module *M; + OwningPtr TheJIT(getJITFromBitcode(Context, Bitcode, M)); + ASSERT_TRUE(TheJIT.get()) << "Failed to create JIT."; + TheJIT->DisableLazyCompilation(true); + + Function *funcIR = M->getFunction("func"); + Function *availableFunctionIR = + M->getFunction("JITTest_AvailableExternallyFunction"); + + // Double-check that the available_externally function is still unmaterialized + // when getPointerToFunction needs to find out if it's available_externally. + EXPECT_TRUE(availableFunctionIR->isMaterializable()); + + int32_t (*func)() = reinterpret_cast( + (intptr_t)TheJIT->getPointerToFunction(funcIR)); + EXPECT_EQ(42, func()) << "func should return 42 from the static version," + << " not 7 from the IR version."; +} + +TEST(LazyLoadedJITTest, EagerCompiledRecursionThroughGhost) { + LLVMContext Context; + const std::string Bitcode = + AssembleToBitcode(Context, + "define i32 @recur1(i32 %a) { " + " %zero = icmp eq i32 %a, 0 " + " br i1 %zero, label %done, label %notdone " + "done: " + " ret i32 3 " + "notdone: " + " %am1 = sub i32 %a, 1 " + " %result = call i32 @recur2(i32 %am1) " + " ret i32 %result " + "} " + " " + "define i32 @recur2(i32 %b) { " + " %result = call i32 @recur1(i32 %b) " + " ret i32 %result " + "} "); + ASSERT_FALSE(Bitcode.empty()) << "Assembling failed"; + Module *M; + OwningPtr TheJIT(getJITFromBitcode(Context, Bitcode, M)); + ASSERT_TRUE(TheJIT.get()) << "Failed to create JIT."; + TheJIT->DisableLazyCompilation(true); + + Function *recur1IR = M->getFunction("recur1"); + Function *recur2IR = M->getFunction("recur2"); + EXPECT_TRUE(recur1IR->isMaterializable()); + EXPECT_TRUE(recur2IR->isMaterializable()); + + int32_t (*recur1)(int32_t) = reinterpret_cast( + (intptr_t)TheJIT->getPointerToFunction(recur1IR)); + EXPECT_EQ(3, recur1(4)); +} +#endif // !defined(__arm__) && !defined(__powerpc__) && !defined(__s390__) + +// This code is copied from JITEventListenerTest, but it only runs once for all +// the tests in this directory. Everything seems fine, but that's strange +// behavior. class JITEnvironment : public testing::Environment { virtual void SetUp() { - // Required for ExecutionEngine::createJIT to create a JIT. + // Required to create a JIT. InitializeNativeTarget(); } };