//===-- SimpleNativeMemoryMapTest.cpp -------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Test SimpleNativeMemoryMap APIs. // //===----------------------------------------------------------------------===// #include "orc-rt/SimpleNativeMemoryMap.h" #include "orc-rt/SPSAllocAction.h" #include "orc-rt/Session.h" #include "AllocActionTestUtils.h" #include "CommonTestUtils.h" #include "gtest/gtest.h" #include #include #include using namespace orc_rt; // Write the given value to the address pointed to by P. static orc_rt_WrapperFunctionBuffer write_value_sps_allocaction(const char *ArgData, size_t ArgSize) { return SPSAllocActionFunction::handle( ArgData, ArgSize, [](ExecutorAddr P, uint64_t Val) { *P.toPtr() = Val; return WrapperFunctionBuffer(); }) .release(); } // Read the uint64_t value at Src and write it to Dst. static orc_rt_WrapperFunctionBuffer read_value_sps_allocaction(const char *ArgData, size_t ArgSize) { return SPSAllocActionFunction::handle( ArgData, ArgSize, [](ExecutorAddr Dst, ExecutorAddr Src) { *Dst.toPtr() = *Src.toPtr(); return WrapperFunctionBuffer(); }) .release(); } TEST(SimpleNativeMemoryMapTest, CreateAndDestroy) { // Test that we can create and destroy a SimpleNativeMemoryMap instance as // expected. Session S(mockExecutorProcessInfo(), std::make_unique(), noErrors); SimpleSymbolTable ThrowAway; auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway)); } TEST(SimpleNativeMemoryMapTest, ReserveAndRelease) { // Test that we can reserve and release a slab of address space as expected, // without finalizing any memory within it. Session S(mockExecutorProcessInfo(), std::make_unique(), noErrors); SimpleSymbolTable ThrowAway; auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway)); std::future> ReserveResult; SNMM->reserve(waitFor(ReserveResult), 1024 * 1024 * 1024); void *Addr = cantFail(ReserveResult.get()); std::future ReleaseResult; SNMM->releaseMultiple(waitFor(ReleaseResult), {Addr}); cantFail(ReleaseResult.get()); } TEST(SimpleNativeMemoryMapTest, FullPipelineForOneRWSegment) { // Test that we can: // 1. reserve some address space. // 2. initialize a range within it as read/write, and that finalize actions // are applied as expected. // 3. deinitialize the initialized range, with deallocation actions applied as // expected. // 4. release the address range. Session S(mockExecutorProcessInfo(), std::make_unique(), noErrors); SimpleSymbolTable ThrowAway; auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway)); std::future> ReserveResult; SNMM->reserve(waitFor(ReserveResult), 1024 * 1024 * 1024); void *Addr = cantFail(ReserveResult.get()); char *InitializeBase = // Initialize addr at non-zero (64kb) offset from base. reinterpret_cast(Addr) + 64 * 1024; uint64_t SentinelValue1 = 0; // Read from pre-filled content uint64_t SentinelValue2 = 0; // Written in initialize, read back during dealloc. uint64_t SentinelValue3 = 42; // Read from zero-filled region. // Build initial content vector. std::vector Content; Content.resize(sizeof(uint64_t) * 2); memcpy(Content.data(), &SentinelValue3, sizeof(uint64_t)); memcpy(Content.data() + sizeof(uint64_t), &SentinelValue1, sizeof(uint64_t)); SimpleNativeMemoryMap::InitializeRequest IR; IR.Segments.push_back({MemProt::Read | MemProt::Write, InitializeBase, 64 * 1024, {Content.data(), Content.size()}}); // Read initial content into Sentinel 1. IR.AAPs.push_back({ *MakeAllocAction::from( read_value_sps_allocaction, ExecutorAddr::fromPtr(&SentinelValue1), ExecutorAddr::fromPtr(InitializeBase)), {} // No dealloc action. }); // Write value in finalize action, then read back into Sentinel 2. IR.AAPs.push_back( {*MakeAllocAction::from( write_value_sps_allocaction, ExecutorAddr::fromPtr(InitializeBase) + sizeof(uint64_t), uint64_t(42)), *MakeAllocAction::from( read_value_sps_allocaction, ExecutorAddr::fromPtr(&SentinelValue2), ExecutorAddr::fromPtr(InitializeBase) + sizeof(uint64_t))}); // Read first 64 bits of the zero-fill region. IR.AAPs.push_back({ *MakeAllocAction::from( read_value_sps_allocaction, ExecutorAddr::fromPtr(&SentinelValue3), ExecutorAddr::fromPtr(InitializeBase) + sizeof(uint64_t) * 2), {} // No dealloc action. }); std::future> InitializeResult; SNMM->initialize(waitFor(InitializeResult), std::move(IR)); void *InitializeKeyAddr = cantFail(InitializeResult.get()); EXPECT_EQ(SentinelValue1, 42U); EXPECT_EQ(SentinelValue2, 0U); EXPECT_EQ(SentinelValue3, 0U); std::future DeallocResult; SNMM->deinitializeMultiple(waitFor(DeallocResult), {InitializeKeyAddr}); cantFail(DeallocResult.get()); EXPECT_EQ(SentinelValue1, 42U); EXPECT_EQ(SentinelValue2, 42U); EXPECT_EQ(SentinelValue3, 0U); std::future ReleaseResult; SNMM->releaseMultiple(waitFor(ReleaseResult), {Addr}); cantFail(ReleaseResult.get()); } TEST(SimpleNativeMemoryMapTest, ReserveRejectsNonPageSizeMultiple) { // Verify that reserve rejects sizes that aren't page-size multiples. Session S(mockExecutorProcessInfo(), std::make_unique(), noErrors); SimpleSymbolTable ThrowAway; auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway)); std::future> ReserveResult; SNMM->reserve(waitFor(ReserveResult), S.processInfo().pageSize() + 1); auto Result = ReserveResult.get(); EXPECT_FALSE(!!Result); consumeError(Result.takeError()); } TEST(SimpleNativeMemoryMapTest, ReserveAcceptsPageSizeMultiple) { // Verify that reserve accepts a size that's an exact page-size multiple. Session S(mockExecutorProcessInfo(), std::make_unique(), noErrors); SimpleSymbolTable ThrowAway; auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway)); std::future> ReserveResult; SNMM->reserve(waitFor(ReserveResult), S.processInfo().pageSize()); void *Addr = cantFail(ReserveResult.get()); std::future ReleaseResult; SNMM->releaseMultiple(waitFor(ReleaseResult), {Addr}); cantFail(ReleaseResult.get()); } TEST(SimpleNativeMemoryMapTest, ReleaseMultipleReportsErrors) { // Test that releaseMultiple reports errors via Session::reportError // when some addresses aren't recognized. std::vector Errors; Session S(mockExecutorProcessInfo(), std::make_unique(), [&](Error Err) { Errors.push_back(toString(std::move(Err))); }); SimpleSymbolTable ThrowAway; auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway)); // Try to release an address that was never reserved. int Dummy; std::future ReleaseResult; SNMM->releaseMultiple(waitFor(ReleaseResult), {&Dummy}); auto Err = ReleaseResult.get(); EXPECT_TRUE(!!Err); consumeError(std::move(Err)); // The error for the unrecognized address should have been reported // via reportError (not silently consumed). EXPECT_EQ(Errors.size(), 1U); } TEST(SimpleNativeMemoryMapTest, DeinitializeMultipleReportsErrors) { // Test that deinitializeMultiple reports errors via Session::reportError // when some addresses aren't recognized. std::vector Errors; Session S(mockExecutorProcessInfo(), std::make_unique(), [&](Error Err) { Errors.push_back(toString(std::move(Err))); }); SimpleSymbolTable ThrowAway; auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway)); // Reserve and initialize a slab so we have a valid context. std::future> ReserveResult; SNMM->reserve(waitFor(ReserveResult), 1024 * 1024 * 1024); void *Addr = cantFail(ReserveResult.get()); // Try to deinitialize an address that was never initialized. // This should fail and report the error. std::future DeinitResult; SNMM->deinitializeMultiple(waitFor(DeinitResult), {Addr}); auto Err = DeinitResult.get(); EXPECT_TRUE(!!Err); consumeError(std::move(Err)); EXPECT_EQ(Errors.size(), 1U); std::future ReleaseResult; SNMM->releaseMultiple(waitFor(ReleaseResult), {Addr}); cantFail(ReleaseResult.get()); } TEST(SimpleNativeMemoryMapTest, ReserveInitializeShutdown) { // Test that memory is deinitialized in the case where we reserve and // initialize some memory, then just shut down the memory manager. Session S(mockExecutorProcessInfo(), std::make_unique(), noErrors); SimpleSymbolTable ThrowAway; auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway)); std::future> ReserveResult; SNMM->reserve(waitFor(ReserveResult), 1024 * 1024 * 1024); void *Addr = cantFail(ReserveResult.get()); char *InitializeBase = // Initialize addr at non-zero (64kb) offset from base. reinterpret_cast(Addr) + 64 * 1024; uint64_t SentinelValue = 0; SimpleNativeMemoryMap::InitializeRequest IR; IR.Segments.push_back( {MemProt::Read | MemProt::Write, InitializeBase, 64 * 1024, {}}); IR.AAPs.push_back( {*MakeAllocAction::from( write_value_sps_allocaction, ExecutorAddr::fromPtr(InitializeBase), uint64_t(42)), *MakeAllocAction::from( read_value_sps_allocaction, ExecutorAddr::fromPtr(&SentinelValue), ExecutorAddr::fromPtr(InitializeBase))}); std::future> InitializeResult; SNMM->initialize(waitFor(InitializeResult), std::move(IR)); cantFail(InitializeResult.get()); EXPECT_EQ(SentinelValue, 0U); std::future ShutdownResult; SNMM->onShutdown(waitFor(ShutdownResult)); ShutdownResult.get(); EXPECT_EQ(SentinelValue, 42); } TEST(SimpleNativeMemoryMapTest, ReserveInitializeDetachShutdown) { // Test that memory is deinitialized in the case where we reserve and // initialize some memory, then just shut down the memory manager. Session S(mockExecutorProcessInfo(), std::make_unique(), noErrors); SimpleSymbolTable ThrowAway; auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway)); std::future> ReserveResult; SNMM->reserve(waitFor(ReserveResult), 1024 * 1024 * 1024); void *Addr = cantFail(ReserveResult.get()); char *InitializeBase = // Initialize addr at non-zero (64kb) offset from base. reinterpret_cast(Addr) + 64 * 1024; uint64_t SentinelValue = 0; SimpleNativeMemoryMap::InitializeRequest IR; IR.Segments.push_back( {MemProt::Read | MemProt::Write, InitializeBase, 64 * 1024, {}}); IR.AAPs.push_back( {*MakeAllocAction::from( write_value_sps_allocaction, ExecutorAddr::fromPtr(InitializeBase), uint64_t(42)), *MakeAllocAction::from( read_value_sps_allocaction, ExecutorAddr::fromPtr(&SentinelValue), ExecutorAddr::fromPtr(InitializeBase))}); std::future> InitializeResult; SNMM->initialize(waitFor(InitializeResult), std::move(IR)); cantFail(InitializeResult.get()); EXPECT_EQ(SentinelValue, 0U); std::future DetachResult; SNMM->onDetach(waitFor(DetachResult), /* ShutdownRequested */ false); DetachResult.get(); EXPECT_EQ(SentinelValue, 0); std::future ShutdownResult; SNMM->onShutdown(waitFor(ShutdownResult)); ShutdownResult.get(); EXPECT_EQ(SentinelValue, 42); }