//===-- SPSNativeMemoryMapTest.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 SPS serialization for MemoryFlags APIs. // //===----------------------------------------------------------------------===// #include "orc-rt/SimpleNativeMemoryMap.h" #include "orc-rt/SPSAllocAction.h" #include "orc-rt/SPSMemoryFlags.h" #include "AllocActionTestUtils.h" #include "DirectCaller.h" #include "gtest/gtest.h" #include using namespace orc_rt; namespace orc_rt { struct SPSSimpleNativeMemoryMapSegment; /// A SimpleNativeMemoryMap::InitializeRequest::Segment plus segment content (if /// segment content type is regular). struct TestSNMMSegment : public SimpleNativeMemoryMap::InitializeRequest::Segment { TestSNMMSegment(AllocGroup AG, char *Address, size_t Size, std::vector C = {}) : SimpleNativeMemoryMap::InitializeRequest::Segment( {AG, Address, Size, {}}), OwnedContent(std::move(C)) { this->Content = {OwnedContent.data(), OwnedContent.size()}; } std::vector OwnedContent; }; template <> class SPSSerializationTraits { using SPSType = SPSTuple>; public: static size_t size(const TestSNMMSegment &S) { return SPSType::AsArgList::size(S.AG, ExecutorAddr::fromPtr(S.Address), static_cast(S.Size), S.Content); } static bool serialize(SPSOutputBuffer &OB, const TestSNMMSegment &S) { return SPSType::AsArgList::serialize( OB, S.AG, ExecutorAddr::fromPtr(S.Address), static_cast(S.Size), S.Content); } }; struct SPSSimpleNativeMemoryMapInitializeRequest; struct TestSNMMInitializeRequest { std::vector Segments; std::vector AAPs; }; template <> class SPSSerializationTraits { using SPSType = SPSTuple, SPSSequence>; public: static size_t size(const TestSNMMInitializeRequest &IR) { return SPSType::AsArgList::size(IR.Segments, IR.AAPs); } static bool serialize(SPSOutputBuffer &OB, const TestSNMMInitializeRequest &IR) { return SPSType::AsArgList::serialize(OB, IR.Segments, IR.AAPs); } }; } // namespace orc_rt template move_only_function waitFor(std::future &F) { std::promise P; F = P.get_future(); return [P = std::move(P)](T Val) mutable { P.set_value(std::move(Val)); }; } TEST(SimpleNativeMemoryMapTest, CreateAndDestroy) { // Test that we can create and destroy a SimpleNativeMemoryMap instance as // expected. auto SNMM = std::make_unique(); } template static void snmm_reserve(OnCompleteFn &&OnComplete, SimpleNativeMemoryMap *Instance, size_t Size) { using SPSSig = SPSExpected(SPSExecutorAddr, SPSSize); SPSWrapperFunction::call( DirectCaller(nullptr, orc_rt_SimpleNativeMemoryMap_reserve_sps_wrapper), std::forward(OnComplete), Instance, Size); } template static void snmm_releaseMultiple(OnCompleteFn &&OnComplete, SimpleNativeMemoryMap *Instance, span Addr) { using SPSSig = SPSError(SPSExecutorAddr, SPSSequence); SPSWrapperFunction::call( DirectCaller(nullptr, orc_rt_SimpleNativeMemoryMap_releaseMultiple_sps_wrapper), std::forward(OnComplete), Instance, Addr); } template static void snmm_initialize(OnCompleteFn &&OnComplete, SimpleNativeMemoryMap *Instance, TestSNMMInitializeRequest IR) { using SPSSig = SPSExpected( SPSExecutorAddr, SPSSimpleNativeMemoryMapInitializeRequest); SPSWrapperFunction::call( DirectCaller(nullptr, orc_rt_SimpleNativeMemoryMap_initialize_sps_wrapper), std::forward(OnComplete), Instance, std::move(IR)); } template static void snmm_deinitializeMultiple(OnCompleteFn &&OnComplete, SimpleNativeMemoryMap *Instance, span Base) { using SPSSig = SPSError(SPSExecutorAddr, SPSSequence); SPSWrapperFunction::call( DirectCaller( nullptr, orc_rt_SimpleNativeMemoryMap_deinitializeMultiple_sps_wrapper), std::forward(OnComplete), Instance, Base); } TEST(SimpleNativeMemoryMapTest, ReserveAndRelease) { // Test that we can reserve and release a slab of address space as expected, // without finalizing any memory within it. auto SNMM = std::make_unique(); std::future>> ReserveAddr; snmm_reserve(waitFor(ReserveAddr), SNMM.get(), 1024 * 1024 * 1024); auto Addr = cantFail(cantFail(ReserveAddr.get())); std::future> ReleaseResult; snmm_releaseMultiple(waitFor(ReleaseResult), SNMM.get(), {&Addr, 1}); cantFail(cantFail(ReleaseResult.get())); } // 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. // Increments int via pointer. 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(SimpleNativeMemoryMap, 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. auto SNMM = std::make_unique(); std::future>> ReserveAddr; snmm_reserve(waitFor(ReserveAddr), SNMM.get(), 1024 * 1024 * 1024); void *Addr = cantFail(cantFail(ReserveAddr.get())); std::future>> InitializeKey; TestSNMMInitializeRequest IR; 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)); IR.Segments.push_back({MemProt::Read | MemProt::Write, InitializeBase, 64 * 1024, std::move(Content)}); // 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. }); snmm_initialize(waitFor(InitializeKey), SNMM.get(), std::move(IR)); void *InitializeKeyAddr = cantFail(cantFail(InitializeKey.get())); EXPECT_EQ(SentinelValue1, 42U); EXPECT_EQ(SentinelValue2, 0U); EXPECT_EQ(SentinelValue3, 0U); std::future> DeallocResult; snmm_deinitializeMultiple(waitFor(DeallocResult), SNMM.get(), {&InitializeKeyAddr, 1}); cantFail(cantFail(DeallocResult.get())); EXPECT_EQ(SentinelValue1, 42U); EXPECT_EQ(SentinelValue2, 42U); EXPECT_EQ(SentinelValue3, 0U); std::future> ReleaseResult; snmm_releaseMultiple(waitFor(ReleaseResult), SNMM.get(), {&Addr, 1}); cantFail(cantFail(ReleaseResult.get())); } TEST(SimpleNativeMemoryMap, ReserveInitializeShutdown) { // Test that memory is deinitialized in the case where we reserve and // initialize some memory, then just shut down the memory manager. auto SNMM = std::make_unique(); std::future>> ReserveAddr; snmm_reserve(waitFor(ReserveAddr), SNMM.get(), 1024 * 1024 * 1024); void *Addr = cantFail(cantFail(ReserveAddr.get())); std::future>> InitializeKey; TestSNMMInitializeRequest IR; char *InitializeBase = // Initialize addr at non-zero (64kb) offset from base. reinterpret_cast(Addr) + 64 * 1024; uint64_t SentinelValue = 0; 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))}); snmm_initialize(waitFor(InitializeKey), SNMM.get(), std::move(IR)); cantFail(cantFail(InitializeKey.get())); EXPECT_EQ(SentinelValue, 0U); std::future ShutdownResult; SNMM->shutdown(waitFor(ShutdownResult)); cantFail(ShutdownResult.get()); EXPECT_EQ(SentinelValue, 42); } TEST(SimpleNativeMemoryMap, ReserveInitializeDetachShutdown) { // Test that memory is deinitialized in the case where we reserve and // initialize some memory, then just shut down the memory manager. auto SNMM = std::make_unique(); std::future>> ReserveAddr; snmm_reserve(waitFor(ReserveAddr), SNMM.get(), 1024 * 1024 * 1024); void *Addr = cantFail(cantFail(ReserveAddr.get())); std::future>> InitializeKey; TestSNMMInitializeRequest IR; char *InitializeBase = // Initialize addr at non-zero (64kb) offset from base. reinterpret_cast(Addr) + 64 * 1024; uint64_t SentinelValue = 0; 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))}); snmm_initialize(waitFor(InitializeKey), SNMM.get(), std::move(IR)); cantFail(cantFail(InitializeKey.get())); EXPECT_EQ(SentinelValue, 0U); std::future DetachResult; SNMM->detach(waitFor(DetachResult)); cantFail(DetachResult.get()); EXPECT_EQ(SentinelValue, 0); std::future ShutdownResult; SNMM->shutdown(waitFor(ShutdownResult)); cantFail(ShutdownResult.get()); EXPECT_EQ(SentinelValue, 42); }