Files
llvm-project/orc-rt/unittests/SimpleNativeMemoryMapTest.cpp
Lang Hames 6bfb44f320 [orc-rt] Add ShutdownRequested flag to Service::onDetach. (#187230)
The ShutdownRequested flag indicates to Services whether a shutdown
operation is already pending. Services may use this information to
optimize their book-keeping: either preparing for a (potentially
lengthy) detached state, or for an upcoming shutdown.

Session does not call onDetached yet: That (including setting the
ShutdownRequested argument) will happen in a follow-up patch.
2026-03-18 21:39:21 +11:00

331 lines
12 KiB
C++

//===-- 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 <cstring>
#include <string>
#include <vector>
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<SPSExecutorAddr, uint64_t>::handle(
ArgData, ArgSize,
[](ExecutorAddr P, uint64_t Val) {
*P.toPtr<uint64_t *>() = 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<SPSExecutorAddr, SPSExecutorAddr>::handle(
ArgData, ArgSize,
[](ExecutorAddr Dst, ExecutorAddr Src) {
*Dst.toPtr<uint64_t *>() = *Src.toPtr<uint64_t *>();
return WrapperFunctionBuffer();
})
.release();
}
TEST(SimpleNativeMemoryMapTest, CreateAndDestroy) {
// Test that we can create and destroy a SimpleNativeMemoryMap instance as
// expected.
Session S(mockExecutorProcessInfo(), std::make_unique<NoDispatcher>(),
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<NoDispatcher>(),
noErrors);
SimpleSymbolTable ThrowAway;
auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway));
std::future<Expected<void *>> ReserveResult;
SNMM->reserve(waitFor(ReserveResult), 1024 * 1024 * 1024);
void *Addr = cantFail(ReserveResult.get());
std::future<Error> 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<NoDispatcher>(),
noErrors);
SimpleSymbolTable ThrowAway;
auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway));
std::future<Expected<void *>> 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<char *>(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<char> 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<SPSExecutorAddr, SPSExecutorAddr>::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<SPSExecutorAddr, uint64_t>::from(
write_value_sps_allocaction,
ExecutorAddr::fromPtr(InitializeBase) + sizeof(uint64_t),
uint64_t(42)),
*MakeAllocAction<SPSExecutorAddr, SPSExecutorAddr>::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<SPSExecutorAddr, SPSExecutorAddr>::from(
read_value_sps_allocaction, ExecutorAddr::fromPtr(&SentinelValue3),
ExecutorAddr::fromPtr(InitializeBase) + sizeof(uint64_t) * 2),
{} // No dealloc action.
});
std::future<Expected<void *>> 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<Error> DeallocResult;
SNMM->deinitializeMultiple(waitFor(DeallocResult), {InitializeKeyAddr});
cantFail(DeallocResult.get());
EXPECT_EQ(SentinelValue1, 42U);
EXPECT_EQ(SentinelValue2, 42U);
EXPECT_EQ(SentinelValue3, 0U);
std::future<Error> 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<NoDispatcher>(),
noErrors);
SimpleSymbolTable ThrowAway;
auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway));
std::future<Expected<void *>> 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<NoDispatcher>(),
noErrors);
SimpleSymbolTable ThrowAway;
auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway));
std::future<Expected<void *>> ReserveResult;
SNMM->reserve(waitFor(ReserveResult), S.processInfo().pageSize());
void *Addr = cantFail(ReserveResult.get());
std::future<Error> 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<std::string> Errors;
Session S(mockExecutorProcessInfo(), std::make_unique<NoDispatcher>(),
[&](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<Error> 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<std::string> Errors;
Session S(mockExecutorProcessInfo(), std::make_unique<NoDispatcher>(),
[&](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<Expected<void *>> 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<Error> DeinitResult;
SNMM->deinitializeMultiple(waitFor(DeinitResult), {Addr});
auto Err = DeinitResult.get();
EXPECT_TRUE(!!Err);
consumeError(std::move(Err));
EXPECT_EQ(Errors.size(), 1U);
std::future<Error> 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<NoDispatcher>(),
noErrors);
SimpleSymbolTable ThrowAway;
auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway));
std::future<Expected<void *>> 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<char *>(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<SPSExecutorAddr, uint64_t>::from(
write_value_sps_allocaction, ExecutorAddr::fromPtr(InitializeBase),
uint64_t(42)),
*MakeAllocAction<SPSExecutorAddr, SPSExecutorAddr>::from(
read_value_sps_allocaction, ExecutorAddr::fromPtr(&SentinelValue),
ExecutorAddr::fromPtr(InitializeBase))});
std::future<Expected<void *>> InitializeResult;
SNMM->initialize(waitFor(InitializeResult), std::move(IR));
cantFail(InitializeResult.get());
EXPECT_EQ(SentinelValue, 0U);
std::future<void> 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<NoDispatcher>(),
noErrors);
SimpleSymbolTable ThrowAway;
auto SNMM = cantFail(SimpleNativeMemoryMap::Create(S, ThrowAway));
std::future<Expected<void *>> 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<char *>(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<SPSExecutorAddr, uint64_t>::from(
write_value_sps_allocaction, ExecutorAddr::fromPtr(InitializeBase),
uint64_t(42)),
*MakeAllocAction<SPSExecutorAddr, SPSExecutorAddr>::from(
read_value_sps_allocaction, ExecutorAddr::fromPtr(&SentinelValue),
ExecutorAddr::fromPtr(InitializeBase))});
std::future<Expected<void *>> InitializeResult;
SNMM->initialize(waitFor(InitializeResult), std::move(IR));
cantFail(InitializeResult.get());
EXPECT_EQ(SentinelValue, 0U);
std::future<void> DetachResult;
SNMM->onDetach(waitFor(DetachResult), /* ShutdownRequested */ false);
DetachResult.get();
EXPECT_EQ(SentinelValue, 0);
std::future<void> ShutdownResult;
SNMM->onShutdown(waitFor(ShutdownResult));
ShutdownResult.get();
EXPECT_EQ(SentinelValue, 42);
}