blob: 2ff7e91a732342c276de0410ac5d212144981af1 [file] [log] [blame]
//===- ObjectTransformLayerTest.cpp - Unit tests for ObjectTransformLayer -===//
//
// 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/ObjectTransformLayer.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h"
#include "llvm/ExecutionEngine/Orc/NullResolver.h"
#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/IR/Module.h"
#include "llvm/Object/ObjectFile.h"
#include "gtest/gtest.h"
using namespace llvm::orc;
namespace {
// stand-in for object::ObjectFile
typedef int MockObjectFile;
// stand-in for llvm::MemoryBuffer set
typedef int MockMemoryBuffer;
// Mock transform that operates on unique pointers to object files, and
// allocates new object files rather than mutating the given ones.
struct AllocatingTransform {
std::shared_ptr<MockObjectFile>
operator()(std::shared_ptr<MockObjectFile> Obj) const {
return std::make_shared<MockObjectFile>(*Obj + 1);
}
};
// Mock base layer for verifying behavior of transform layer.
// Each method "T foo(args)" is accompanied by two auxiliary methods:
// - "void expectFoo(args)", to be called before calling foo on the transform
// layer; saves values of args, which mock layer foo then verifies against.
// - "void verifyFoo(T)", to be called after foo, which verifies that the
// transform layer called the base layer and forwarded any return value.
class MockBaseLayer {
public:
MockBaseLayer() : MockSymbol(nullptr) { resetExpectations(); }
template <typename ObjPtrT> llvm::Error addObject(VModuleKey K, ObjPtrT Obj) {
EXPECT_EQ(MockKey, K) << "Key should pass through";
EXPECT_EQ(MockObject + 1, *Obj) << "Transform should be applied";
LastCalled = "addObject";
return llvm::Error::success();
}
template <typename ObjPtrT> void expectAddObject(VModuleKey K, ObjPtrT Obj) {
MockKey = K;
MockObject = *Obj;
}
void verifyAddObject() {
EXPECT_EQ("addObject", LastCalled);
resetExpectations();
}
llvm::Error removeObject(VModuleKey K) {
EXPECT_EQ(MockKey, K);
LastCalled = "removeObject";
return llvm::Error::success();
}
void expectRemoveObject(VModuleKey K) { MockKey = K; }
void verifyRemoveObject() {
EXPECT_EQ("removeObject", LastCalled);
resetExpectations();
}
llvm::JITSymbol findSymbol(const std::string &Name,
bool ExportedSymbolsOnly) {
EXPECT_EQ(MockName, Name) << "Name should pass through";
EXPECT_EQ(MockBool, ExportedSymbolsOnly) << "Flag should pass through";
LastCalled = "findSymbol";
MockSymbol = llvm::JITSymbol(122, llvm::JITSymbolFlags::None);
return llvm::JITSymbol(122, llvm::JITSymbolFlags::None);
}
void expectFindSymbol(const std::string &Name, bool ExportedSymbolsOnly) {
MockName = Name;
MockBool = ExportedSymbolsOnly;
}
void verifyFindSymbol(llvm::JITSymbol Returned) {
EXPECT_EQ("findSymbol", LastCalled);
EXPECT_EQ(cantFail(MockSymbol.getAddress()),
cantFail(Returned.getAddress()))
<< "Return should pass through";
resetExpectations();
}
llvm::JITSymbol findSymbolIn(VModuleKey K, const std::string &Name,
bool ExportedSymbolsOnly) {
EXPECT_EQ(MockKey, K) << "VModuleKey should pass through";
EXPECT_EQ(MockName, Name) << "Name should pass through";
EXPECT_EQ(MockBool, ExportedSymbolsOnly) << "Flag should pass through";
LastCalled = "findSymbolIn";
MockSymbol = llvm::JITSymbol(122, llvm::JITSymbolFlags::None);
return llvm::JITSymbol(122, llvm::JITSymbolFlags::None);
}
void expectFindSymbolIn(VModuleKey K, const std::string &Name,
bool ExportedSymbolsOnly) {
MockKey = K;
MockName = Name;
MockBool = ExportedSymbolsOnly;
}
void verifyFindSymbolIn(llvm::JITSymbol Returned) {
EXPECT_EQ("findSymbolIn", LastCalled);
EXPECT_EQ(cantFail(MockSymbol.getAddress()),
cantFail(Returned.getAddress()))
<< "Return should pass through";
resetExpectations();
}
llvm::Error emitAndFinalize(VModuleKey K) {
EXPECT_EQ(MockKey, K) << "VModuleKey should pass through";
LastCalled = "emitAndFinalize";
return llvm::Error::success();
}
void expectEmitAndFinalize(VModuleKey K) { MockKey = K; }
void verifyEmitAndFinalize() {
EXPECT_EQ("emitAndFinalize", LastCalled);
resetExpectations();
}
void mapSectionAddress(VModuleKey K, const void *LocalAddress,
llvm::JITTargetAddress TargetAddr) {
EXPECT_EQ(MockKey, K);
EXPECT_EQ(MockLocalAddress, LocalAddress);
EXPECT_EQ(MockTargetAddress, TargetAddr);
LastCalled = "mapSectionAddress";
}
void expectMapSectionAddress(VModuleKey K, const void *LocalAddress,
llvm::JITTargetAddress TargetAddr) {
MockKey = K;
MockLocalAddress = LocalAddress;
MockTargetAddress = TargetAddr;
}
void verifyMapSectionAddress() {
EXPECT_EQ("mapSectionAddress", LastCalled);
resetExpectations();
}
private:
// Backing fields for remembering parameter/return values
std::string LastCalled;
VModuleKey MockKey;
MockObjectFile MockObject;
std::string MockName;
bool MockBool;
llvm::JITSymbol MockSymbol;
const void *MockLocalAddress;
llvm::JITTargetAddress MockTargetAddress;
MockMemoryBuffer MockBuffer;
// Clear remembered parameters between calls
void resetExpectations() {
LastCalled = "nothing";
MockKey = 0;
MockObject = 0;
MockName = "bogus";
MockSymbol = llvm::JITSymbol(nullptr);
MockLocalAddress = nullptr;
MockTargetAddress = 0;
MockBuffer = 0;
}
};
// Test each operation on LegacyObjectTransformLayer.
TEST(LegacyObjectTransformLayerTest, Main) {
MockBaseLayer M;
ExecutionSession ES(std::make_shared<SymbolStringPool>());
// Create one object transform layer using a transform (as a functor)
// that allocates new objects, and deals in unique pointers.
LegacyObjectTransformLayer<MockBaseLayer, AllocatingTransform> T1(
llvm::AcknowledgeORCv1Deprecation, M);
// Create a second object transform layer using a transform (as a lambda)
// that mutates objects in place, and deals in naked pointers
LegacyObjectTransformLayer<MockBaseLayer,
std::function<std::shared_ptr<MockObjectFile>(
std::shared_ptr<MockObjectFile>)>>
T2(llvm::AcknowledgeORCv1Deprecation, M,
[](std::shared_ptr<MockObjectFile> Obj) {
++(*Obj);
return Obj;
});
// Test addObject with T1 (allocating)
auto K1 = ES.allocateVModule();
auto Obj1 = std::make_shared<MockObjectFile>(211);
M.expectAddObject(K1, Obj1);
cantFail(T1.addObject(K1, std::move(Obj1)));
M.verifyAddObject();
// Test addObjectSet with T2 (mutating)
auto K2 = ES.allocateVModule();
auto Obj2 = std::make_shared<MockObjectFile>(222);
M.expectAddObject(K2, Obj2);
cantFail(T2.addObject(K2, Obj2));
M.verifyAddObject();
EXPECT_EQ(223, *Obj2) << "Expected mutation";
// Test removeObjectSet
M.expectRemoveObject(K2);
cantFail(T1.removeObject(K2));
M.verifyRemoveObject();
// Test findSymbol
std::string Name = "foo";
bool ExportedOnly = true;
M.expectFindSymbol(Name, ExportedOnly);
llvm::JITSymbol Sym1 = T2.findSymbol(Name, ExportedOnly);
M.verifyFindSymbol(std::move(Sym1));
// Test findSymbolIn
Name = "bar";
ExportedOnly = false;
M.expectFindSymbolIn(K1, Name, ExportedOnly);
llvm::JITSymbol Sym2 = T1.findSymbolIn(K1, Name, ExportedOnly);
M.verifyFindSymbolIn(std::move(Sym2));
// Test emitAndFinalize
M.expectEmitAndFinalize(K1);
cantFail(T2.emitAndFinalize(K1));
M.verifyEmitAndFinalize();
// Test mapSectionAddress
char Buffer[24];
llvm::JITTargetAddress MockAddress = 255;
M.expectMapSectionAddress(K1, Buffer, MockAddress);
T1.mapSectionAddress(K1, Buffer, MockAddress);
M.verifyMapSectionAddress();
// Verify transform getter (non-const)
auto Mutatee = std::make_shared<MockObjectFile>(277);
auto Out = T2.getTransform()(Mutatee);
EXPECT_EQ(*Mutatee, *Out) << "Expected in-place transform";
EXPECT_EQ(278, *Mutatee) << "Expected incrementing transform";
// Verify transform getter (const)
auto OwnedObj = std::make_shared<MockObjectFile>(288);
const auto &T1C = T1;
OwnedObj = T1C.getTransform()(std::move(OwnedObj));
EXPECT_EQ(289, *OwnedObj) << "Expected incrementing transform";
volatile bool RunStaticChecks = false;
if (!RunStaticChecks)
return;
// Make sure that LegacyObjectTransformLayer implements the object layer concept
// correctly by sandwitching one between an ObjectLinkingLayer and an
// LegacyIRCompileLayer, verifying that it compiles if we have a call to the
// IRComileLayer's addModule that should call the transform layer's
// addObject, and also calling the other public transform layer methods
// directly to make sure the methods they intend to forward to exist on
// the ObjectLinkingLayer.
// We'll need a concrete MemoryManager class.
class NullManager : public llvm::RuntimeDyld::MemoryManager {
public:
uint8_t *allocateCodeSection(uintptr_t, unsigned, unsigned,
llvm::StringRef) override {
return nullptr;
}
uint8_t *allocateDataSection(uintptr_t, unsigned, unsigned, llvm::StringRef,
bool) override {
return nullptr;
}
void registerEHFrames(uint8_t *, uint64_t, size_t) override {}
void deregisterEHFrames() override {}
bool finalizeMemory(std::string *) override { return false; }
};
// Construct the jit layers.
LegacyRTDyldObjectLinkingLayer BaseLayer(
llvm::AcknowledgeORCv1Deprecation, ES, [](VModuleKey) {
return LegacyRTDyldObjectLinkingLayer::Resources{
std::make_shared<llvm::SectionMemoryManager>(),
std::make_shared<NullResolver>()};
});
auto IdentityTransform = [](std::unique_ptr<llvm::MemoryBuffer> Obj) {
return Obj;
};
LegacyObjectTransformLayer<decltype(BaseLayer), decltype(IdentityTransform)>
TransformLayer(llvm::AcknowledgeORCv1Deprecation, BaseLayer,
IdentityTransform);
auto NullCompiler = [](llvm::Module &) {
return std::unique_ptr<llvm::MemoryBuffer>(nullptr);
};
LegacyIRCompileLayer<decltype(TransformLayer), decltype(NullCompiler)>
CompileLayer(llvm::AcknowledgeORCv1Deprecation, TransformLayer,
NullCompiler);
// Make sure that the calls from LegacyIRCompileLayer to LegacyObjectTransformLayer
// compile.
cantFail(CompileLayer.addModule(ES.allocateVModule(),
std::unique_ptr<llvm::Module>()));
// Make sure that the calls from LegacyObjectTransformLayer to ObjectLinkingLayer
// compile.
VModuleKey DummyKey = ES.allocateVModule();
cantFail(TransformLayer.emitAndFinalize(DummyKey));
TransformLayer.findSymbolIn(DummyKey, Name, false);
TransformLayer.findSymbol(Name, true);
TransformLayer.mapSectionAddress(DummyKey, nullptr, 0);
cantFail(TransformLayer.removeObject(DummyKey));
}
}