Compiler projects using llvm
//===------------------------ MemoryMapperTest.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
//
//===----------------------------------------------------------------------===//

#include "llvm/ExecutionEngine/Orc/MemoryMapper.h"
#include "llvm/Support/Process.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"

using namespace llvm;
using namespace llvm::orc;
using namespace llvm::orc::shared;

namespace {

Expected<ExecutorAddrRange> reserve(MemoryMapper &M, size_t NumBytes) {
  std::promise<MSVCPExpected<ExecutorAddrRange>> P;
  auto F = P.get_future();
  M.reserve(NumBytes, [&](auto R) { P.set_value(std::move(R)); });
  return F.get();
}

Expected<ExecutorAddr> initialize(MemoryMapper &M,
                                  MemoryMapper::AllocInfo &AI) {
  std::promise<MSVCPExpected<ExecutorAddr>> P;
  auto F = P.get_future();
  M.initialize(AI, [&](auto R) { P.set_value(std::move(R)); });
  return F.get();
}

Error deinitialize(MemoryMapper &M,
                   const std::vector<ExecutorAddr> &Allocations) {
  std::promise<MSVCPError> P;
  auto F = P.get_future();
  M.deinitialize(Allocations, [&](auto R) { P.set_value(std::move(R)); });
  return F.get();
}

Error release(MemoryMapper &M, const std::vector<ExecutorAddr> &Reservations) {
  std::promise<MSVCPError> P;
  auto F = P.get_future();
  M.release(Reservations, [&](auto R) { P.set_value(std::move(R)); });
  return F.get();
}

// A basic function to be used as both initializer/deinitializer
orc::shared::CWrapperFunctionResult incrementWrapper(const char *ArgData,
                                                     size_t ArgSize) {
  return WrapperFunction<SPSError(SPSExecutorAddr)>::handle(
             ArgData, ArgSize,
             [](ExecutorAddr A) -> Error {
               *A.toPtr<int *>() += 1;
               return Error::success();
             })
      .release();
}

TEST(MemoryMapperTest, InitializeDeinitialize) {
  // These counters are used to track how many times the initializer and
  // deinitializer functions are called
  int InitializeCounter = 0;
  int DeinitializeCounter = 0;
  {
    std::unique_ptr<MemoryMapper> Mapper =
        cantFail(InProcessMemoryMapper::Create());

    // We will do two separate allocations
    auto PageSize = Mapper->getPageSize();
    auto TotalSize = PageSize * 2;

    // Reserve address space
    auto Mem1 = reserve(*Mapper, TotalSize);
    EXPECT_THAT_ERROR(Mem1.takeError(), Succeeded());

    // Test string for memory transfer
    std::string HW = "Hello, world!";

    {
      // Provide working memory
      char *WA1 = Mapper->prepare(Mem1->Start, HW.size() + 1);
      std::strcpy(static_cast<char *>(WA1), HW.c_str());
    }

    // A structure to be passed to initialize
    MemoryMapper::AllocInfo Alloc1;
    {
      MemoryMapper::AllocInfo::SegInfo Seg1;
      Seg1.Offset = 0;
      Seg1.ContentSize = HW.size();
      Seg1.ZeroFillSize = PageSize - Seg1.ContentSize;
      Seg1.Prot = sys::Memory::MF_READ | sys::Memory::MF_WRITE;

      Alloc1.MappingBase = Mem1->Start;
      Alloc1.Segments.push_back(Seg1);
      Alloc1.Actions.push_back(
          {cantFail(WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddr>>(
               ExecutorAddr::fromPtr(incrementWrapper),
               ExecutorAddr::fromPtr(&InitializeCounter))),
           cantFail(WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddr>>(
               ExecutorAddr::fromPtr(incrementWrapper),
               ExecutorAddr::fromPtr(&DeinitializeCounter)))});
    }

    {
      char *WA2 = Mapper->prepare(Mem1->Start + PageSize, HW.size() + 1);
      std::strcpy(static_cast<char *>(WA2), HW.c_str());
    }

    MemoryMapper::AllocInfo Alloc2;
    {
      MemoryMapper::AllocInfo::SegInfo Seg2;
      Seg2.Offset = PageSize;
      Seg2.ContentSize = HW.size();
      Seg2.ZeroFillSize = PageSize - Seg2.ContentSize;
      Seg2.Prot = sys::Memory::MF_READ | sys::Memory::MF_WRITE;

      Alloc2.MappingBase = Mem1->Start;
      Alloc2.Segments.push_back(Seg2);
      Alloc2.Actions.push_back(
          {cantFail(WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddr>>(
               ExecutorAddr::fromPtr(incrementWrapper),
               ExecutorAddr::fromPtr(&InitializeCounter))),
           cantFail(WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddr>>(
               ExecutorAddr::fromPtr(incrementWrapper),
               ExecutorAddr::fromPtr(&DeinitializeCounter)))});
    }

    EXPECT_EQ(InitializeCounter, 0);
    EXPECT_EQ(DeinitializeCounter, 0);

    // Set memory protections and run initializers
    auto Init1 = initialize(*Mapper, Alloc1);
    EXPECT_THAT_ERROR(Init1.takeError(), Succeeded());
    EXPECT_EQ(HW, std::string(static_cast<char *>(Init1->toPtr<char *>())));

    EXPECT_EQ(InitializeCounter, 1);
    EXPECT_EQ(DeinitializeCounter, 0);

    auto Init2 = initialize(*Mapper, Alloc2);
    EXPECT_THAT_ERROR(Init2.takeError(), Succeeded());
    EXPECT_EQ(HW, std::string(static_cast<char *>(Init2->toPtr<char *>())));

    EXPECT_EQ(InitializeCounter, 2);
    EXPECT_EQ(DeinitializeCounter, 0);

    // Explicit deinitialization of first allocation
    std::vector<ExecutorAddr> DeinitAddr = {*Init1};
    EXPECT_THAT_ERROR(deinitialize(*Mapper, DeinitAddr), Succeeded());

    EXPECT_EQ(InitializeCounter, 2);
    EXPECT_EQ(DeinitializeCounter, 1);

    // Test explicit release
    {
      auto Mem2 = reserve(*Mapper, PageSize);
      EXPECT_THAT_ERROR(Mem2.takeError(), Succeeded());

      char *WA = Mapper->prepare(Mem2->Start, HW.size() + 1);
      std::strcpy(static_cast<char *>(WA), HW.c_str());

      MemoryMapper::AllocInfo Alloc3;
      {
        MemoryMapper::AllocInfo::SegInfo Seg3;
        Seg3.Offset = 0;
        Seg3.ContentSize = HW.size();
        Seg3.ZeroFillSize = PageSize - Seg3.ContentSize;
        Seg3.Prot = sys::Memory::MF_READ | sys::Memory::MF_WRITE;

        Alloc3.MappingBase = Mem2->Start;
        Alloc3.Segments.push_back(Seg3);
        Alloc3.Actions.push_back(
            {cantFail(WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddr>>(
                 ExecutorAddr::fromPtr(incrementWrapper),
                 ExecutorAddr::fromPtr(&InitializeCounter))),
             cantFail(WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddr>>(
                 ExecutorAddr::fromPtr(incrementWrapper),
                 ExecutorAddr::fromPtr(&DeinitializeCounter)))});
      }
      auto Init3 = initialize(*Mapper, Alloc3);
      EXPECT_THAT_ERROR(Init3.takeError(), Succeeded());
      EXPECT_EQ(HW, std::string(static_cast<char *>(Init3->toPtr<char *>())));

      EXPECT_EQ(InitializeCounter, 3);
      EXPECT_EQ(DeinitializeCounter, 1);

      std::vector<ExecutorAddr> ReleaseAddrs = {Mem2->Start};
      EXPECT_THAT_ERROR(release(*Mapper, ReleaseAddrs), Succeeded());

      EXPECT_EQ(InitializeCounter, 3);
      EXPECT_EQ(DeinitializeCounter, 2);
    }
  }

  // Implicit deinitialization by the destructor
  EXPECT_EQ(InitializeCounter, 3);
  EXPECT_EQ(DeinitializeCounter, 3);
}

} // namespace