322 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			322 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===- 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 &)
 | |
|       -> llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>> {
 | |
|     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));
 | |
| }
 | |
| }
 |