diff options
author | Jeffrey Yasskin <jyasskin@google.com> | 2009-11-16 22:41:33 +0000 |
---|---|---|
committer | Jeffrey Yasskin <jyasskin@google.com> | 2009-11-16 22:41:33 +0000 |
commit | d1ba06bf131a9d217426529d2e28af1f2eeed47a (patch) | |
tree | 72ae565430358edb4e81b988c8725938c1f60763 /unittests | |
parent | da589a3a963e6cc179d850c5fd395d3e10ce741c (diff) | |
download | external_llvm-d1ba06bf131a9d217426529d2e28af1f2eeed47a.zip external_llvm-d1ba06bf131a9d217426529d2e28af1f2eeed47a.tar.gz external_llvm-d1ba06bf131a9d217426529d2e28af1f2eeed47a.tar.bz2 |
Make X86-64 in the Large model always emit 64-bit calls.
The large code model is documented at
http://www.x86-64.org/documentation/abi.pdf and says that calls should
assume their target doesn't live within the 32-bit pc-relative offset
that fits in the call instruction.
To do this, we turn off the global-address->target-global-address
conversion in X86TargetLowering::LowerCall(). The first attempt at
this broke the lazy JIT because it can separate the movabs(imm->reg)
from the actual call instruction. The lazy JIT receives the address of
the movabs as a relocation and needs to record the return address from
the call; and then when that call happens, it needs to patch the
movabs with the newly-compiled target. We could thread the call
instruction into the relocation and record the movabs<->call mapping
explicitly, but that seems to require at least as much new
complication in the code generator as this change.
To fix this, we make lazy functions _always_ go through a call
stub. You'd think we'd only have to force lazy calls through a stub on
difficult platforms, but that turns out to break indirect calls
through a function pointer. The right fix for that is to distinguish
between calls and address-of operations on uncompiled functions, but
that's complex enough to leave for someone else to do.
Another attempt at this defined a new CALL64i pseudo-instruction,
which expanded to a 2-instruction sequence in the assembly output and
was special-cased in the X86CodeEmitter's emitInstruction()
function. That broke indirect calls in the same way as above.
This patch also removes a hack forcing Darwin to the small code model.
Without far-call-stubs, the small code model requires things of the
JITMemoryManager that the DefaultJITMemoryManager can't provide.
Thanks to echristo for lots of testing!
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@88984 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'unittests')
-rw-r--r-- | unittests/ExecutionEngine/JIT/JITTest.cpp | 157 |
1 files changed, 151 insertions, 6 deletions
diff --git a/unittests/ExecutionEngine/JIT/JITTest.cpp b/unittests/ExecutionEngine/JIT/JITTest.cpp index 98b2922..b0c2f24 100644 --- a/unittests/ExecutionEngine/JIT/JITTest.cpp +++ b/unittests/ExecutionEngine/JIT/JITTest.cpp @@ -26,10 +26,22 @@ #include "llvm/Support/IRBuilder.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/TypeBuilder.h" +#include "llvm/Target/TargetMachine.h" #include "llvm/Target/TargetSelect.h" #include "llvm/Type.h" #include <vector> +#include <string.h> + +#if HAVE_ERRNO_H +#include <errno.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if _POSIX_MAPPED_FILES > 0 +#include <sys/mman.h> +#endif using namespace llvm; @@ -177,6 +189,15 @@ public: } }; +void LoadAssemblyInto(Module *M, const char *assembly) { + SMDiagnostic Error; + bool success = NULL != ParseAssemblyString(assembly, M, Error, M->getContext()); + std::string errMsg; + raw_string_ostream os(errMsg); + Error.Print("", os); + ASSERT_TRUE(success) << os.str(); +} + class JITTest : public testing::Test { protected: virtual void SetUp() { @@ -191,12 +212,7 @@ class JITTest : public testing::Test { } 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; @@ -498,6 +514,135 @@ TEST_F(JITTest, NoStubs) { } #endif +#if _POSIX_MAPPED_FILES > 0 && (defined (__x86_64__) || defined (_M_AMD64) || defined (_M_X64)) +class FarCallMemMgr : public RecordingJITMemoryManager { + void *MmapRegion; + size_t MmapSize; + uint8_t *NextStub; + uint8_t *NextFunction; + + public: + FarCallMemMgr() + : MmapSize(16ULL << 30) { // 16GB + MmapRegion = mmap(NULL, MmapSize, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (MmapRegion == MAP_FAILED) { + ADD_FAILURE() << "mmap failed: " << strerror(errno); + } + // Set up the 16GB mapped region in several chunks: + // Stubs / ~5GB empty space / Function 1 / ~5GB empty space / Function 2 + // This way no two entities can use a 32-bit relative call to reach each other. + NextStub = static_cast<uint8_t*>(MmapRegion); + NextFunction = NextStub + (5ULL << 30); + + // Next, poison some of the memory so a wild call will eventually crash, + // even if memory was initialized by the OS to 0. We can't poison all of + // the memory because we want to be able to run on systems with less than + // 16GB of physical ram. + int TrapInstr = 0xCC; // INT 3 + memset(NextStub, TrapInstr, 1<<10); + for (size_t Offset = 1<<30; Offset < MmapSize; Offset += 1<<30) { + // Fill the 2KB around each GB boundary with trap instructions. This + // should ensure that we can't run into emitted functions without hitting + // the trap. + memset(NextStub + Offset - (1<<10), TrapInstr, 2<<10); + } + } + + ~FarCallMemMgr() { + EXPECT_EQ(0, munmap(MmapRegion, MmapSize)); + } + + virtual void setMemoryWritable() {} + virtual void setMemoryExecutable() {} + virtual uint8_t *startFunctionBody(const Function *F, + uintptr_t &ActualSize) { + ActualSize = 1 << 30; + uint8_t *Result = NextFunction; + NextFunction += 5ULL << 30; + return Result; + } + virtual void endFunctionBody(const Function*, uint8_t*, uint8_t*) {} + virtual uint8_t *allocateStub(const GlobalValue* F, unsigned StubSize, + unsigned Alignment) { + NextStub = reinterpret_cast<uint8_t*>( + uintptr_t(NextStub + Alignment - 1) &~ uintptr_t(Alignment - 1)); + uint8_t *Result = NextStub; + NextStub += StubSize; + return Result; + } +}; + +class FarTargetTest : public ::testing::TestWithParam<CodeGenOpt::Level> { + protected: + FarTargetTest() : SavedCodeModel(TargetMachine::getCodeModel()) {} + ~FarTargetTest() { + TargetMachine::setCodeModel(SavedCodeModel); + } + + const CodeModel::Model SavedCodeModel; +}; +INSTANTIATE_TEST_CASE_P(CodeGenOpt, + FarTargetTest, + ::testing::Values(CodeGenOpt::None, + CodeGenOpt::Default)); + +TEST_P(FarTargetTest, CallToFarTarget) { + // x86-64 can only make direct calls to functions within 32 bits of + // the current PC. To call anything farther away, we have to load + // the address into a register and call through the register. The + // old JIT did this by allocating a stub for any far call. However, + // that stub needed to be within 32 bits of the callsite. Here we + // test that the JIT correctly deals with stubs and calls more than + // 32 bits away from the callsite. + + // Make sure the code generator is assuming code might be far away. + //TargetMachine::setCodeModel(CodeModel::Large); + + LLVMContext Context; + Module *M = new Module("<main>", Context); + ExistingModuleProvider *MP = new ExistingModuleProvider(M); + + JITMemoryManager *MemMgr = new FarCallMemMgr(); + std::string Error; + OwningPtr<ExecutionEngine> JIT(EngineBuilder(MP) + .setEngineKind(EngineKind::JIT) + .setErrorStr(&Error) + .setJITMemoryManager(MemMgr) + .setOptLevel(GetParam()) + .create()); + ASSERT_EQ(Error, ""); + TargetMachine::setCodeModel(CodeModel::Large); + + LoadAssemblyInto(M, + "define i32 @test() { " + " ret i32 7 " + "} " + " " + "define i32 @test_far() { " + " %result = call i32 @test() " + " ret i32 %result " + "} "); + // First, lay out a function early in memory. + Function *TestFunction = M->getFunction("test"); + int32_t (*TestFunctionPtr)() = reinterpret_cast<int32_t(*)()>( + (intptr_t)JIT->getPointerToFunction(TestFunction)); + ASSERT_EQ(7, TestFunctionPtr()); + + // We now lay out the far-away function. This should land >4GB away from test(). + Function *FarFunction = M->getFunction("test_far"); + int32_t (*FarFunctionPtr)() = reinterpret_cast<int32_t(*)()>( + (intptr_t)JIT->getPointerToFunction(FarFunction)); + + EXPECT_LT(1LL << 32, llabs(intptr_t(FarFunctionPtr) - intptr_t(TestFunctionPtr))) + << "Functions must be >32 bits apart or the test is meaningless."; + + // This used to result in a segfault in FarFunction, when its call instruction + // jumped to the wrong address. + EXPECT_EQ(7, FarFunctionPtr()); +} +#endif // Platform has far-call problem. + // 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. |