diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2013-12-22 00:04:03 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2013-12-22 00:04:03 +0000 |
commit | f8af5cf600354830d4ccf59732403f0f073eccb9 (patch) | |
tree | 2ba0398b4c42ad4f55561327538044fd2c925a8b /unittests/ExecutionEngine | |
parent | 59d6cff90eecf31cb3dd860c4e786674cfdd42eb (diff) |
Diffstat (limited to 'unittests/ExecutionEngine')
-rw-r--r-- | unittests/ExecutionEngine/JIT/JITMemoryManagerTest.cpp | 23 | ||||
-rw-r--r-- | unittests/ExecutionEngine/JIT/JITTest.cpp | 173 | ||||
-rw-r--r-- | unittests/ExecutionEngine/MCJIT/CMakeLists.txt | 2 | ||||
-rw-r--r-- | unittests/ExecutionEngine/MCJIT/MCJITCAPITest.cpp | 175 | ||||
-rw-r--r-- | unittests/ExecutionEngine/MCJIT/MCJITMemoryManagerTest.cpp | 344 | ||||
-rw-r--r-- | unittests/ExecutionEngine/MCJIT/MCJITMultipleModuleTest.cpp | 395 | ||||
-rw-r--r-- | unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp | 24 | ||||
-rw-r--r-- | unittests/ExecutionEngine/MCJIT/MCJITTest.cpp | 113 | ||||
-rw-r--r-- | unittests/ExecutionEngine/MCJIT/MCJITTestAPICommon.h | 20 | ||||
-rw-r--r-- | unittests/ExecutionEngine/MCJIT/MCJITTestBase.h | 217 | ||||
-rw-r--r-- | unittests/ExecutionEngine/MCJIT/Makefile | 2 |
11 files changed, 1006 insertions, 482 deletions
diff --git a/unittests/ExecutionEngine/JIT/JITMemoryManagerTest.cpp b/unittests/ExecutionEngine/JIT/JITMemoryManagerTest.cpp index 21ca0d448ced3..731f7807f5934 100644 --- a/unittests/ExecutionEngine/JIT/JITMemoryManagerTest.cpp +++ b/unittests/ExecutionEngine/JIT/JITMemoryManagerTest.cpp @@ -277,4 +277,27 @@ TEST(JITMemoryManagerTest, TestManyStubs) { EXPECT_EQ(3U, MemMgr->GetNumStubSlabs()); } +// Check section allocation and alignment +TEST(JITMemoryManagerTest, AllocateSection) { + OwningPtr<JITMemoryManager> MemMgr( + JITMemoryManager::CreateDefaultMemManager()); + uint8_t *code1 = MemMgr->allocateCodeSection(256, 0, 1, StringRef()); + uint8_t *data1 = MemMgr->allocateDataSection(256, 16, 2, StringRef(), true); + uint8_t *code2 = MemMgr->allocateCodeSection(257, 32, 3, StringRef()); + uint8_t *data2 = MemMgr->allocateDataSection(256, 64, 4, StringRef(), false); + uint8_t *code3 = MemMgr->allocateCodeSection(258, 64, 5, StringRef()); + + EXPECT_NE((uint8_t*)0, code1); + EXPECT_NE((uint8_t*)0, code2); + EXPECT_NE((uint8_t*)0, data1); + EXPECT_NE((uint8_t*)0, data2); + + // Check alignment + EXPECT_EQ((uint64_t)code1 & 0xf, 0u); + EXPECT_EQ((uint64_t)code2 & 0x1f, 0u); + EXPECT_EQ((uint64_t)code3 & 0x3f, 0u); + EXPECT_EQ((uint64_t)data1 & 0xf, 0u); + EXPECT_EQ((uint64_t)data2 & 0x3f, 0u); +} + } diff --git a/unittests/ExecutionEngine/JIT/JITTest.cpp b/unittests/ExecutionEngine/JIT/JITTest.cpp index e6f4cb9af25f5..4c7b1e23195d9 100644 --- a/unittests/ExecutionEngine/JIT/JITTest.cpp +++ b/unittests/ExecutionEngine/JIT/JITTest.cpp @@ -33,6 +33,22 @@ 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 @@ -119,15 +135,19 @@ public: 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 *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) { - return Base->allocateCodeSection(Size, Alignment, SectionID); + virtual uint8_t *allocateCodeSection( + uintptr_t Size, unsigned Alignment, unsigned SectionID, + StringRef SectionName) { + return Base->allocateCodeSection( + Size, Alignment, SectionID, SectionName); } - virtual bool applyPermissions(std::string *ErrMsg) { return false; } + virtual bool finalizeMemory(std::string *ErrMsg) { return false; } virtual uint8_t *allocateSpace(intptr_t Size, unsigned Alignment) { return Base->allocateSpace(Size, Alignment); } @@ -143,54 +163,6 @@ public: deallocateFunctionBodyCalls.push_back(DeallocateFunctionBodyCall(Body)); Base->deallocateFunctionBody(Body); } - struct DeallocateExceptionTableCall { - DeallocateExceptionTableCall(const void *ET) : ET(ET) {} - const void *ET; - }; - std::vector<DeallocateExceptionTableCall> 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<StartExceptionTableCall> 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<EndExceptionTableCall> 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) { @@ -216,7 +188,6 @@ class JITTest : public testing::Test { RJMM->setPoisonMemory(true); std::string Error; TargetOptions Options; - Options.JITExceptionHandling = true; TheJIT.reset(EngineBuilder(M).setEngineKind(EngineKind::JIT) .setJITMemoryManager(RJMM) .setErrorStr(&Error) @@ -302,46 +273,6 @@ TEST(JIT, GlobalInFunction) { EXPECT_EQ(3, *GPtr); } -// Regression test for a bug. The JITEmitter wasn't checking to verify that -// it hadn't run out of space while generating the DWARF exception information -// for an emitted function. - -class ExceptionMemoryManagerMock : public RecordingJITMemoryManager { - public: - virtual uint8_t *startExceptionTable(const Function *F, - uintptr_t &ActualSize) { - // force an insufficient size the first time through. - bool ChangeActualSize = false; - if (ActualSize == 0) - ChangeActualSize = true;; - uint8_t *result = - RecordingJITMemoryManager::startExceptionTable(F, ActualSize); - if (ChangeActualSize) - ActualSize = 1; - return result; - } -}; - -class JITExceptionMemoryTest : public JITTest { - protected: - virtual RecordingJITMemoryManager *createMemoryManager() { - return new ExceptionMemoryManagerMock; - } -}; - -TEST_F(JITExceptionMemoryTest, ExceptionTableOverflow) { - Function *F = Function::Create(TypeBuilder<void(void), false>::get(Context), - Function::ExternalLinkage, - "func1", M); - BasicBlock *Block = BasicBlock::Create(Context, "block", F); - IRBuilder<> Builder(Block); - Builder.CreateRetVoid(); - TheJIT->getPointerToFunction(F); - ASSERT_TRUE(RJMM->startExceptionTableCalls.size() == 2); - ASSERT_TRUE(RJMM->deallocateExceptionTableCalls.size() == 1); - ASSERT_TRUE(RJMM->endExceptionTableCalls.size() == 1); -} - int PlusOne(int arg) { return arg + 1; } @@ -501,27 +432,6 @@ TEST_F(JITTest, ModuleDeletion) { } EXPECT_EQ(RJMM->startFunctionBodyCalls.size(), RJMM->deallocateFunctionBodyCalls.size()); - - SmallPtrSet<const void*, 2> 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, MIPS and PPC still emit stubs for calls since the target may be @@ -637,14 +547,6 @@ TEST_F(JITTest, FunctionIsRecompiledAndRelinked) { } #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 = " @@ -661,15 +563,6 @@ TEST_F(JITTest, AvailableExternallyGlobalIsntEmitted) { 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); @@ -828,16 +721,4 @@ TEST(LazyLoadedJITTest, EagerCompiledRecursionThroughGhost) { } #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 to create a JIT. - InitializeNativeTarget(); - } -}; -testing::Environment* const jit_env = - testing::AddGlobalTestEnvironment(new JITEnvironment); - } diff --git a/unittests/ExecutionEngine/MCJIT/CMakeLists.txt b/unittests/ExecutionEngine/MCJIT/CMakeLists.txt index 922cb7efd5eb6..ed4309919387d 100644 --- a/unittests/ExecutionEngine/MCJIT/CMakeLists.txt +++ b/unittests/ExecutionEngine/MCJIT/CMakeLists.txt @@ -2,7 +2,6 @@ set(LLVM_LINK_COMPONENTS asmparser bitreader bitwriter - jit mcjit nativecodegen ) @@ -11,6 +10,7 @@ set(MCJITTestsSources MCJITTest.cpp MCJITCAPITest.cpp MCJITMemoryManagerTest.cpp + MCJITMultipleModuleTest.cpp MCJITObjectCacheTest.cpp ) diff --git a/unittests/ExecutionEngine/MCJIT/MCJITCAPITest.cpp b/unittests/ExecutionEngine/MCJIT/MCJITCAPITest.cpp index 07ea1afe1a85f..46d6d9b8a9a60 100644 --- a/unittests/ExecutionEngine/MCJIT/MCJITCAPITest.cpp +++ b/unittests/ExecutionEngine/MCJIT/MCJITCAPITest.cpp @@ -17,81 +17,180 @@ #include "llvm-c/ExecutionEngine.h" #include "llvm-c/Target.h" #include "llvm-c/Transforms/Scalar.h" +#include "llvm/ExecutionEngine/SectionMemoryManager.h" #include "llvm/Support/Host.h" #include "MCJITTestAPICommon.h" #include "gtest/gtest.h" using namespace llvm; +static bool didCallAllocateCodeSection; + +static uint8_t *roundTripAllocateCodeSection(void *object, uintptr_t size, + unsigned alignment, + unsigned sectionID, + const char *sectionName) { + didCallAllocateCodeSection = true; + return static_cast<SectionMemoryManager*>(object)->allocateCodeSection( + size, alignment, sectionID, sectionName); +} + +static uint8_t *roundTripAllocateDataSection(void *object, uintptr_t size, + unsigned alignment, + unsigned sectionID, + const char *sectionName, + LLVMBool isReadOnly) { + return static_cast<SectionMemoryManager*>(object)->allocateDataSection( + size, alignment, sectionID, sectionName, isReadOnly); +} + +static LLVMBool roundTripFinalizeMemory(void *object, char **errMsg) { + std::string errMsgString; + bool result = + static_cast<SectionMemoryManager*>(object)->finalizeMemory(&errMsgString); + if (result) { + *errMsg = LLVMCreateMessage(errMsgString.c_str()); + return 1; + } + return 0; +} + +static void roundTripDestroy(void *object) { + delete static_cast<SectionMemoryManager*>(object); +} + +namespace { class MCJITCAPITest : public testing::Test, public MCJITTestAPICommon { protected: MCJITCAPITest() { // The architectures below are known to be compatible with MCJIT as they // are copied from test/ExecutionEngine/MCJIT/lit.local.cfg and should be // kept in sync. + SupportedArchs.push_back(Triple::aarch64); SupportedArchs.push_back(Triple::arm); SupportedArchs.push_back(Triple::mips); SupportedArchs.push_back(Triple::x86); SupportedArchs.push_back(Triple::x86_64); + // Some architectures have sub-architectures in which tests will fail, like + // ARM. These two vectors will define if they do have sub-archs (to avoid + // extra work for those who don't), and if so, if they are listed to work + HasSubArchs.push_back(Triple::arm); + SupportedSubArchs.push_back("armv6"); + SupportedSubArchs.push_back("armv7"); + // The operating systems below are known to be sufficiently incompatible // that they will fail the MCJIT C API tests. UnsupportedOSs.push_back(Triple::Cygwin); } -}; - -TEST_F(MCJITCAPITest, simple_function) { - SKIP_UNSUPPORTED_PLATFORM; - char *error = 0; + virtual void SetUp() { + didCallAllocateCodeSection = false; + Module = 0; + Function = 0; + Engine = 0; + Error = 0; + } - // Creates a function that returns 42, compiles it, and runs it. + virtual void TearDown() { + if (Engine) + LLVMDisposeExecutionEngine(Engine); + else if (Module) + LLVMDisposeModule(Module); + } - LLVMModuleRef module = LLVMModuleCreateWithName("simple_module"); - - LLVMSetTarget(module, HostTriple.c_str()); + void buildSimpleFunction() { + Module = LLVMModuleCreateWithName("simple_module"); + + LLVMSetTarget(Module, HostTriple.c_str()); + + Function = LLVMAddFunction( + Module, "simple_function", LLVMFunctionType(LLVMInt32Type(), 0, 0, 0)); + LLVMSetFunctionCallConv(Function, LLVMCCallConv); + + LLVMBasicBlockRef entry = LLVMAppendBasicBlock(Function, "entry"); + LLVMBuilderRef builder = LLVMCreateBuilder(); + LLVMPositionBuilderAtEnd(builder, entry); + LLVMBuildRet(builder, LLVMConstInt(LLVMInt32Type(), 42, 0)); + + LLVMVerifyModule(Module, LLVMAbortProcessAction, &Error); + LLVMDisposeMessage(Error); + + LLVMDisposeBuilder(builder); + } - LLVMValueRef function = LLVMAddFunction( - module, "simple_function", LLVMFunctionType(LLVMInt32Type(), 0, 0, 0)); - LLVMSetFunctionCallConv(function, LLVMCCallConv); + void buildMCJITOptions() { + LLVMInitializeMCJITCompilerOptions(&Options, sizeof(Options)); + Options.OptLevel = 2; + + // Just ensure that this field still exists. + Options.NoFramePointerElim = false; + } - LLVMBasicBlockRef entry = LLVMAppendBasicBlock(function, "entry"); - LLVMBuilderRef builder = LLVMCreateBuilder(); - LLVMPositionBuilderAtEnd(builder, entry); - LLVMBuildRet(builder, LLVMConstInt(LLVMInt32Type(), 42, 0)); + void useRoundTripSectionMemoryManager() { + Options.MCJMM = LLVMCreateSimpleMCJITMemoryManager( + new SectionMemoryManager(), + roundTripAllocateCodeSection, + roundTripAllocateDataSection, + roundTripFinalizeMemory, + roundTripDestroy); + } - LLVMVerifyModule(module, LLVMAbortProcessAction, &error); - LLVMDisposeMessage(error); + void buildMCJITEngine() { + ASSERT_EQ( + 0, LLVMCreateMCJITCompilerForModule(&Engine, Module, &Options, + sizeof(Options), &Error)); + } - LLVMDisposeBuilder(builder); + void buildAndRunPasses() { + LLVMPassManagerRef pass = LLVMCreatePassManager(); + LLVMAddTargetData(LLVMGetExecutionEngineTargetData(Engine), pass); + LLVMAddConstantPropagationPass(pass); + LLVMAddInstructionCombiningPass(pass); + LLVMRunPassManager(pass, Module); + LLVMDisposePassManager(pass); + } - LLVMMCJITCompilerOptions options; - LLVMInitializeMCJITCompilerOptions(&options, sizeof(options)); - options.OptLevel = 2; + LLVMModuleRef Module; + LLVMValueRef Function; + LLVMMCJITCompilerOptions Options; + LLVMExecutionEngineRef Engine; + char *Error; +}; +} // end anonymous namespace + +TEST_F(MCJITCAPITest, simple_function) { + SKIP_UNSUPPORTED_PLATFORM; - // Just ensure that this field still exists. - options.NoFramePointerElim = false; + buildSimpleFunction(); + buildMCJITOptions(); + buildMCJITEngine(); + buildAndRunPasses(); - LLVMExecutionEngineRef engine; - ASSERT_EQ( - 0, LLVMCreateMCJITCompilerForModule(&engine, module, &options, - sizeof(options), &error)); + union { + void *raw; + int (*usable)(); + } functionPointer; + functionPointer.raw = LLVMGetPointerToGlobal(Engine, Function); - LLVMPassManagerRef pass = LLVMCreatePassManager(); - LLVMAddTargetData(LLVMGetExecutionEngineTargetData(engine), pass); - LLVMAddConstantPropagationPass(pass); - LLVMAddInstructionCombiningPass(pass); - LLVMRunPassManager(pass, module); - LLVMDisposePassManager(pass); + EXPECT_EQ(42, functionPointer.usable()); +} + +TEST_F(MCJITCAPITest, custom_memory_manager) { + SKIP_UNSUPPORTED_PLATFORM; + + buildSimpleFunction(); + buildMCJITOptions(); + useRoundTripSectionMemoryManager(); + buildMCJITEngine(); + buildAndRunPasses(); union { void *raw; int (*usable)(); } functionPointer; - functionPointer.raw = LLVMGetPointerToGlobal(engine, function); + functionPointer.raw = LLVMGetPointerToGlobal(Engine, Function); EXPECT_EQ(42, functionPointer.usable()); - - LLVMDisposeExecutionEngine(engine); + EXPECT_TRUE(didCallAllocateCodeSection); } - diff --git a/unittests/ExecutionEngine/MCJIT/MCJITMemoryManagerTest.cpp b/unittests/ExecutionEngine/MCJIT/MCJITMemoryManagerTest.cpp index ab09acad0d3b6..c24346de84eee 100644 --- a/unittests/ExecutionEngine/MCJIT/MCJITMemoryManagerTest.cpp +++ b/unittests/ExecutionEngine/MCJIT/MCJITMemoryManagerTest.cpp @@ -1,172 +1,172 @@ -//===- MCJITMemoryManagerTest.cpp - Unit tests for the JIT memory manager -===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "llvm/ExecutionEngine/SectionMemoryManager.h"
-#include "llvm/ADT/OwningPtr.h"
-#include "llvm/ExecutionEngine/JIT.h"
-#include "gtest/gtest.h"
-
-using namespace llvm;
-
-namespace {
-
-TEST(MCJITMemoryManagerTest, BasicAllocations) {
- OwningPtr<SectionMemoryManager> MemMgr(new SectionMemoryManager());
-
- uint8_t *code1 = MemMgr->allocateCodeSection(256, 0, 1);
- uint8_t *data1 = MemMgr->allocateDataSection(256, 0, 2, true);
- uint8_t *code2 = MemMgr->allocateCodeSection(256, 0, 3);
- uint8_t *data2 = MemMgr->allocateDataSection(256, 0, 4, false);
-
- EXPECT_NE((uint8_t*)0, code1);
- EXPECT_NE((uint8_t*)0, code2);
- EXPECT_NE((uint8_t*)0, data1);
- EXPECT_NE((uint8_t*)0, data2);
-
- // Initialize the data
- for (unsigned i = 0; i < 256; ++i) {
- code1[i] = 1;
- code2[i] = 2;
- data1[i] = 3;
- data2[i] = 4;
- }
-
- // Verify the data (this is checking for overlaps in the addresses)
- for (unsigned i = 0; i < 256; ++i) {
- EXPECT_EQ(1, code1[i]);
- EXPECT_EQ(2, code2[i]);
- EXPECT_EQ(3, data1[i]);
- EXPECT_EQ(4, data2[i]);
- }
-
- std::string Error;
- EXPECT_FALSE(MemMgr->applyPermissions(&Error));
-}
-
-TEST(MCJITMemoryManagerTest, LargeAllocations) {
- OwningPtr<SectionMemoryManager> MemMgr(new SectionMemoryManager());
-
- uint8_t *code1 = MemMgr->allocateCodeSection(0x100000, 0, 1);
- uint8_t *data1 = MemMgr->allocateDataSection(0x100000, 0, 2, true);
- uint8_t *code2 = MemMgr->allocateCodeSection(0x100000, 0, 3);
- uint8_t *data2 = MemMgr->allocateDataSection(0x100000, 0, 4, false);
-
- EXPECT_NE((uint8_t*)0, code1);
- EXPECT_NE((uint8_t*)0, code2);
- EXPECT_NE((uint8_t*)0, data1);
- EXPECT_NE((uint8_t*)0, data2);
-
- // Initialize the data
- for (unsigned i = 0; i < 0x100000; ++i) {
- code1[i] = 1;
- code2[i] = 2;
- data1[i] = 3;
- data2[i] = 4;
- }
-
- // Verify the data (this is checking for overlaps in the addresses)
- for (unsigned i = 0; i < 0x100000; ++i) {
- EXPECT_EQ(1, code1[i]);
- EXPECT_EQ(2, code2[i]);
- EXPECT_EQ(3, data1[i]);
- EXPECT_EQ(4, data2[i]);
- }
-
- std::string Error;
- EXPECT_FALSE(MemMgr->applyPermissions(&Error));
-}
-
-TEST(MCJITMemoryManagerTest, ManyAllocations) {
- OwningPtr<SectionMemoryManager> MemMgr(new SectionMemoryManager());
-
- uint8_t* code[10000];
- uint8_t* data[10000];
-
- for (unsigned i = 0; i < 10000; ++i) {
- const bool isReadOnly = i % 2 == 0;
-
- code[i] = MemMgr->allocateCodeSection(32, 0, 1);
- data[i] = MemMgr->allocateDataSection(32, 0, 2, isReadOnly);
-
- for (unsigned j = 0; j < 32; j++) {
- code[i][j] = 1 + (i % 254);
- data[i][j] = 2 + (i % 254);
- }
-
- EXPECT_NE((uint8_t *)0, code[i]);
- EXPECT_NE((uint8_t *)0, data[i]);
- }
-
- // Verify the data (this is checking for overlaps in the addresses)
- for (unsigned i = 0; i < 10000; ++i) {
- for (unsigned j = 0; j < 32;j++ ) {
- uint8_t ExpectedCode = 1 + (i % 254);
- uint8_t ExpectedData = 2 + (i % 254);
- EXPECT_EQ(ExpectedCode, code[i][j]);
- EXPECT_EQ(ExpectedData, data[i][j]);
- }
- }
-
- std::string Error;
- EXPECT_FALSE(MemMgr->applyPermissions(&Error));
-}
-
-TEST(MCJITMemoryManagerTest, ManyVariedAllocations) {
- OwningPtr<SectionMemoryManager> MemMgr(new SectionMemoryManager());
-
- uint8_t* code[10000];
- uint8_t* data[10000];
-
- for (unsigned i = 0; i < 10000; ++i) {
- uintptr_t CodeSize = i % 16 + 1;
- uintptr_t DataSize = i % 8 + 1;
-
- bool isReadOnly = i % 3 == 0;
- unsigned Align = 8 << (i % 4);
-
- code[i] = MemMgr->allocateCodeSection(CodeSize, Align, i);
- data[i] = MemMgr->allocateDataSection(DataSize, Align, i + 10000,
- isReadOnly);
-
- for (unsigned j = 0; j < CodeSize; j++) {
- code[i][j] = 1 + (i % 254);
- }
-
- for (unsigned j = 0; j < DataSize; j++) {
- data[i][j] = 2 + (i % 254);
- }
-
- EXPECT_NE((uint8_t *)0, code[i]);
- EXPECT_NE((uint8_t *)0, data[i]);
-
- uintptr_t CodeAlign = Align ? (uintptr_t)code[i] % Align : 0;
- uintptr_t DataAlign = Align ? (uintptr_t)data[i] % Align : 0;
-
- EXPECT_EQ((uintptr_t)0, CodeAlign);
- EXPECT_EQ((uintptr_t)0, DataAlign);
- }
-
- for (unsigned i = 0; i < 10000; ++i) {
- uintptr_t CodeSize = i % 16 + 1;
- uintptr_t DataSize = i % 8 + 1;
-
- for (unsigned j = 0; j < CodeSize; j++) {
- uint8_t ExpectedCode = 1 + (i % 254);
- EXPECT_EQ(ExpectedCode, code[i][j]);
- }
-
- for (unsigned j = 0; j < DataSize; j++) {
- uint8_t ExpectedData = 2 + (i % 254);
- EXPECT_EQ(ExpectedData, data[i][j]);
- }
- }
-}
-
-} // Namespace
-
+//===- MCJITMemoryManagerTest.cpp - Unit tests for the JIT memory manager -===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ExecutionEngine/SectionMemoryManager.h" +#include "llvm/ADT/OwningPtr.h" +#include "llvm/ExecutionEngine/JIT.h" +#include "gtest/gtest.h" + +using namespace llvm; + +namespace { + +TEST(MCJITMemoryManagerTest, BasicAllocations) { + OwningPtr<SectionMemoryManager> MemMgr(new SectionMemoryManager()); + + uint8_t *code1 = MemMgr->allocateCodeSection(256, 0, 1, ""); + uint8_t *data1 = MemMgr->allocateDataSection(256, 0, 2, "", true); + uint8_t *code2 = MemMgr->allocateCodeSection(256, 0, 3, ""); + uint8_t *data2 = MemMgr->allocateDataSection(256, 0, 4, "", false); + + EXPECT_NE((uint8_t*)0, code1); + EXPECT_NE((uint8_t*)0, code2); + EXPECT_NE((uint8_t*)0, data1); + EXPECT_NE((uint8_t*)0, data2); + + // Initialize the data + for (unsigned i = 0; i < 256; ++i) { + code1[i] = 1; + code2[i] = 2; + data1[i] = 3; + data2[i] = 4; + } + + // Verify the data (this is checking for overlaps in the addresses) + for (unsigned i = 0; i < 256; ++i) { + EXPECT_EQ(1, code1[i]); + EXPECT_EQ(2, code2[i]); + EXPECT_EQ(3, data1[i]); + EXPECT_EQ(4, data2[i]); + } + + std::string Error; + EXPECT_FALSE(MemMgr->finalizeMemory(&Error)); +} + +TEST(MCJITMemoryManagerTest, LargeAllocations) { + OwningPtr<SectionMemoryManager> MemMgr(new SectionMemoryManager()); + + uint8_t *code1 = MemMgr->allocateCodeSection(0x100000, 0, 1, ""); + uint8_t *data1 = MemMgr->allocateDataSection(0x100000, 0, 2, "", true); + uint8_t *code2 = MemMgr->allocateCodeSection(0x100000, 0, 3, ""); + uint8_t *data2 = MemMgr->allocateDataSection(0x100000, 0, 4, "", false); + + EXPECT_NE((uint8_t*)0, code1); + EXPECT_NE((uint8_t*)0, code2); + EXPECT_NE((uint8_t*)0, data1); + EXPECT_NE((uint8_t*)0, data2); + + // Initialize the data + for (unsigned i = 0; i < 0x100000; ++i) { + code1[i] = 1; + code2[i] = 2; + data1[i] = 3; + data2[i] = 4; + } + + // Verify the data (this is checking for overlaps in the addresses) + for (unsigned i = 0; i < 0x100000; ++i) { + EXPECT_EQ(1, code1[i]); + EXPECT_EQ(2, code2[i]); + EXPECT_EQ(3, data1[i]); + EXPECT_EQ(4, data2[i]); + } + + std::string Error; + EXPECT_FALSE(MemMgr->finalizeMemory(&Error)); +} + +TEST(MCJITMemoryManagerTest, ManyAllocations) { + OwningPtr<SectionMemoryManager> MemMgr(new SectionMemoryManager()); + + uint8_t* code[10000]; + uint8_t* data[10000]; + + for (unsigned i = 0; i < 10000; ++i) { + const bool isReadOnly = i % 2 == 0; + + code[i] = MemMgr->allocateCodeSection(32, 0, 1, ""); + data[i] = MemMgr->allocateDataSection(32, 0, 2, "", isReadOnly); + + for (unsigned j = 0; j < 32; j++) { + code[i][j] = 1 + (i % 254); + data[i][j] = 2 + (i % 254); + } + + EXPECT_NE((uint8_t *)0, code[i]); + EXPECT_NE((uint8_t *)0, data[i]); + } + + // Verify the data (this is checking for overlaps in the addresses) + for (unsigned i = 0; i < 10000; ++i) { + for (unsigned j = 0; j < 32;j++ ) { + uint8_t ExpectedCode = 1 + (i % 254); + uint8_t ExpectedData = 2 + (i % 254); + EXPECT_EQ(ExpectedCode, code[i][j]); + EXPECT_EQ(ExpectedData, data[i][j]); + } + } + + std::string Error; + EXPECT_FALSE(MemMgr->finalizeMemory(&Error)); +} + +TEST(MCJITMemoryManagerTest, ManyVariedAllocations) { + OwningPtr<SectionMemoryManager> MemMgr(new SectionMemoryManager()); + + uint8_t* code[10000]; + uint8_t* data[10000]; + + for (unsigned i = 0; i < 10000; ++i) { + uintptr_t CodeSize = i % 16 + 1; + uintptr_t DataSize = i % 8 + 1; + + bool isReadOnly = i % 3 == 0; + unsigned Align = 8 << (i % 4); + + code[i] = MemMgr->allocateCodeSection(CodeSize, Align, i, ""); + data[i] = MemMgr->allocateDataSection(DataSize, Align, i + 10000, "", + isReadOnly); + + for (unsigned j = 0; j < CodeSize; j++) { + code[i][j] = 1 + (i % 254); + } + + for (unsigned j = 0; j < DataSize; j++) { + data[i][j] = 2 + (i % 254); + } + + EXPECT_NE((uint8_t *)0, code[i]); + EXPECT_NE((uint8_t *)0, data[i]); + + uintptr_t CodeAlign = Align ? (uintptr_t)code[i] % Align : 0; + uintptr_t DataAlign = Align ? (uintptr_t)data[i] % Align : 0; + + EXPECT_EQ((uintptr_t)0, CodeAlign); + EXPECT_EQ((uintptr_t)0, DataAlign); + } + + for (unsigned i = 0; i < 10000; ++i) { + uintptr_t CodeSize = i % 16 + 1; + uintptr_t DataSize = i % 8 + 1; + + for (unsigned j = 0; j < CodeSize; j++) { + uint8_t ExpectedCode = 1 + (i % 254); + EXPECT_EQ(ExpectedCode, code[i][j]); + } + + for (unsigned j = 0; j < DataSize; j++) { + uint8_t ExpectedData = 2 + (i % 254); + EXPECT_EQ(ExpectedData, data[i][j]); + } + } +} + +} // Namespace + diff --git a/unittests/ExecutionEngine/MCJIT/MCJITMultipleModuleTest.cpp b/unittests/ExecutionEngine/MCJIT/MCJITMultipleModuleTest.cpp new file mode 100644 index 0000000000000..7c3239ea2598d --- /dev/null +++ b/unittests/ExecutionEngine/MCJIT/MCJITMultipleModuleTest.cpp @@ -0,0 +1,395 @@ +//===- MCJITMultipeModuleTest.cpp - Unit tests for the MCJIT---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This test suite verifies MCJIT for handling multiple modules in a single +// ExecutionEngine by building multiple modules, making function calls across +// modules, accessing global variables, etc. +//===----------------------------------------------------------------------===// + +#include "llvm/ExecutionEngine/MCJIT.h" +#include "MCJITTestBase.h" +#include "gtest/gtest.h" + +using namespace llvm; + +namespace { + +class MCJITMultipleModuleTest : public testing::Test, public MCJITTestBase {}; + +// FIXME: ExecutionEngine has no support empty modules +/* +TEST_F(MCJITMultipleModuleTest, multiple_empty_modules) { + SKIP_UNSUPPORTED_PLATFORM; + + createJIT(M.take()); + // JIT-compile + EXPECT_NE(0, TheJIT->getObjectImage()) + << "Unable to generate executable loaded object image"; + + TheJIT->addModule(createEmptyModule("<other module>")); + TheJIT->addModule(createEmptyModule("<other other module>")); + + // JIT again + EXPECT_NE(0, TheJIT->getObjectImage()) + << "Unable to generate executable loaded object image"; +} +*/ + +// Helper Function to test add operation +void checkAdd(uint64_t ptr) { + ASSERT_TRUE(ptr != 0) << "Unable to get pointer to function."; + int (*AddPtr)(int, int) = (int (*)(int, int))ptr; + EXPECT_EQ(0, AddPtr(0, 0)); + EXPECT_EQ(1, AddPtr(1, 0)); + EXPECT_EQ(3, AddPtr(1, 2)); + EXPECT_EQ(-5, AddPtr(-2, -3)); + EXPECT_EQ(30, AddPtr(10, 20)); + EXPECT_EQ(-30, AddPtr(-10, -20)); + EXPECT_EQ(-40, AddPtr(-10, -30)); +} + +void checkAccumulate(uint64_t ptr) { + ASSERT_TRUE(ptr != 0) << "Unable to get pointer to function."; + int32_t (*FPtr)(int32_t) = (int32_t (*)(int32_t))(intptr_t)ptr; + EXPECT_EQ(0, FPtr(0)); + EXPECT_EQ(1, FPtr(1)); + EXPECT_EQ(3, FPtr(2)); + EXPECT_EQ(6, FPtr(3)); + EXPECT_EQ(10, FPtr(4)); + EXPECT_EQ(15, FPtr(5)); +} + +// FIXME: ExecutionEngine has no support empty modules +/* +TEST_F(MCJITMultipleModuleTest, multiple_empty_modules) { + SKIP_UNSUPPORTED_PLATFORM; + + createJIT(M.take()); + // JIT-compile + EXPECT_NE(0, TheJIT->getObjectImage()) + << "Unable to generate executable loaded object image"; + + TheJIT->addModule(createEmptyModule("<other module>")); + TheJIT->addModule(createEmptyModule("<other other module>")); + + // JIT again + EXPECT_NE(0, TheJIT->getObjectImage()) + << "Unable to generate executable loaded object image"; +} +*/ + +// Module A { Function FA }, +// Module B { Function FB }, +// execute FA then FB +TEST_F(MCJITMultipleModuleTest, two_module_case) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B; + Function *FA, *FB; + createTwoModuleCase(A, FA, B, FB); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FB->getName().str()); + checkAdd(ptr); +} + +// Module A { Function FA }, +// Module B { Function FB }, +// execute FB then FA +TEST_F(MCJITMultipleModuleTest, two_module_reverse_case) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B; + Function *FA, *FB; + createTwoModuleCase(A, FA, B, FB); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FB->getName().str()); + TheJIT->finalizeObject(); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAdd(ptr); +} + +// Module A { Function FA }, +// Module B { Extern FA, Function FB which calls FA }, +// execute FB then FA +TEST_F(MCJITMultipleModuleTest, two_module_extern_reverse_case) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B; + Function *FA, *FB; + createTwoModuleExternCase(A, FA, B, FB); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FB->getName().str()); + TheJIT->finalizeObject(); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAdd(ptr); +} + +// Module A { Function FA }, +// Module B { Extern FA, Function FB which calls FA }, +// execute FA then FB +TEST_F(MCJITMultipleModuleTest, two_module_extern_case) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B; + Function *FA, *FB; + createTwoModuleExternCase(A, FA, B, FB); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FB->getName().str()); + checkAdd(ptr); +} + +// Module A { Function FA1, Function FA2 which calls FA1 }, +// Module B { Extern FA1, Function FB which calls FA1 }, +// execute FB then FA2 +TEST_F(MCJITMultipleModuleTest, two_module_consecutive_call_case) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B; + Function *FA1, *FA2, *FB; + createTwoModuleExternCase(A, FA1, B, FB); + FA2 = insertSimpleCallFunction<int32_t(int32_t, int32_t)>(A.get(), FA1); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FB->getName().str()); + TheJIT->finalizeObject(); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FA2->getName().str()); + checkAdd(ptr); +} + +// TODO: +// Module A { Extern Global GVB, Global Variable GVA, Function FA loads GVB }, +// Module B { Extern Global GVA, Global Variable GVB, Function FB loads GVA }, + + +// Module A { Global Variable GVA, Function FA loads GVA }, +// Module B { Global Variable GVB, Function FB loads GVB }, +// execute FB then FA +TEST_F(MCJITMultipleModuleTest, two_module_global_variables_case) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B; + Function *FA, *FB; + GlobalVariable *GVA, *GVB; + A.reset(createEmptyModule("A")); + B.reset(createEmptyModule("B")); + + int32_t initialNum = 7; + GVA = insertGlobalInt32(A.get(), "GVA", initialNum); + GVB = insertGlobalInt32(B.get(), "GVB", initialNum); + FA = startFunction<int32_t(void)>(A.get(), "FA"); + endFunctionWithRet(FA, Builder.CreateLoad(GVA)); + FB = startFunction<int32_t(void)>(B.get(), "FB"); + endFunctionWithRet(FB, Builder.CreateLoad(GVB)); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + + uint64_t FBPtr = TheJIT->getFunctionAddress(FB->getName().str()); + TheJIT->finalizeObject(); + EXPECT_TRUE(0 != FBPtr); + int32_t(*FuncPtr)(void) = (int32_t(*)(void))FBPtr; + EXPECT_EQ(initialNum, FuncPtr()) + << "Invalid value for global returned from JITted function in module B"; + + uint64_t FAPtr = TheJIT->getFunctionAddress(FA->getName().str()); + EXPECT_TRUE(0 != FAPtr); + FuncPtr = (int32_t(*)(void))FAPtr; + EXPECT_EQ(initialNum, FuncPtr()) + << "Invalid value for global returned from JITted function in module A"; +} + +// Module A { Function FA }, +// Module B { Extern FA, Function FB which calls FA }, +// Module C { Extern FA, Function FC which calls FA }, +// execute FC, FB, FA +TEST_F(MCJITMultipleModuleTest, three_module_case) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B, C; + Function *FA, *FB, *FC; + createThreeModuleCase(A, FA, B, FB, C, FC); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + TheJIT->addModule(C.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FC->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FB->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAdd(ptr); +} + +// Module A { Function FA }, +// Module B { Extern FA, Function FB which calls FA }, +// Module C { Extern FA, Function FC which calls FA }, +// execute FA, FB, FC +TEST_F(MCJITMultipleModuleTest, three_module_case_reverse_order) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B, C; + Function *FA, *FB, *FC; + createThreeModuleCase(A, FA, B, FB, C, FC); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + TheJIT->addModule(C.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FB->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FC->getName().str()); + checkAdd(ptr); +} + +// Module A { Function FA }, +// Module B { Extern FA, Function FB which calls FA }, +// Module C { Extern FB, Function FC which calls FB }, +// execute FC, FB, FA +TEST_F(MCJITMultipleModuleTest, three_module_chain_case) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B, C; + Function *FA, *FB, *FC; + createThreeModuleChainedCallsCase(A, FA, B, FB, C, FC); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + TheJIT->addModule(C.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FC->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FB->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAdd(ptr); +} + +// Module A { Function FA }, +// Module B { Extern FA, Function FB which calls FA }, +// Module C { Extern FB, Function FC which calls FB }, +// execute FA, FB, FC +TEST_F(MCJITMultipleModuleTest, three_modules_chain_case_reverse_order) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B, C; + Function *FA, *FB, *FC; + createThreeModuleChainedCallsCase(A, FA, B, FB, C, FC); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + TheJIT->addModule(C.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FB->getName().str()); + checkAdd(ptr); + + ptr = TheJIT->getFunctionAddress(FC->getName().str()); + checkAdd(ptr); +} + +// Module A { Extern FB, Function FA which calls FB1 }, +// Module B { Extern FA, Function FB1, Function FB2 which calls FA }, +// execute FA, then FB1 +// FIXME: this test case is not supported by MCJIT +TEST_F(MCJITMultipleModuleTest, cross_module_dependency_case) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B; + Function *FA, *FB1, *FB2; + createCrossModuleRecursiveCase(A, FA, B, FB1, FB2); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAccumulate(ptr); + + ptr = TheJIT->getFunctionAddress(FB1->getName().str()); + checkAccumulate(ptr); +} + +// Module A { Extern FB, Function FA which calls FB1 }, +// Module B { Extern FA, Function FB1, Function FB2 which calls FA }, +// execute FB1 then FA +// FIXME: this test case is not supported by MCJIT +TEST_F(MCJITMultipleModuleTest, cross_module_dependency_case_reverse_order) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B; + Function *FA, *FB1, *FB2; + createCrossModuleRecursiveCase(A, FA, B, FB1, FB2); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FB1->getName().str()); + checkAccumulate(ptr); + + ptr = TheJIT->getFunctionAddress(FA->getName().str()); + checkAccumulate(ptr); +} + +// Module A { Extern FB1, Function FA which calls FB1 }, +// Module B { Extern FA, Function FB1, Function FB2 which calls FA }, +// execute FB1 then FB2 +// FIXME: this test case is not supported by MCJIT +TEST_F(MCJITMultipleModuleTest, cross_module_dependency_case3) { + SKIP_UNSUPPORTED_PLATFORM; + + OwningPtr<Module> A, B; + Function *FA, *FB1, *FB2; + createCrossModuleRecursiveCase(A, FA, B, FB1, FB2); + + createJIT(A.take()); + TheJIT->addModule(B.take()); + + uint64_t ptr = TheJIT->getFunctionAddress(FB1->getName().str()); + checkAccumulate(ptr); + + ptr = TheJIT->getFunctionAddress(FB2->getName().str()); + checkAccumulate(ptr); +} +} diff --git a/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp b/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp index 0061e30e7a541..7073a5265d62c 100644 --- a/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp +++ b/unittests/ExecutionEngine/MCJIT/MCJITObjectCacheTest.cpp @@ -28,7 +28,7 @@ public: virtual ~TestObjectCache() { // Free any buffers we've allocated. - SmallVector<MemoryBuffer *, 2>::iterator it, end; + SmallVectorImpl<MemoryBuffer *>::iterator it, end; end = AllocatedBuffers.end(); for (it = AllocatedBuffers.begin(); it != end; ++it) { delete *it; @@ -45,6 +45,16 @@ public: ObjMap[ModuleID] = copyBuffer(Obj); } + virtual MemoryBuffer* getObject(const Module* M) { + const MemoryBuffer* BufferFound = getObjectInternal(M); + ModulesLookedUp.insert(M->getModuleIdentifier()); + if (!BufferFound) + return NULL; + // Our test cache wants to maintain ownership of its object buffers + // so we make a copy here for the execution engine. + return MemoryBuffer::getMemBufferCopy(BufferFound->getBuffer()); + } + // Test-harness-specific functions bool wereDuplicatesInserted() { return DuplicateInserted; } @@ -62,13 +72,6 @@ public: return it->second; } -protected: - virtual const MemoryBuffer* getObject(const Module* M) { - const MemoryBuffer* BufferFound = getObjectInternal(M); - ModulesLookedUp.insert(M->getModuleIdentifier()); - return BufferFound; - } - private: MemoryBuffer *copyBuffer(const MemoryBuffer *Buf) { // Create a local copy of the buffer. @@ -98,14 +101,13 @@ protected: void compileAndRun(int ExpectedRC = OriginalRC) { // This function shouldn't be called until after SetUp. - ASSERT_TRUE(0 != TheJIT); + ASSERT_TRUE(TheJIT.isValid()); ASSERT_TRUE(0 != Main); + // We may be using a null cache, so ensure compilation is valid. TheJIT->finalizeObject(); void *vPtr = TheJIT->getPointerToFunction(Main); - static_cast<SectionMemoryManager*>(MM)->invalidateInstructionCache(); - EXPECT_TRUE(0 != vPtr) << "Unable to get pointer to main() from JIT"; diff --git a/unittests/ExecutionEngine/MCJIT/MCJITTest.cpp b/unittests/ExecutionEngine/MCJIT/MCJITTest.cpp index e9cf904b1813f..fab81551fa5eb 100644 --- a/unittests/ExecutionEngine/MCJIT/MCJITTest.cpp +++ b/unittests/ExecutionEngine/MCJIT/MCJITTest.cpp @@ -18,19 +18,24 @@ using namespace llvm; +namespace { + class MCJITTest : public testing::Test, public MCJITTestBase { protected: - - virtual void SetUp() { - M.reset(createEmptyModule("<main>")); - } + virtual void SetUp() { M.reset(createEmptyModule("<main>")); } }; -namespace { +// FIXME: Ensure creating an execution engine does not crash when constructed +// with a null module. +/* +TEST_F(MCJITTest, null_module) { + createJIT(0); +} +*/ // FIXME: In order to JIT an empty module, there needs to be // an interface to ExecutionEngine that forces compilation but -// does require retrieval of a pointer to a function/global. +// does not require retrieval of a pointer to a function/global. /* TEST_F(MCJITTest, empty_module) { createJIT(M.take()); @@ -46,8 +51,6 @@ TEST_F(MCJITTest, global_variable) { GlobalValue *Global = insertGlobalInt32(M.get(), "test_global", initialValue); createJIT(M.take()); void *globalPtr = TheJIT->getPointerToGlobal(Global); - MM->applyPermissions(); - static_cast<SectionMemoryManager*>(MM)->invalidateInstructionCache(); EXPECT_TRUE(0 != globalPtr) << "Unable to get pointer to global value from JIT"; @@ -60,16 +63,19 @@ TEST_F(MCJITTest, add_function) { Function *F = insertAddFunction(M.get()); createJIT(M.take()); - void *addPtr = TheJIT->getPointerToFunction(F); - MM->applyPermissions(); - static_cast<SectionMemoryManager*>(MM)->invalidateInstructionCache(); + uint64_t addPtr = TheJIT->getFunctionAddress(F->getName().str()); EXPECT_TRUE(0 != addPtr) << "Unable to get pointer to function from JIT"; - int (*AddPtrTy)(int, int) = (int(*)(int, int))(intptr_t)addPtr; - EXPECT_EQ(0, AddPtrTy(0, 0)); - EXPECT_EQ(3, AddPtrTy(1, 2)); - EXPECT_EQ(-5, AddPtrTy(-2, -3)); + ASSERT_TRUE(addPtr != 0) << "Unable to get pointer to function ."; + int (*AddPtr)(int, int) = (int(*)(int, int))addPtr ; + EXPECT_EQ(0, AddPtr(0, 0)); + EXPECT_EQ(1, AddPtr(1, 0)); + EXPECT_EQ(3, AddPtr(1, 2)); + EXPECT_EQ(-5, AddPtr(-2, -3)); + EXPECT_EQ(30, AddPtr(10, 20)); + EXPECT_EQ(-30, AddPtr(-10, -20)); + EXPECT_EQ(-40, AddPtr(-10, -30)); } TEST_F(MCJITTest, run_main) { @@ -78,13 +84,11 @@ TEST_F(MCJITTest, run_main) { int rc = 6; Function *Main = insertMainFunction(M.get(), 6); createJIT(M.take()); - void *vPtr = TheJIT->getPointerToFunction(Main); - MM->applyPermissions(); - static_cast<SectionMemoryManager*>(MM)->invalidateInstructionCache(); - EXPECT_TRUE(0 != vPtr) + uint64_t ptr = TheJIT->getFunctionAddress(Main->getName().str()); + EXPECT_TRUE(0 != ptr) << "Unable to get pointer to main() from JIT"; - int (*FuncPtr)(void) = (int(*)(void))(intptr_t)vPtr; + int (*FuncPtr)(void) = (int(*)(void))ptr; int returnCode = FuncPtr(); EXPECT_EQ(returnCode, rc); } @@ -101,12 +105,10 @@ TEST_F(MCJITTest, return_global) { endFunctionWithRet(ReturnGlobal, ReadGlobal); createJIT(M.take()); - void *rgvPtr = TheJIT->getPointerToFunction(ReturnGlobal); - MM->applyPermissions(); - static_cast<SectionMemoryManager*>(MM)->invalidateInstructionCache(); + uint64_t rgvPtr = TheJIT->getFunctionAddress(ReturnGlobal->getName().str()); EXPECT_TRUE(0 != rgvPtr); - int32_t(*FuncPtr)(void) = (int32_t(*)(void))(intptr_t)rgvPtr; + int32_t(*FuncPtr)(void) = (int32_t(*)(void))rgvPtr; EXPECT_EQ(initialNum, FuncPtr()) << "Invalid value for global returned from JITted function"; } @@ -136,7 +138,7 @@ TEST_F(MCJITTest, increment_global) { void *gvPtr = TheJIT->getPointerToGlobal(GV); EXPECT_EQ(initialNum, *(int32_t*)gvPtr); - void *vPtr = TheJIT->getPointerToFunction(IncrementGlobal); + void *vPtr = TheJIT->getFunctionAddress(IncrementGlobal->getName().str()); EXPECT_TRUE(0 != vPtr) << "Unable to get pointer to main() from JIT"; @@ -150,6 +152,9 @@ TEST_F(MCJITTest, increment_global) { } */ +// PR16013: XFAIL this test on ARM, which currently can't handle multiple relocations. +#if !defined(__arm__) + TEST_F(MCJITTest, multiple_functions) { SKIP_UNSUPPORTED_PLATFORM; @@ -171,65 +176,15 @@ TEST_F(MCJITTest, multiple_functions) { } createJIT(M.take()); - void *vPtr = TheJIT->getPointerToFunction(Outer); - MM->applyPermissions(); - static_cast<SectionMemoryManager*>(MM)->invalidateInstructionCache(); - EXPECT_TRUE(0 != vPtr) + uint64_t ptr = TheJIT->getFunctionAddress(Outer->getName().str()); + EXPECT_TRUE(0 != ptr) << "Unable to get pointer to outer function from JIT"; - int32_t(*FuncPtr)(void) = (int32_t(*)(void))(intptr_t)vPtr; + int32_t(*FuncPtr)(void) = (int32_t(*)(void))ptr; EXPECT_EQ(innerRetVal, FuncPtr()) << "Incorrect result returned from function"; } -// FIXME: ExecutionEngine has no support empty modules -/* -TEST_F(MCJITTest, multiple_empty_modules) { - SKIP_UNSUPPORTED_PLATFORM; - - createJIT(M.take()); - // JIT-compile - EXPECT_NE(0, TheJIT->getObjectImage()) - << "Unable to generate executable loaded object image"; - - TheJIT->addModule(createEmptyModule("<other module>")); - TheJIT->addModule(createEmptyModule("<other other module>")); - - // JIT again - EXPECT_NE(0, TheJIT->getObjectImage()) - << "Unable to generate executable loaded object image"; -} -*/ - -// FIXME: MCJIT must support multiple modules -/* -TEST_F(MCJITTest, multiple_modules) { - SKIP_UNSUPPORTED_PLATFORM; - - Function *Callee = insertAddFunction(M.get()); - createJIT(M.take()); - - // caller function is defined in a different module - M.reset(createEmptyModule("<caller module>")); - - Function *CalleeRef = insertExternalReferenceToFunction(M.get(), Callee); - Function *Caller = insertSimpleCallFunction(M.get(), CalleeRef); - - TheJIT->addModule(M.take()); - - // get a function pointer in a module that was not used in EE construction - void *vPtr = TheJIT->getPointerToFunction(Caller); - EXPECT_NE(0, vPtr) - << "Unable to get pointer to caller function from JIT"; - - int(*FuncPtr)(int, int) = (int(*)(int, int))(intptr_t)vPtr; - EXPECT_EQ(0, FuncPtr(0, 0)); - EXPECT_EQ(30, FuncPtr(10, 20)); - EXPECT_EQ(-30, FuncPtr(-10, -20)); - - // ensure caller is destroyed before callee (free use before def) - M.reset(); -} -*/ +#endif /*!defined(__arm__)*/ } diff --git a/unittests/ExecutionEngine/MCJIT/MCJITTestAPICommon.h b/unittests/ExecutionEngine/MCJIT/MCJITTestAPICommon.h index 8160a186f413b..7b6e39fb23854 100644 --- a/unittests/ExecutionEngine/MCJIT/MCJITTestAPICommon.h +++ b/unittests/ExecutionEngine/MCJIT/MCJITTestAPICommon.h @@ -49,11 +49,23 @@ protected: /// Returns true if the host architecture is known to support MCJIT bool ArchSupportsMCJIT() { Triple Host(HostTriple); + // If ARCH is not supported, bail if (std::find(SupportedArchs.begin(), SupportedArchs.end(), Host.getArch()) - == SupportedArchs.end()) { + == SupportedArchs.end()) return false; - } - return true; + + // If ARCH is supported and has no specific sub-arch support + if (std::find(HasSubArchs.begin(), HasSubArchs.end(), Host.getArch()) + == HasSubArchs.end()) + return true; + + // If ARCH has sub-arch support, find it + SmallVectorImpl<std::string>::const_iterator I = SupportedSubArchs.begin(); + for(; I != SupportedSubArchs.end(); ++I) + if (Host.getArchName().startswith(I->c_str())) + return true; + + return false; } /// Returns true if the host OS is known to support MCJIT @@ -68,6 +80,8 @@ protected: std::string HostTriple; SmallVector<Triple::ArchType, 4> SupportedArchs; + SmallVector<Triple::ArchType, 1> HasSubArchs; + SmallVector<std::string, 2> SupportedSubArchs; // We need to own the memory SmallVector<Triple::OSType, 4> UnsupportedOSs; }; diff --git a/unittests/ExecutionEngine/MCJIT/MCJITTestBase.h b/unittests/ExecutionEngine/MCJIT/MCJITTestBase.h index b0e98a88defc1..b42a9c0980db1 100644 --- a/unittests/ExecutionEngine/MCJIT/MCJITTestBase.h +++ b/unittests/ExecutionEngine/MCJIT/MCJITTestBase.h @@ -30,35 +30,19 @@ namespace llvm { -class MCJITTestBase : public MCJITTestAPICommon { +/// Helper class that can build very simple Modules +class TrivialModuleBuilder { protected: + LLVMContext Context; + IRBuilder<> Builder; + std::string BuilderTriple; - MCJITTestBase() - : OptLevel(CodeGenOpt::None) - , RelocModel(Reloc::Default) - , CodeModel(CodeModel::Default) - , MArch("") - , Builder(Context) - , MM(new SectionMemoryManager) - { - // The architectures below are known to be compatible with MCJIT as they - // are copied from test/ExecutionEngine/MCJIT/lit.local.cfg and should be - // kept in sync. - SupportedArchs.push_back(Triple::arm); - SupportedArchs.push_back(Triple::mips); - SupportedArchs.push_back(Triple::x86); - SupportedArchs.push_back(Triple::x86_64); - - // The operating systems below are known to be incompatible with MCJIT as - // they are copied from the test/ExecutionEngine/MCJIT/lit.local.cfg and - // should be kept in sync. - UnsupportedOSs.push_back(Triple::Cygwin); - UnsupportedOSs.push_back(Triple::Darwin); - } + TrivialModuleBuilder(const std::string &Triple) + : Builder(Context), BuilderTriple(Triple) {} - Module *createEmptyModule(StringRef Name) { + Module *createEmptyModule(StringRef Name = StringRef()) { Module * M = new Module(Name, Context); - M->setTargetTriple(Triple::normalize(HostTriple)); + M->setTargetTriple(Triple::normalize(BuilderTriple)); return M; } @@ -135,12 +119,13 @@ protected: // Inserts an declaration to a function defined elsewhere Function *insertExternalReferenceToFunction(Module *M, Function *Func) { Function *Result = Function::Create(Func->getFunctionType(), - GlobalValue::AvailableExternallyLinkage, + GlobalValue::ExternalLinkage, Func->getName(), M); return Result; } // Inserts a global variable of type int32 + // FIXME: make this a template function to support any type GlobalVariable *insertGlobalInt32(Module *M, StringRef name, int32_t InitialValue) { @@ -155,6 +140,179 @@ protected: return Global; } + // Inserts a function + // int32_t recursive_add(int32_t num) { + // if (num == 0) { + // return num; + // } else { + // int32_t recursive_param = num - 1; + // return num + Helper(recursive_param); + // } + // } + // NOTE: if Helper is left as the default parameter, Helper == recursive_add. + Function *insertAccumulateFunction(Module *M, + Function *Helper = 0, + StringRef Name = "accumulate") { + Function *Result = startFunction<int32_t(int32_t)>(M, Name); + if (Helper == 0) + Helper = Result; + + BasicBlock *BaseCase = BasicBlock::Create(Context, "", Result); + BasicBlock *RecursiveCase = BasicBlock::Create(Context, "", Result); + + // if (num == 0) + Value *Param = Result->arg_begin(); + Value *Zero = ConstantInt::get(Context, APInt(32, 0)); + Builder.CreateCondBr(Builder.CreateICmpEQ(Param, Zero), + BaseCase, RecursiveCase); + + // return num; + Builder.SetInsertPoint(BaseCase); + Builder.CreateRet(Param); + + // int32_t recursive_param = num - 1; + // return Helper(recursive_param); + Builder.SetInsertPoint(RecursiveCase); + Value *One = ConstantInt::get(Context, APInt(32, 1)); + Value *RecursiveParam = Builder.CreateSub(Param, One); + Value *RecursiveReturn = Builder.CreateCall(Helper, RecursiveParam); + Value *Accumulator = Builder.CreateAdd(Param, RecursiveReturn); + Builder.CreateRet(Accumulator); + + return Result; + } + + // Populates Modules A and B: + // Module A { Extern FB1, Function FA which calls FB1 }, + // Module B { Extern FA, Function FB1, Function FB2 which calls FA }, + void createCrossModuleRecursiveCase(OwningPtr<Module> &A, + Function *&FA, + OwningPtr<Module> &B, + Function *&FB1, + Function *&FB2) { + // Define FB1 in B. + B.reset(createEmptyModule("B")); + FB1 = insertAccumulateFunction(B.get(), 0, "FB1"); + + // Declare FB1 in A (as an external). + A.reset(createEmptyModule("A")); + Function *FB1Extern = insertExternalReferenceToFunction(A.get(), FB1); + + // Define FA in A (with a call to FB1). + FA = insertAccumulateFunction(A.get(), FB1Extern, "FA"); + + // Declare FA in B (as an external) + Function *FAExtern = insertExternalReferenceToFunction(B.get(), FA); + + // Define FB2 in B (with a call to FA) + FB2 = insertAccumulateFunction(B.get(), FAExtern, "FB2"); + } + + // Module A { Function FA }, + // Module B { Extern FA, Function FB which calls FA }, + // Module C { Extern FB, Function FC which calls FB }, + void createThreeModuleChainedCallsCase(OwningPtr<Module> &A, + Function *&FA, + OwningPtr<Module> &B, + Function *&FB, + OwningPtr<Module> &C, + Function *&FC) { + A.reset(createEmptyModule("A")); + FA = insertAddFunction(A.get()); + + B.reset(createEmptyModule("B")); + Function *FAExtern_in_B = insertExternalReferenceToFunction(B.get(), FA); + FB = insertSimpleCallFunction<int32_t(int32_t, int32_t)>(B.get(), FAExtern_in_B); + + C.reset(createEmptyModule("C")); + Function *FBExtern_in_C = insertExternalReferenceToFunction(C.get(), FB); + FC = insertSimpleCallFunction<int32_t(int32_t, int32_t)>(C.get(), FBExtern_in_C); + } + + + // Module A { Function FA }, + // Populates Modules A and B: + // Module B { Function FB } + void createTwoModuleCase(OwningPtr<Module> &A, Function *&FA, + OwningPtr<Module> &B, Function *&FB) { + A.reset(createEmptyModule("A")); + FA = insertAddFunction(A.get()); + + B.reset(createEmptyModule("B")); + FB = insertAddFunction(B.get()); + } + + // Module A { Function FA }, + // Module B { Extern FA, Function FB which calls FA } + void createTwoModuleExternCase(OwningPtr<Module> &A, Function *&FA, + OwningPtr<Module> &B, Function *&FB) { + A.reset(createEmptyModule("A")); + FA = insertAddFunction(A.get()); + + B.reset(createEmptyModule("B")); + Function *FAExtern_in_B = insertExternalReferenceToFunction(B.get(), FA); + FB = insertSimpleCallFunction<int32_t(int32_t, int32_t)>(B.get(), + FAExtern_in_B); + } + + // Module A { Function FA }, + // Module B { Extern FA, Function FB which calls FA }, + // Module C { Extern FB, Function FC which calls FA }, + void createThreeModuleCase(OwningPtr<Module> &A, + Function *&FA, + OwningPtr<Module> &B, + Function *&FB, + OwningPtr<Module> &C, + Function *&FC) { + A.reset(createEmptyModule("A")); + FA = insertAddFunction(A.get()); + + B.reset(createEmptyModule("B")); + Function *FAExtern_in_B = insertExternalReferenceToFunction(B.get(), FA); + FB = insertSimpleCallFunction<int32_t(int32_t, int32_t)>(B.get(), FAExtern_in_B); + + C.reset(createEmptyModule("C")); + Function *FAExtern_in_C = insertExternalReferenceToFunction(C.get(), FA); + FC = insertSimpleCallFunction<int32_t(int32_t, int32_t)>(C.get(), FAExtern_in_C); + } +}; + + +class MCJITTestBase : public MCJITTestAPICommon, public TrivialModuleBuilder { +protected: + + MCJITTestBase() + : TrivialModuleBuilder(HostTriple) + , OptLevel(CodeGenOpt::None) + , RelocModel(Reloc::Default) + , CodeModel(CodeModel::Default) + , MArch("") + , MM(new SectionMemoryManager) + { + // The architectures below are known to be compatible with MCJIT as they + // are copied from test/ExecutionEngine/MCJIT/lit.local.cfg and should be + // kept in sync. + SupportedArchs.push_back(Triple::aarch64); + SupportedArchs.push_back(Triple::arm); + SupportedArchs.push_back(Triple::mips); + SupportedArchs.push_back(Triple::mipsel); + SupportedArchs.push_back(Triple::x86); + SupportedArchs.push_back(Triple::x86_64); + + // Some architectures have sub-architectures in which tests will fail, like + // ARM. These two vectors will define if they do have sub-archs (to avoid + // extra work for those who don't), and if so, if they are listed to work + HasSubArchs.push_back(Triple::arm); + SupportedSubArchs.push_back("armv6"); + SupportedSubArchs.push_back("armv7"); + + // The operating systems below are known to be incompatible with MCJIT as + // they are copied from the test/ExecutionEngine/MCJIT/lit.local.cfg and + // should be kept in sync. + UnsupportedOSs.push_back(Triple::Cygwin); + UnsupportedOSs.push_back(Triple::Darwin); + } + void createJIT(Module *M) { // Due to the EngineBuilder constructor, it is required to have a Module @@ -165,7 +323,7 @@ protected: std::string Error; TheJIT.reset(EB.setEngineKind(EngineKind::JIT) .setUseMCJIT(true) /* can this be folded into the EngineKind enum? */ - .setJITMemoryManager(MM) + .setMCJITMemoryManager(MM) .setErrorStr(&Error) .setOptLevel(CodeGenOpt::None) .setAllocateGVsWithCode(false) /*does this do anything?*/ @@ -179,16 +337,13 @@ protected: assert(TheJIT.get() != NULL && "error creating MCJIT with EngineBuilder"); } - LLVMContext Context; CodeGenOpt::Level OptLevel; Reloc::Model RelocModel; CodeModel::Model CodeModel; StringRef MArch; SmallVector<std::string, 1> MAttrs; - OwningPtr<TargetMachine> TM; OwningPtr<ExecutionEngine> TheJIT; - IRBuilder<> Builder; - JITMemoryManager *MM; + RTDyldMemoryManager *MM; OwningPtr<Module> M; }; diff --git a/unittests/ExecutionEngine/MCJIT/Makefile b/unittests/ExecutionEngine/MCJIT/Makefile index 454f83099d4b4..33b043be9ebd0 100644 --- a/unittests/ExecutionEngine/MCJIT/Makefile +++ b/unittests/ExecutionEngine/MCJIT/Makefile @@ -9,7 +9,7 @@ LEVEL = ../../.. TESTNAME = MCJIT -LINK_COMPONENTS := core jit mcjit native support +LINK_COMPONENTS := core mcjit native support include $(LEVEL)/Makefile.config include $(LLVM_SRC_ROOT)/unittests/Makefile.unittest |