| //===- MCJITTest.cpp - Unit tests for the MCJIT -----------------*- C++ -*-===// |
| // |
| // 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 basic MCJIT functionality when invoked form the C |
| // API. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "MCJITTestAPICommon.h" |
| #include "llvm-c/Analysis.h" |
| #include "llvm-c/Core.h" |
| #include "llvm-c/ExecutionEngine.h" |
| #include "llvm-c/Target.h" |
| #include "llvm-c/Transforms/PassManagerBuilder.h" |
| #include "llvm-c/Transforms/Scalar.h" |
| #include "llvm/ExecutionEngine/SectionMemoryManager.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/Host.h" |
| #include "gtest/gtest.h" |
| |
| using namespace llvm; |
| |
| static bool didCallAllocateCodeSection; |
| static bool didAllocateCompactUnwindSection; |
| static bool didCallYield; |
| |
| 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) { |
| if (!strcmp(sectionName, "__compact_unwind")) |
| didAllocateCompactUnwindSection = true; |
| 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); |
| } |
| |
| static void yield(LLVMContextRef, void *) { |
| didCallYield = true; |
| } |
| |
| namespace { |
| |
| // memory manager to test reserve allocation space callback |
| class TestReserveAllocationSpaceMemoryManager: public SectionMemoryManager { |
| public: |
| uintptr_t ReservedCodeSize; |
| uintptr_t UsedCodeSize; |
| uintptr_t ReservedDataSizeRO; |
| uintptr_t UsedDataSizeRO; |
| uintptr_t ReservedDataSizeRW; |
| uintptr_t UsedDataSizeRW; |
| |
| TestReserveAllocationSpaceMemoryManager() : |
| ReservedCodeSize(0), UsedCodeSize(0), ReservedDataSizeRO(0), |
| UsedDataSizeRO(0), ReservedDataSizeRW(0), UsedDataSizeRW(0) { |
| } |
| |
| bool needsToReserveAllocationSpace() override { return true; } |
| |
| void reserveAllocationSpace(uintptr_t CodeSize, uint32_t CodeAlign, |
| uintptr_t DataSizeRO, uint32_t RODataAlign, |
| uintptr_t DataSizeRW, |
| uint32_t RWDataAlign) override { |
| ReservedCodeSize = CodeSize; |
| ReservedDataSizeRO = DataSizeRO; |
| ReservedDataSizeRW = DataSizeRW; |
| } |
| |
| void useSpace(uintptr_t* UsedSize, uintptr_t Size, unsigned Alignment) { |
| uintptr_t AlignedSize = (Size + Alignment - 1) / Alignment * Alignment; |
| uintptr_t AlignedBegin = (*UsedSize + Alignment - 1) / Alignment * Alignment; |
| *UsedSize = AlignedBegin + AlignedSize; |
| } |
| |
| uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment, |
| unsigned SectionID, StringRef SectionName, |
| bool IsReadOnly) override { |
| useSpace(IsReadOnly ? &UsedDataSizeRO : &UsedDataSizeRW, Size, Alignment); |
| return SectionMemoryManager::allocateDataSection(Size, Alignment, |
| SectionID, SectionName, IsReadOnly); |
| } |
| |
| uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment, |
| unsigned SectionID, |
| StringRef SectionName) override { |
| useSpace(&UsedCodeSize, Size, Alignment); |
| return SectionMemoryManager::allocateCodeSection(Size, Alignment, |
| SectionID, SectionName); |
| } |
| }; |
| |
| 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::mips64); |
| SupportedArchs.push_back(Triple::mips64el); |
| 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. |
| UnsupportedEnvironments.push_back(Triple::Cygnus); |
| } |
| |
| void SetUp() override { |
| didCallAllocateCodeSection = false; |
| didAllocateCompactUnwindSection = false; |
| didCallYield = false; |
| Module = nullptr; |
| Function = nullptr; |
| Engine = nullptr; |
| Error = nullptr; |
| } |
| |
| void TearDown() override { |
| if (Engine) |
| LLVMDisposeExecutionEngine(Engine); |
| else if (Module) |
| LLVMDisposeModule(Module); |
| } |
| |
| void buildSimpleFunction() { |
| Module = LLVMModuleCreateWithName("simple_module"); |
| |
| LLVMSetTarget(Module, HostTriple.c_str()); |
| |
| Function = LLVMAddFunction(Module, "simple_function", |
| LLVMFunctionType(LLVMInt32Type(), nullptr,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); |
| } |
| |
| void buildFunctionThatUsesStackmap() { |
| Module = LLVMModuleCreateWithName("simple_module"); |
| |
| LLVMSetTarget(Module, HostTriple.c_str()); |
| |
| LLVMTypeRef stackmapParamTypes[] = { LLVMInt64Type(), LLVMInt32Type() }; |
| LLVMValueRef stackmap = LLVMAddFunction( |
| Module, "llvm.experimental.stackmap", |
| LLVMFunctionType(LLVMVoidType(), stackmapParamTypes, 2, 1)); |
| LLVMSetLinkage(stackmap, LLVMExternalLinkage); |
| |
| Function = LLVMAddFunction(Module, "simple_function", |
| LLVMFunctionType(LLVMInt32Type(), nullptr, 0, 0)); |
| |
| LLVMBasicBlockRef entry = LLVMAppendBasicBlock(Function, "entry"); |
| LLVMBuilderRef builder = LLVMCreateBuilder(); |
| LLVMPositionBuilderAtEnd(builder, entry); |
| LLVMValueRef stackmapArgs[] = { |
| LLVMConstInt(LLVMInt64Type(), 0, 0), LLVMConstInt(LLVMInt32Type(), 5, 0), |
| LLVMConstInt(LLVMInt32Type(), 42, 0) |
| }; |
| LLVMBuildCall(builder, stackmap, stackmapArgs, 3, ""); |
| LLVMBuildRet(builder, LLVMConstInt(LLVMInt32Type(), 42, 0)); |
| |
| LLVMVerifyModule(Module, LLVMAbortProcessAction, &Error); |
| LLVMDisposeMessage(Error); |
| |
| LLVMDisposeBuilder(builder); |
| } |
| |
| void buildModuleWithCodeAndData() { |
| Module = LLVMModuleCreateWithName("simple_module"); |
| |
| LLVMSetTarget(Module, HostTriple.c_str()); |
| |
| // build a global int32 variable initialized to 42. |
| LLVMValueRef GlobalVar = LLVMAddGlobal(Module, LLVMInt32Type(), "intVal"); |
| LLVMSetInitializer(GlobalVar, LLVMConstInt(LLVMInt32Type(), 42, 0)); |
| |
| { |
| Function = LLVMAddFunction(Module, "getGlobal", |
| LLVMFunctionType(LLVMInt32Type(), nullptr, 0, 0)); |
| LLVMSetFunctionCallConv(Function, LLVMCCallConv); |
| |
| LLVMBasicBlockRef Entry = LLVMAppendBasicBlock(Function, "entry"); |
| LLVMBuilderRef Builder = LLVMCreateBuilder(); |
| LLVMPositionBuilderAtEnd(Builder, Entry); |
| |
| LLVMValueRef IntVal = LLVMBuildLoad(Builder, GlobalVar, "intVal"); |
| LLVMBuildRet(Builder, IntVal); |
| |
| LLVMVerifyModule(Module, LLVMAbortProcessAction, &Error); |
| LLVMDisposeMessage(Error); |
| |
| LLVMDisposeBuilder(Builder); |
| } |
| |
| { |
| LLVMTypeRef ParamTypes[] = { LLVMInt32Type() }; |
| Function2 = LLVMAddFunction( |
| Module, "setGlobal", LLVMFunctionType(LLVMVoidType(), ParamTypes, 1, 0)); |
| LLVMSetFunctionCallConv(Function2, LLVMCCallConv); |
| |
| LLVMBasicBlockRef Entry = LLVMAppendBasicBlock(Function2, "entry"); |
| LLVMBuilderRef Builder = LLVMCreateBuilder(); |
| LLVMPositionBuilderAtEnd(Builder, Entry); |
| |
| LLVMValueRef Arg = LLVMGetParam(Function2, 0); |
| LLVMBuildStore(Builder, Arg, GlobalVar); |
| LLVMBuildRetVoid(Builder); |
| |
| LLVMVerifyModule(Module, LLVMAbortProcessAction, &Error); |
| LLVMDisposeMessage(Error); |
| |
| LLVMDisposeBuilder(Builder); |
| } |
| } |
| |
| void buildMCJITOptions() { |
| LLVMInitializeMCJITCompilerOptions(&Options, sizeof(Options)); |
| Options.OptLevel = 2; |
| |
| // Just ensure that this field still exists. |
| Options.NoFramePointerElim = false; |
| } |
| |
| void useRoundTripSectionMemoryManager() { |
| Options.MCJMM = LLVMCreateSimpleMCJITMemoryManager( |
| new SectionMemoryManager(), |
| roundTripAllocateCodeSection, |
| roundTripAllocateDataSection, |
| roundTripFinalizeMemory, |
| roundTripDestroy); |
| } |
| |
| void buildMCJITEngine() { |
| ASSERT_EQ( |
| 0, LLVMCreateMCJITCompilerForModule(&Engine, Module, &Options, |
| sizeof(Options), &Error)); |
| } |
| |
| void buildAndRunPasses() { |
| LLVMPassManagerRef pass = LLVMCreatePassManager(); |
| LLVMAddConstantPropagationPass(pass); |
| LLVMAddInstructionCombiningPass(pass); |
| LLVMRunPassManager(pass, Module); |
| LLVMDisposePassManager(pass); |
| } |
| |
| void buildAndRunOptPasses() { |
| LLVMPassManagerBuilderRef passBuilder; |
| |
| passBuilder = LLVMPassManagerBuilderCreate(); |
| LLVMPassManagerBuilderSetOptLevel(passBuilder, 2); |
| LLVMPassManagerBuilderSetSizeLevel(passBuilder, 0); |
| |
| LLVMPassManagerRef functionPasses = |
| LLVMCreateFunctionPassManagerForModule(Module); |
| LLVMPassManagerRef modulePasses = |
| LLVMCreatePassManager(); |
| |
| LLVMPassManagerBuilderPopulateFunctionPassManager(passBuilder, |
| functionPasses); |
| LLVMPassManagerBuilderPopulateModulePassManager(passBuilder, modulePasses); |
| |
| LLVMPassManagerBuilderDispose(passBuilder); |
| |
| LLVMInitializeFunctionPassManager(functionPasses); |
| for (LLVMValueRef value = LLVMGetFirstFunction(Module); |
| value; value = LLVMGetNextFunction(value)) |
| LLVMRunFunctionPassManager(functionPasses, value); |
| LLVMFinalizeFunctionPassManager(functionPasses); |
| |
| LLVMRunPassManager(modulePasses, Module); |
| |
| LLVMDisposePassManager(functionPasses); |
| LLVMDisposePassManager(modulePasses); |
| } |
| |
| LLVMModuleRef Module; |
| LLVMValueRef Function; |
| LLVMValueRef Function2; |
| LLVMMCJITCompilerOptions Options; |
| LLVMExecutionEngineRef Engine; |
| char *Error; |
| }; |
| } // end anonymous namespace |
| |
| TEST_F(MCJITCAPITest, simple_function) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| buildSimpleFunction(); |
| buildMCJITOptions(); |
| buildMCJITEngine(); |
| buildAndRunPasses(); |
| |
| auto *functionPointer = reinterpret_cast<int (*)()>( |
| reinterpret_cast<uintptr_t>(LLVMGetPointerToGlobal(Engine, Function))); |
| |
| EXPECT_EQ(42, functionPointer()); |
| } |
| |
| TEST_F(MCJITCAPITest, gva) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| Module = LLVMModuleCreateWithName("simple_module"); |
| LLVMSetTarget(Module, HostTriple.c_str()); |
| LLVMValueRef GlobalVar = LLVMAddGlobal(Module, LLVMInt32Type(), "simple_value"); |
| LLVMSetInitializer(GlobalVar, LLVMConstInt(LLVMInt32Type(), 42, 0)); |
| |
| buildMCJITOptions(); |
| buildMCJITEngine(); |
| buildAndRunPasses(); |
| |
| uint64_t raw = LLVMGetGlobalValueAddress(Engine, "simple_value"); |
| int32_t *usable = (int32_t *) raw; |
| |
| EXPECT_EQ(42, *usable); |
| } |
| |
| TEST_F(MCJITCAPITest, gfa) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| buildSimpleFunction(); |
| buildMCJITOptions(); |
| buildMCJITEngine(); |
| buildAndRunPasses(); |
| |
| uint64_t raw = LLVMGetFunctionAddress(Engine, "simple_function"); |
| int (*usable)() = (int (*)()) raw; |
| |
| EXPECT_EQ(42, usable()); |
| } |
| |
| TEST_F(MCJITCAPITest, custom_memory_manager) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| buildSimpleFunction(); |
| buildMCJITOptions(); |
| useRoundTripSectionMemoryManager(); |
| buildMCJITEngine(); |
| buildAndRunPasses(); |
| |
| auto *functionPointer = reinterpret_cast<int (*)()>( |
| reinterpret_cast<uintptr_t>(LLVMGetPointerToGlobal(Engine, Function))); |
| |
| EXPECT_EQ(42, functionPointer()); |
| EXPECT_TRUE(didCallAllocateCodeSection); |
| } |
| |
| TEST_F(MCJITCAPITest, stackmap_creates_compact_unwind_on_darwin) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| // This test is also not supported on non-x86 platforms. |
| if (Triple(HostTriple).getArch() != Triple::x86_64) |
| return; |
| |
| buildFunctionThatUsesStackmap(); |
| buildMCJITOptions(); |
| useRoundTripSectionMemoryManager(); |
| buildMCJITEngine(); |
| buildAndRunOptPasses(); |
| |
| auto *functionPointer = reinterpret_cast<int (*)()>( |
| reinterpret_cast<uintptr_t>(LLVMGetPointerToGlobal(Engine, Function))); |
| |
| EXPECT_EQ(42, functionPointer()); |
| EXPECT_TRUE(didCallAllocateCodeSection); |
| |
| // Up to this point, the test is specific only to X86-64. But this next |
| // expectation is only valid on Darwin because it assumes that unwind |
| // data is made available only through compact_unwind. It would be |
| // worthwhile to extend this to handle non-Darwin platforms, in which |
| // case you'd want to look for an eh_frame or something. |
| // |
| // FIXME: Currently, MCJIT relies on a configure-time check to determine which |
| // sections to emit. The JIT client should have runtime control over this. |
| EXPECT_TRUE( |
| Triple(HostTriple).getOS() != Triple::Darwin || |
| Triple(HostTriple).isMacOSXVersionLT(10, 7) || |
| didAllocateCompactUnwindSection); |
| } |
| |
| TEST_F(MCJITCAPITest, reserve_allocation_space) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| TestReserveAllocationSpaceMemoryManager* MM = new TestReserveAllocationSpaceMemoryManager(); |
| |
| buildModuleWithCodeAndData(); |
| buildMCJITOptions(); |
| Options.MCJMM = wrap(MM); |
| buildMCJITEngine(); |
| buildAndRunPasses(); |
| |
| auto GetGlobalFct = reinterpret_cast<int (*)()>( |
| reinterpret_cast<uintptr_t>(LLVMGetPointerToGlobal(Engine, Function))); |
| |
| auto SetGlobalFct = reinterpret_cast<void (*)(int)>( |
| reinterpret_cast<uintptr_t>(LLVMGetPointerToGlobal(Engine, Function2))); |
| |
| SetGlobalFct(789); |
| EXPECT_EQ(789, GetGlobalFct()); |
| EXPECT_LE(MM->UsedCodeSize, MM->ReservedCodeSize); |
| EXPECT_LE(MM->UsedDataSizeRO, MM->ReservedDataSizeRO); |
| EXPECT_LE(MM->UsedDataSizeRW, MM->ReservedDataSizeRW); |
| EXPECT_TRUE(MM->UsedCodeSize > 0); |
| EXPECT_TRUE(MM->UsedDataSizeRW > 0); |
| } |
| |
| TEST_F(MCJITCAPITest, yield) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| buildSimpleFunction(); |
| buildMCJITOptions(); |
| buildMCJITEngine(); |
| LLVMContextRef C = LLVMGetGlobalContext(); |
| LLVMContextSetYieldCallback(C, yield, nullptr); |
| buildAndRunPasses(); |
| |
| auto *functionPointer = reinterpret_cast<int (*)()>( |
| reinterpret_cast<uintptr_t>(LLVMGetPointerToGlobal(Engine, Function))); |
| |
| EXPECT_EQ(42, functionPointer()); |
| EXPECT_TRUE(didCallYield); |
| } |
| |
| static int localTestFunc() { |
| return 42; |
| } |
| |
| TEST_F(MCJITCAPITest, addGlobalMapping) { |
| SKIP_UNSUPPORTED_PLATFORM; |
| |
| Module = LLVMModuleCreateWithName("testModule"); |
| LLVMSetTarget(Module, HostTriple.c_str()); |
| LLVMTypeRef FunctionType = LLVMFunctionType(LLVMInt32Type(), nullptr, 0, 0); |
| LLVMValueRef MappedFn = LLVMAddFunction(Module, "mapped_fn", FunctionType); |
| |
| Function = LLVMAddFunction(Module, "test_fn", FunctionType); |
| LLVMBasicBlockRef Entry = LLVMAppendBasicBlock(Function, ""); |
| LLVMBuilderRef Builder = LLVMCreateBuilder(); |
| LLVMPositionBuilderAtEnd(Builder, Entry); |
| LLVMValueRef RetVal = LLVMBuildCall(Builder, MappedFn, nullptr, 0, ""); |
| LLVMBuildRet(Builder, RetVal); |
| LLVMDisposeBuilder(Builder); |
| |
| LLVMVerifyModule(Module, LLVMAbortProcessAction, &Error); |
| LLVMDisposeMessage(Error); |
| |
| buildMCJITOptions(); |
| buildMCJITEngine(); |
| |
| LLVMAddGlobalMapping( |
| Engine, MappedFn, |
| reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(&localTestFunc))); |
| |
| buildAndRunPasses(); |
| |
| uint64_t raw = LLVMGetFunctionAddress(Engine, "test_fn"); |
| int (*usable)() = (int (*)()) raw; |
| |
| EXPECT_EQ(42, usable()); |
| } |