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 SizeBytes; + // The index of the memory value. + size_t Index; +}; + +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 @@ -42,6 +42,9 @@ if(LLVM_ENABLE_LIBPFM AND HAVE_LIBPFM) list(APPEND libs pfm) endif() +if(HAVE_LIBRT) + list(APPEND libs rt) +endif() add_llvm_library(LLVMExegesis DISABLE_LLVM_LINK_LLVM_DYLIB @@ -66,6 +69,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,65 @@ +//===-- 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 + +#ifndef __linux__ +typedef int pid_t; +#endif // __linux__ + +namespace llvm { +namespace exegesis { + +class SubprocessMemory { +public: + static constexpr const size_t AuxiliaryMemoryOffset = 1; + static constexpr const size_t AuxiliaryMemorySize = 4096; + + 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.Index); + SharedMemoryNames.push_back(SharedMemoryName); + int SharedMemoryFD = + shm_open(SharedMemoryName.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (ftruncate(SharedMemoryFD, MemVal.SizeBytes) != 0) { + return make_error("Truncating a memory definiton failed: " + + Twine(strerror(errno))); + } + + char *SharedMemoryMapping = + (char *)mmap(NULL, MemVal.SizeBytes, 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.SizeBytes - ValueWidthBytes) { + memcpy(SharedMemoryMapping + CurrentByte, MemVal.Value.getRawData(), + ValueWidthBytes); + CurrentByte += ValueWidthBytes; + } + // fill the last section + memcpy(SharedMemoryMapping + CurrentByte, MemVal.Value.getRawData(), + MemVal.SizeBytes - CurrentByte); + if (munmap(SharedMemoryMapping, MemVal.SizeBytes) != 0) { + return make_error( + "Unmapping 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.Index); + AuxiliaryMemoryMapping[AuxiliaryMemoryOffset + MemVal.Index] = + shm_open(MemoryValueName.c_str(), O_RDWR, S_IRUSR | S_IWUSR); + if (AuxiliaryMemoryMapping[AuxiliaryMemoryOffset + MemVal.Index] == -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 @@ -4,7 +4,7 @@ ${LLVM_MAIN_SRC_DIR}/tools/llvm-exegesis/lib ) -add_llvm_exegesis_unittest_sources( +set(LLVM_EXEGESIS_X86_UNITTEST_SOURCES BenchmarkResultTest.cpp RegisterAliasingTest.cpp SchedClassResolutionTest.cpp @@ -12,6 +12,19 @@ SnippetGeneratorTest.cpp SnippetRepetitorTest.cpp TargetTest.cpp +) + +# Only compile the subprocess memory unittests on x86_64 linux. They only +# sense to run on POSIX systems due to the implementations use of the POSIX +# shared memory APIs and there are currently test failures on certain platforms +# (s390x/PPC) that need further investigation. +# TODO(boomanaiden154): Investigate and fix test failures on PPC +if(LLVM_HOST_TRIPLE MATCHES "^x86_64-.*-linux") + list(APPEND LLVM_EXEGESIS_X86_UNITTEST_SOURCES SubprocessMemoryTest.cpp) +endif() + +add_llvm_exegesis_unittest_sources( + ${LLVM_EXEGESIS_X86_UNITTEST_SOURCES} ) add_llvm_exegesis_unittest_link_components( 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__ + +class SubprocessMemoryTest : public X86TestBase { +protected: + void + testCommon(std::unordered_map MemoryDefinitions, + const int MainProcessPID) { + EXPECT_FALSE(SM.initializeSubprocessMemory(MainProcessPID)); + EXPECT_FALSE(SM.addMemoryDefinition(MemoryDefinitions, MainProcessPID)); + } + + 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); + } + + SubprocessMemory SM; +}; + +TEST_F(SubprocessMemoryTest, OneDefinition) { + testCommon({{"test1", {APInt(8, 0xff), 4096, 0}}}, 0); + checkSharedMemoryDefinition("/0memdef0", 4096, {0xff}); +} + +TEST_F(SubprocessMemoryTest, MultipleDefinitions) { + testCommon({{"test1", {APInt(8, 0xaa), 4096, 0}}, + {"test2", {APInt(8, 0xbb), 4096, 1}}, + {"test3", {APInt(8, 0xcc), 4096, 2}}}, + 1); + checkSharedMemoryDefinition("/1memdef0", 4096, {0xaa}); + checkSharedMemoryDefinition("/1memdef1", 4096, {0xbb}); + checkSharedMemoryDefinition("/1memdef2", 4096, {0xcc}); +} + +TEST_F(SubprocessMemoryTest, DefinitionFillsCompletely) { + testCommon({{"test1", {APInt(8, 0xaa), 4096, 0}}, + {"test2", {APInt(16, 0xbbbb), 4096, 1}}, + {"test3", {APInt(24, 0xcccccc), 4096, 2}}}, + 2); + std::vector Test1Expected(512, 0xaa); + std::vector Test2Expected(512, 0xbb); + std::vector Test3Expected(512, 0xcc); + checkSharedMemoryDefinition("/2memdef0", 4096, Test1Expected); + checkSharedMemoryDefinition("/2memdef1", 4096, Test2Expected); + checkSharedMemoryDefinition("/2memdef2", 4096, Test3Expected); +} + +// The test below only works on little endian systems. +#ifdef __ORDER_LITTLE_ENDIAN__ +TEST_F(SubprocessMemoryTest, DefinitionEndTruncation) { + testCommon({{"test1", {APInt(48, 0xaabbccddeeff), 4096, 0}}}, 3); + 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("/3memdef0", 4096, Test1Expected); +} +#endif // __ORDER_LITTLE_ENDIAN__ + +#endif // __linux__ + +} // namespace exegesis +} // namespace llvm