diff --git a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h --- a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h +++ b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h @@ -43,6 +43,22 @@ enum class BenchmarkFilter { All, RegOnly, WithMem }; +struct MemoryValue { + // The arbitrary bit width constant that defines the value. + APInt Value; + // The size of the value in bytes. + size_t Size; + // The index of the memory value. + size_t Number; +}; + +struct MemoryMapping { + // The address to place the mapping at. + intptr_t Address; + // The name of the value that should be mapped. + std::string MemoryValueName; +}; + struct BenchmarkKey { // The LLVM opcode name. std::vector Instructions; diff --git a/llvm/tools/llvm-exegesis/lib/CMakeLists.txt b/llvm/tools/llvm-exegesis/lib/CMakeLists.txt --- a/llvm/tools/llvm-exegesis/lib/CMakeLists.txt +++ b/llvm/tools/llvm-exegesis/lib/CMakeLists.txt @@ -66,6 +66,7 @@ SnippetFile.cpp SnippetGenerator.cpp SnippetRepetitor.cpp + SubprocessMemory.cpp Target.cpp UopsBenchmarkRunner.cpp diff --git a/llvm/tools/llvm-exegesis/lib/SubprocessMemory.h b/llvm/tools/llvm-exegesis/lib/SubprocessMemory.h new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-exegesis/lib/SubprocessMemory.h @@ -0,0 +1,63 @@ +//===-- SubprocessMemory.h --------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Defines a class that automatically handles auxiliary memory and the +/// underlying shared memory backings for memory definitions +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TOOLS_LLVM_EXEGESIS_SUBPROCESSMEMORY_H +#define LLVM_TOOLS_LLVM_EXEGESIS_SUBPROCESSMEMORY_H + +#include "BenchmarkResult.h" +#include +#include +#include + +namespace llvm { +namespace exegesis { + +static constexpr const size_t AuxiliaryMemoryOffset = 1; +static constexpr const size_t AuxiliaryMemorySize = 4096; + +class SubprocessMemory { +public: + Error initializeSubprocessMemory(pid_t ProcessID); + + // The following function sets up memory definitions. It creates shared memory + // objects for the definitions and fills them with the specified values. + // Arguments: + // MemoryDefinitions - A map from memory value names to MemoryValues + // ProcessID - The ID of the current process. + Error addMemoryDefinition( + std::unordered_map MemoryDefinitions, + pid_t ProcessID); + + // The following function sets up the auxiliary memory by opening shared + // memory objects backing memory definitions and putting file descriptors into + // appropriate places. + // Arguments: + // MemoryDefinitions - A map from memory values + // names to Memoryvalues. ParentPID - The ID of the process that setup the + // memory definitions. CounterFileDescriptor - The file descriptor for the + // performance counter that will be placed in the auxiliary memory section. + static Expected setupAuxiliaryMemoryInSubprocess( + std::unordered_map MemoryDefinitions, + pid_t ParentPID, int CounterFileDescriptor); + + ~SubprocessMemory(); + +private: + std::vector SharedMemoryNames; +}; + +} // namespace exegesis +} // namespace llvm + +#endif diff --git a/llvm/tools/llvm-exegesis/lib/SubprocessMemory.cpp b/llvm/tools/llvm-exegesis/lib/SubprocessMemory.cpp new file mode 100644 --- /dev/null +++ b/llvm/tools/llvm-exegesis/lib/SubprocessMemory.cpp @@ -0,0 +1,144 @@ +//===-- SubprocessMemory.cpp ------------------------------------*- C++ -*-===// +// +// 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 "SubprocessMemory.h" +#include "Error.h" +#include "llvm/Support/Error.h" + +#ifdef __linux__ +#include +#include +#include +#endif + +namespace llvm { +namespace exegesis { + +#ifdef __linux__ + +Error SubprocessMemory::initializeSubprocessMemory(pid_t ProcessID) { + // Add the PID to the shared memory name so that if we're running multiple + // processes at the same time, they won't interfere with each other. + // This comes up particularly often when running the exegesis tests with + // llvm-lit + std::string AuxiliaryMemoryName = "/auxmem" + std::to_string(ProcessID); + int AuxiliaryMemoryFD = shm_open(AuxiliaryMemoryName.c_str(), + O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (AuxiliaryMemoryFD == -1) + return make_error( + "Failed to create shared memory object for auxiliary memory: " + + Twine(strerror(errno))); + if (ftruncate(AuxiliaryMemoryFD, AuxiliaryMemorySize) != 0) { + return make_error("Truncating the auxiliary memory failed: " + + Twine(strerror(errno))); + } + return Error::success(); +} + +Error SubprocessMemory::addMemoryDefinition( + std::unordered_map MemoryDefinitions, + pid_t ProcessPID) { + SharedMemoryNames.reserve(MemoryDefinitions.size()); + for (auto &[Name, MemVal] : MemoryDefinitions) { + std::string SharedMemoryName = "/" + std::to_string(ProcessPID) + "memdef" + + std::to_string(MemVal.Number); + SharedMemoryNames.push_back(SharedMemoryName); + int SharedMemoryFD = + shm_open(SharedMemoryName.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (ftruncate(SharedMemoryFD, MemVal.Size) != 0) { + return make_error("Truncating a memory definiton failed: " + + Twine(strerror(errno))); + } + + char *SharedMemoryMapping = + (char *)mmap(NULL, MemVal.Size, PROT_READ | PROT_WRITE, MAP_SHARED, + SharedMemoryFD, 0); + // fill the buffer with the specified value + size_t CurrentByte = 0; + const size_t ValueWidthBytes = MemVal.Value.getBitWidth() / 8; + while (CurrentByte < MemVal.Size - ValueWidthBytes) { + memcpy(SharedMemoryMapping + CurrentByte, MemVal.Value.getRawData(), + ValueWidthBytes); + CurrentByte += ValueWidthBytes; + } + // fill the last section + memcpy(SharedMemoryMapping + CurrentByte, MemVal.Value.getRawData(), + MemVal.Size - CurrentByte); + if (munmap(SharedMemoryMapping, MemVal.Size) != 0) { + return make_error( + "Unmappng a memory definition in the parent failed: " + + Twine(strerror(errno))); + } + } + return Error::success(); +} + +Expected SubprocessMemory::setupAuxiliaryMemoryInSubprocess( + std::unordered_map MemoryDefinitions, + pid_t ParentPID, int CounterFileDescriptor) { + std::string AuxiliaryMemoryName = "/auxmem" + std::to_string(ParentPID); + int AuxiliaryMemoryFileDescriptor = + shm_open(AuxiliaryMemoryName.c_str(), O_RDWR, S_IRUSR | S_IWUSR); + if (AuxiliaryMemoryFileDescriptor == -1) + return make_error( + "Getting file descriptor for auxiliary memory failed"); + // set up memory value file descriptors + int *AuxiliaryMemoryMapping = + (int *)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, + AuxiliaryMemoryFileDescriptor, 0); + if ((intptr_t)AuxiliaryMemoryMapping == -1) + return make_error("Mapping auxiliary memory failed"); + AuxiliaryMemoryMapping[0] = CounterFileDescriptor; + for (auto &[Name, MemVal] : MemoryDefinitions) { + std::string MemoryValueName = "/" + std::to_string(ParentPID) + "memdef" + + std::to_string(MemVal.Number); + AuxiliaryMemoryMapping[AuxiliaryMemoryOffset + MemVal.Number] = + shm_open(MemoryValueName.c_str(), O_RDWR, S_IRUSR | S_IWUSR); + if (AuxiliaryMemoryMapping[AuxiliaryMemoryOffset + MemVal.Number] == -1) + return make_error("Mapping shared memory failed"); + } + if (munmap(AuxiliaryMemoryMapping, 4096) == -1) + return make_error("Unmapping auxiliary memory failed"); + return AuxiliaryMemoryFileDescriptor; +} + +SubprocessMemory::~SubprocessMemory() { + for (std::string SharedMemoryName : SharedMemoryNames) { + if (shm_unlink(SharedMemoryName.c_str()) != 0) { + errs() << "Failed to unlink shared memory section: " << strerror(errno) + << "\n"; + } + } +} + +#else + +Error SubprocessMemory::initializeSubprocessMemory(pid_t ProcessPID) { + return make_error( + "initializeSubprocessMemory is only supported on Linux"); +} + +Error SubprocessMemory::addMemoryDefinition( + std::unordered_map MemoryDefinitions, + pid_t ProcessPID) { + return make_error("addMemoryDefinitions is only supported on Linux"); +} + +Expected SubprocessMemory::setupAuxiliaryMemoryInSubprocess( + std::unordered_map MemoryDefinitions, + pid_t ParentPID, int CounterFileDescriptor) { + return make_error( + "setupAuxiliaryMemoryInSubprocess is only supported on Linux"); +} + +SubprocessMemory::~SubprocessMemory() {} + +#endif // __linux__ + +} // namespace exegesis +} // namespace llvm diff --git a/llvm/unittests/tools/llvm-exegesis/X86/CMakeLists.txt b/llvm/unittests/tools/llvm-exegesis/X86/CMakeLists.txt --- a/llvm/unittests/tools/llvm-exegesis/X86/CMakeLists.txt +++ b/llvm/unittests/tools/llvm-exegesis/X86/CMakeLists.txt @@ -11,6 +11,7 @@ SnippetFileTest.cpp SnippetGeneratorTest.cpp SnippetRepetitorTest.cpp + SubprocessMemoryTest.cpp TargetTest.cpp ) diff --git a/llvm/unittests/tools/llvm-exegesis/X86/SubprocessMemoryTest.cpp b/llvm/unittests/tools/llvm-exegesis/X86/SubprocessMemoryTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/tools/llvm-exegesis/X86/SubprocessMemoryTest.cpp @@ -0,0 +1,114 @@ +//===-- SubprocessMemoryTest.cpp --------------------------------*- C++ -*-===// +// +// 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 "SubprocessMemory.h" + +#include "X86/TestBase.h" +#include "gtest/gtest.h" +#include + +#ifdef __linux__ +#include +#include +#include +#endif // __linux__ + +namespace llvm { +namespace exegesis { + +#ifdef __linux__ + +static constexpr const int MainProcessPID = 0; + +class SubprocessMemoryTest : public X86TestBase { +protected: + SubprocessMemory + testCommon(std::unordered_map MemoryDefinitions) { + SubprocessMemory SM; + EXPECT_FALSE(SM.initializeSubprocessMemory(MainProcessPID)); + EXPECT_FALSE(SM.addMemoryDefinition(MemoryDefinitions, MainProcessPID)); + return SM; + } + + void checkSharedMemoryDefinition(const std::string &DefinitionName, + size_t DefinitionSize, + std::vector ExpectedValue) { + int SharedMemoryFD = + shm_open(DefinitionName.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + uint8_t *SharedMemoryMapping = (uint8_t *)mmap( + NULL, DefinitionSize, PROT_READ, MAP_SHARED, SharedMemoryFD, 0); + EXPECT_NE((intptr_t)SharedMemoryMapping, -1); + for (size_t I = 0; I < ExpectedValue.size(); ++I) { + EXPECT_EQ(SharedMemoryMapping[I], ExpectedValue[I]); + } + munmap(SharedMemoryMapping, DefinitionSize); + } +}; + +TEST_F(SubprocessMemoryTest, OneDefinition) { + SubprocessMemory SM = testCommon({{"test1", {APInt(8, 0xff), 4096, 0}}}); + checkSharedMemoryDefinition("/0memdef0", 4096, {0xff}); +} + +TEST_F(SubprocessMemoryTest, MultipleDefinitions) { + SubprocessMemory SM = testCommon({{"test1", {APInt(8, 0xaa), 4096, 0}}, + {"test2", {APInt(8, 0xbb), 4096, 1}}, + {"test3", {APInt(8, 0xcc), 4096, 2}}}); + checkSharedMemoryDefinition("/0memdef0", 4096, {0xaa}); + checkSharedMemoryDefinition("/0memdef1", 4096, {0xbb}); + checkSharedMemoryDefinition("/0memdef2", 4096, {0xcc}); +} + +TEST_F(SubprocessMemoryTest, DefinitionFillsCompletely) { + SubprocessMemory SM = testCommon({{"test1", {APInt(8, 0xaa), 4096, 0}}, + {"test2", {APInt(16, 0xbbbb), 4096, 1}}, + {"test3", {APInt(24, 0xcccccc), 4096, 2}}}); + std::vector Test1Expected(512, 0xaa); + std::vector Test2Expected(512, 0xbb); + std::vector Test3Expected(512, 0xcc); + checkSharedMemoryDefinition("/0memdef0", 4096, Test1Expected); + checkSharedMemoryDefinition("/0memdef1", 4096, Test2Expected); + checkSharedMemoryDefinition("/0memdef2", 4096, Test3Expected); +} + +// The test below only works on little endian systems. +#ifdef __ORDER_LITTLE_ENDIAN__ +TEST_F(SubprocessMemoryTest, DefinitionEndTruncation) { + SubprocessMemory SM = + testCommon({{"test1", {APInt(48, 0xaabbccddeeff), 4096, 0}}}); + std::vector Test1Expected(512, 0); + // order is reversed since we're assuming a little endian system. + for (size_t I = 0; I < Test1Expected.size(); ++I) { + switch (I % 6) { + case 0: + Test1Expected[I] = 0xff; + break; + case 1: + Test1Expected[I] = 0xee; + break; + case 2: + Test1Expected[I] = 0xdd; + break; + case 3: + Test1Expected[I] = 0xcc; + break; + case 4: + Test1Expected[I] = 0xbb; + break; + case 5: + Test1Expected[I] = 0xaa; + } + } + checkSharedMemoryDefinition("/0memdef0", 4096, Test1Expected); +} +#endif // __ORDER_LITTLE_ENDIAN__ + +#endif // __linux__ + +} // namespace exegesis +} // namespace llvm