X-Git-Url: http://plrg.eecs.uci.edu/git/?p=oota-llvm.git;a=blobdiff_plain;f=unittests%2FExecutionEngine%2FJIT%2FJITTest.cpp;h=817d207c2dca9d49afe5936779d6e05a59d446dd;hp=bbf34603872d62a5759586952f6a0810cdb4969b;hb=7ad7c75048eb32f95eae04847908793e0183802f;hpb=898e9df8dbc65b9b7b92c35ac6673ac8651ff3e8 diff --git a/unittests/ExecutionEngine/JIT/JITTest.cpp b/unittests/ExecutionEngine/JIT/JITTest.cpp index bbf34603872..817d207c2dc 100644 --- a/unittests/ExecutionEngine/JIT/JITTest.cpp +++ b/unittests/ExecutionEngine/JIT/JITTest.cpp @@ -7,43 +7,62 @@ // //===----------------------------------------------------------------------===// -#include "gtest/gtest.h" -#include "llvm/ADT/OwningPtr.h" -#include "llvm/ADT/SmallPtrSet.h" -#include "llvm/Assembly/Parser.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/SmallPtrSet.h" +#include "llvm/AsmParser/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/LLVMContext.h" -#include "llvm/Module.h" -#include "llvm/ModuleProvider.h" -#include "llvm/Support/IRBuilder.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/TypeBuilder.h" -#include "llvm/Target/TargetSelect.h" -#include "llvm/Type.h" - +#include "llvm/Support/TargetSelect.h" +#include "gtest/gtest.h" #include using namespace llvm; +// 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. Note that this variable must be defined even on platforms where +// JIT tests are disabled as it is referenced from the .def file. +extern "C" int32_t JITTest_AvailableExternallyGlobal; +int32_t JITTest_AvailableExternallyGlobal LLVM_ATTRIBUTE_USED = 42; + +// 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. Note that this function must be defined even on platforms where +// JIT tests are disabled as it is referenced from the .def file. +extern "C" int32_t JITTest_AvailableExternallyFunction() LLVM_ATTRIBUTE_USED; +extern "C" int32_t JITTest_AvailableExternallyFunction() { + return 42; +} + namespace { +// Tests on ARM, PowerPC and SystemZ disabled as we're running the old jit +#if !defined(__arm__) && !defined(__powerpc__) && !defined(__s390__) \ + && !defined(__aarch64__) + 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(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); @@ -57,12 +76,17 @@ std::string DumpFunction(const Function *F) { } class RecordingJITMemoryManager : public JITMemoryManager { - const OwningPtr Base; + const std::unique_ptr 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(); } @@ -90,8 +114,8 @@ public: return Result; } int stubsAllocated; - virtual uint8_t *allocateStub(const GlobalValue* F, unsigned StubSize, - unsigned Alignment) { + uint8_t *allocateStub(const GlobalValue *F, unsigned StubSize, + unsigned Alignment) override { stubsAllocated++; return Base->allocateStub(F, StubSize, Alignment); } @@ -112,6 +136,19 @@ public: EndFunctionBodyCall(F, FunctionStart, FunctionEnd)); Base->endFunctionBody(F, FunctionStart, FunctionEnd); } + virtual uint8_t *allocateDataSection( + uintptr_t Size, unsigned Alignment, unsigned SectionID, + StringRef SectionName, bool IsReadOnly) { + return Base->allocateDataSection( + Size, Alignment, SectionID, SectionName, IsReadOnly); + } + virtual uint8_t *allocateCodeSection( + uintptr_t Size, unsigned Alignment, unsigned SectionID, + StringRef SectionName) { + return Base->allocateCodeSection( + Size, Alignment, SectionID, SectionName); + } + virtual bool finalizeMemory(std::string *ErrMsg) { return false; } virtual uint8_t *allocateSpace(intptr_t Size, unsigned Alignment) { return Base->allocateSpace(Size, Alignment); } @@ -127,84 +164,46 @@ public: deallocateFunctionBodyCalls.push_back(DeallocateFunctionBodyCall(Body)); Base->deallocateFunctionBody(Body); } - struct DeallocateExceptionTableCall { - DeallocateExceptionTableCall(const void *ET) : ET(ET) {} - const void *ET; - }; - std::vector deallocateExceptionTableCalls; - virtual void deallocateExceptionTable(void *ET) { - deallocateExceptionTableCalls.push_back(DeallocateExceptionTableCall(ET)); - Base->deallocateExceptionTable(ET); - } - struct StartExceptionTableCall { - StartExceptionTableCall(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 startExceptionTableCalls; - virtual uint8_t* startExceptionTable(const Function* F, - uintptr_t &ActualSize) { - uintptr_t InitialActualSize = ActualSize; - uint8_t *Result = Base->startExceptionTable(F, ActualSize); - startExceptionTableCalls.push_back( - StartExceptionTableCall(Result, F, InitialActualSize, ActualSize)); - return Result; - } - struct EndExceptionTableCall { - EndExceptionTableCall(const Function *F, uint8_t *TableStart, - uint8_t *TableEnd, uint8_t* FrameRegister) - : F(F), F_dump(DumpFunction(F)), - TableStart(TableStart), TableEnd(TableEnd), - FrameRegister(FrameRegister) {} - const Function *F; - std::string F_dump; - uint8_t *TableStart; - uint8_t *TableEnd; - uint8_t *FrameRegister; - }; - std::vector endExceptionTableCalls; - virtual void endExceptionTable(const Function *F, uint8_t *TableStart, - uint8_t *TableEnd, uint8_t* FrameRegister) { - endExceptionTableCalls.push_back( - EndExceptionTableCall(F, TableStart, TableEnd, FrameRegister)); - return Base->endExceptionTable(F, TableStart, TableEnd, FrameRegister); - } }; +bool LoadAssemblyInto(Module *M, const char *assembly) { + SMDiagnostic Error; + bool success = + nullptr != 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); - MP = new ExistingModuleProvider(M); - RJMM = new RecordingJITMemoryManager; + RJMM = createMemoryManager(); RJMM->setPoisonMemory(true); std::string Error; - TheJIT.reset(EngineBuilder(MP).setEngineKind(EngineKind::JIT) + TargetOptions Options; + TheJIT.reset(EngineBuilder(M).setEngineKind(EngineKind::JIT) .setJITMemoryManager(RJMM) - .setErrorStr(&Error).create()); - ASSERT_TRUE(TheJIT.get() != NULL) << Error; + .setErrorStr(&Error) + .setTargetOptions(Options).create()); + ASSERT_TRUE(TheJIT.get() != nullptr) << Error; } void LoadAssembly(const char *assembly) { - SMDiagnostic Error; - bool success = NULL != ParseAssemblyString(assembly, M, Error, Context); - std::string errMsg; - raw_string_ostream os(errMsg); - Error.Print("", os); - ASSERT_TRUE(success) << os.str(); + LoadAssemblyInto(M, assembly); } LLVMContext Context; - Module *M; // Owned by MP. - ModuleProvider *MP; // Owned by ExecutionEngine. + Module *M; // Owned by ExecutionEngine. RecordingJITMemoryManager *RJMM; - OwningPtr TheJIT; + std::unique_ptr TheJIT; }; // Regression test for a bug. The JIT used to allocate globals inside the same @@ -215,24 +214,23 @@ class JITTest : public testing::Test { 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(EngineBuilder(MP) - .setEngineKind(EngineKind::JIT) - .setErrorStr(&Error) - .setJITMemoryManager(MemMgr) - // The next line enables the fix: - .setAllocateGVsWithCode(false) - .create()); + std::unique_ptr 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::getInt32Ty(context); + Type *GTy = Type::getInt32Ty(context); GlobalVariable *G = new GlobalVariable( *M, GTy, @@ -251,7 +249,7 @@ TEST(JIT, GlobalInFunction) { // 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); + ASSERT_NE((int32_t*)nullptr, GPtr); EXPECT_EQ(0, *GPtr); // F1() should increment G. @@ -316,11 +314,11 @@ TEST_F(JITTest, FarCallToKnownFunction) { TEST_F(JITTest, NonLazyCompilationStillNeedsStubs) { TheJIT->DisableLazyCompilation(true); - const FunctionType *Func1Ty = + FunctionType *Func1Ty = cast(TypeBuilder::get(Context)); - std::vector arg_types; + std::vector arg_types; arg_types.push_back(Type::getInt1Ty(Context)); - const FunctionType *FuncTy = FunctionType::get( + FunctionType *FuncTy = FunctionType::get( Type::getVoidTy(Context), arg_types, false); Function *Func1 = Function::Create(Func1Ty, Function::ExternalLinkage, "func1", M); @@ -373,7 +371,7 @@ TEST_F(JITTest, NonLazyLeaksNoStubs) { TheJIT->DisableLazyCompilation(true); // Create two functions with a single basic block each. - const FunctionType *FuncTy = + FunctionType *FuncTy = cast(TypeBuilder::get(Context)); Function *Func1 = Function::Create(FuncTy, Function::ExternalLinkage, "func1", M); @@ -420,7 +418,8 @@ TEST_F(JITTest, ModuleDeletion) { "} "); Function *func = M->getFunction("main"); TheJIT->getPointerToFunction(func); - TheJIT->deleteModuleProvider(MP); + TheJIT->removeModule(M); + delete M; SmallPtrSet FunctionsDeallocated; for (unsigned i = 0, e = RJMM->deallocateFunctionBodyCalls.size(); @@ -434,33 +433,13 @@ TEST_F(JITTest, ModuleDeletion) { } EXPECT_EQ(RJMM->startFunctionBodyCalls.size(), RJMM->deallocateFunctionBodyCalls.size()); - - SmallPtrSet ExceptionTablesDeallocated; - unsigned NumTablesDeallocated = 0; - for (unsigned i = 0, e = RJMM->deallocateExceptionTableCalls.size(); - i != e; ++i) { - ExceptionTablesDeallocated.insert( - RJMM->deallocateExceptionTableCalls[i].ET); - if (RJMM->deallocateExceptionTableCalls[i].ET != NULL) { - // If JITEmitDebugInfo is off, we'll "deallocate" NULL, which doesn't - // appear in startExceptionTableCalls. - NumTablesDeallocated++; - } - } - for (unsigned i = 0, e = RJMM->startExceptionTableCalls.size(); i != e; ++i) { - EXPECT_TRUE(ExceptionTablesDeallocated.count( - RJMM->startExceptionTableCalls[i].Result)) - << "Function's exception table leaked: \n" - << RJMM->startExceptionTableCalls[i].F_dump; - } - EXPECT_EQ(RJMM->startExceptionTableCalls.size(), - NumTablesDeallocated); } -// ARM 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 +// 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(__powerpc__) && !defined(__ppc__) +#if !defined(__arm__) && !defined(__mips__) && \ + !defined(__powerpc__) && !defined(__ppc__) && !defined(__aarch64__) typedef int (*FooPtr) (); TEST_F(JITTest, NoStubs) { @@ -534,13 +513,40 @@ TEST_F(JITTest, FunctionPointersOutliveTheirCreator) { #endif } -} // 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 = 42; -namespace { +// ARM does not have an implementation of replaceMachineCodeForFunction(), +// so recompileAndRelinkFunction doesn't work. +#if !defined(__arm__) && !defined(__aarch64__) +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__) TEST_F(JITTest, AvailableExternallyGlobalIsntEmitted) { TheJIT->DisableLazyCompilation(true); @@ -559,16 +565,164 @@ TEST_F(JITTest, AvailableExternallyGlobalIsntEmitted) { << " not 7 from the IR version."; } -// 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 to create a JIT. - InitializeNativeTarget(); +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 ""; } -}; -testing::Environment* const jit_env = - testing::AddGlobalTestEnvironment(new JITEnvironment); + + 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"); + ErrorOr ModuleOrErr = getLazyBitcodeModule(BitcodeBuffer, Context); + if (std::error_code EC = ModuleOrErr.getError()) { + ADD_FAILURE() << EC.message(); + delete BitcodeBuffer; + return nullptr; + } + M = ModuleOrErr.get(); + std::string errMsg; + ExecutionEngine *TheJIT = EngineBuilder(M) + .setEngineKind(EngineKind::JIT) + .setErrorStr(&errMsg) + .create(); + if (TheJIT == nullptr) { + ADD_FAILURE() << errMsg; + delete M; + M = nullptr; + return nullptr; + } + 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; + std::unique_ptr 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; + std::unique_ptr 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__) }