From d916fe0129380ea6dafd2d09ab69b2920096ea62 Mon Sep 17 00:00:00 2001 From: Graham Sider Date: Fri, 24 Sep 2021 18:44:29 -0400 Subject: [PATCH] kfdtest: Add LLVM AMDGPU assembler components Initial commit for transition from IsaGenerator/SP3 assembler model to the LLVM AMDGPU (AMDGCN) assembler backend: - Add Assembler class, may be instantiated for assembly similar to IsaGenerator. - Add Assembler and LLVM archive dependencies to build process. - CXX bumped to gnu++14 as required for LLVM compilation. - Compatible with LLVM 7.0 and greater (latest Lightning/llvm-git version should be used for up-to-date gfx support). Note that this is just a build dependency and *not* a runtime dependency. LLVM does not need to be installed on the host machine to run kfdtest. - CMake will first look for a Lightning build. Lightning itself does not need to be installed system-wide, just built. If this fails, it will attempt to find a system-wide LLVM install. General Assembler usage and notes: - Similar to IsaGenerator, applicable test classes will contain an Assembler object pointer which may be instantiated in the test constructor. - Instantiation requires the GFXIP version in order to find the appropriate LLVM AMDGPU Target ID. - The RunAssemble() member func takes in a standard const char* shader and fills the TextData member with the output binary; TextSize with the size of TextData. These may be accessed via GetInstrStream() and GetInstrStreamSize(), or the output binary may be copied into an IsaBuffer via CopyInstrStream(). RunAssembleBuf() combines RunAssemble() and CopyInstrStream() and additionally takes an optional BufSize parameter to specify the size of the output buffer (defaults to PAGE_SIZE). - Assembler object deletion is to be done in the base test destructor. Assembler-specific memory allocation is freed in the Assembler destructor. - For debug, one can call PrintTextHex() to print out a formatted hex representation of the output binary, or PrintELFHex() to print out the intermediate ELF object. Note that PrintTextHex() is public whereas PrintELFHex() is private. - Prints use the LLVM outs() call as that allows for use of the LLVM format_hex() func in the aforementioned debug prints. This is subject to change if the LOG() call would be preferred. RunAssemble control flow: - Ensure correct Assembler initialization and clear previous run TextData (if necessary). - Initialize LLVM AMDGPU target, required interfaces, and buffers. - Set parser to specified target/subtarget and assemble into ELF code object. - Extract .text section from ELF, allocate space for TextData and store. - On success, returns 0 (HSAKMT_STATUS_SUCCESS). On error, returns -1 (subject to change to be in line with HSAKMT_STATUS enum). Signed-off-by: Graham Sider Change-Id: I1d96230824db651d3ffbaa46eb68fc274e7066b5 [ROCm/ROCR-Runtime commit: 65b1e0c058259b55b33b477860c92ce5a93b9149] --- .../rocr-runtime/tests/kfdtest/CMakeLists.txt | 38 +- .../tests/kfdtest/src/Assemble.cpp | 379 ++++++++++++++++++ .../tests/kfdtest/src/Assemble.hpp | 84 ++++ 3 files changed, 499 insertions(+), 2 deletions(-) create mode 100644 projects/rocr-runtime/tests/kfdtest/src/Assemble.cpp create mode 100644 projects/rocr-runtime/tests/kfdtest/src/Assemble.hpp diff --git a/projects/rocr-runtime/tests/kfdtest/CMakeLists.txt b/projects/rocr-runtime/tests/kfdtest/CMakeLists.txt index 05557ec16d..39994dbff3 100644 --- a/projects/rocr-runtime/tests/kfdtest/CMakeLists.txt +++ b/projects/rocr-runtime/tests/kfdtest/CMakeLists.txt @@ -95,6 +95,39 @@ endif() message ( "Find libhsakmt at ${HSAKMT_LIBRARY_DIRS}" ) +if ( POLICY CMP0074 ) + cmake_policy( SET CMP0074 NEW ) +endif() + +find_path( LIGHTNING_CMAKE_DIR NAMES LLVMConfig.cmake + PATHS $ENV{OUT_DIR}/llvm/lib/cmake/llvm NO_CACHE NO_DEFAULT_PATH) + +if ( DEFINED LIGHTNING_CMAKE_DIR AND EXISTS ${LIGHTNING_CMAKE_DIR} ) + set ( LLVM_DIR ${LIGHTNING_CMAKE_DIR} ) +else() + message( WARNING "Couldn't find Lightning build. " + "Attempting to use system LLVM install..." ) +endif() + +find_package( LLVM REQUIRED CONFIG ) + +if( ${LLVM_PACKAGE_VERSION} VERSION_LESS "7.0" ) + message( FATAL_ERROR "Requires LLVM 7.0 or greater " + "(found ${LLVM_PACKAGE_VERSION})" ) +elseif( ${LLVM_PACKAGE_VERSION} VERSION_LESS "14.0" ) + message( WARNING "Not using latest LLVM version. " + "Some ASIC targets may not work!" ) +endif() + +message( STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}" ) +message( STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}" ) + +include_directories(${LLVM_INCLUDE_DIRS}) +separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) +add_definitions(${LLVM_DEFINITIONS_LIST}) + +llvm_map_components_to_libnames(llvm_libs AMDGPUAsmParser Core Support) + set ( SP3_DIR ${PROJECT_SOURCE_DIR}/sp3 ) include_directories(${PROJECT_SOURCE_DIR}/gtest-1.6.0) @@ -112,6 +145,7 @@ set (SRC_FILES gtest-1.6.0/gtest-all.cpp src/Dispatch.cpp src/GoogleTestExtension.cpp src/IndirectBuffer.cpp + src/Assemble.cpp src/IsaGenerator.cpp src/IsaGenerator_Aldebaran.cpp src/IsaGenerator_Gfx10.cpp @@ -163,7 +197,7 @@ message( STATUS "PROJECT_SOURCE_DIR:" ${PROJECT_SOURCE_DIR} ) if ( "${CMAKE_C_COMPILER_VERSION}" STRGREATER "4.8.0") ## Add --enable-new-dtags to generate DT_RUNPATH -set ( CMAKE_CXX_FLAGS "-std=gnu++11 -Wl,--enable-new-dtags" ) +set ( CMAKE_CXX_FLAGS "-std=gnu++14 -Wl,--enable-new-dtags" ) endif() if ( "${CMAKE_BUILD_TYPE}" STREQUAL Release ) set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2" ) @@ -185,7 +219,7 @@ link_directories(${SP3_DIR}) add_executable(kfdtest ${SRC_FILES}) -target_link_libraries(kfdtest ${HSAKMT_LIBRARIES} ${DRM_LDFLAGS} ${DRM_AMDGPU_LDFLAGS} pthread m stdc++ rt amdsp3 numa) +target_link_libraries(kfdtest ${HSAKMT_LIBRARIES} ${DRM_LDFLAGS} ${DRM_AMDGPU_LDFLAGS} ${llvm_libs} pthread m stdc++ rt amdsp3 numa) configure_file ( scripts/kfdtest.exclude kfdtest.exclude COPYONLY ) configure_file ( scripts/run_kfdtest.sh run_kfdtest.sh COPYONLY ) diff --git a/projects/rocr-runtime/tests/kfdtest/src/Assemble.cpp b/projects/rocr-runtime/tests/kfdtest/src/Assemble.cpp new file mode 100644 index 0000000000..cf4b9e7de0 --- /dev/null +++ b/projects/rocr-runtime/tests/kfdtest/src/Assemble.cpp @@ -0,0 +1,379 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// The University of Illinois/NCSA +// Open Source License (NCSA) +// +// Copyright (c) 2022, Advanced Micro Devices, Inc. All rights reserved. +// +// Developed by: +// +// AMD Research and AMD HSA Software Development +// +// Advanced Micro Devices, Inc. +// +// www.amd.com +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal with the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimers. +// - Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimers in +// the documentation and/or other materials provided with the distribution. +// - Neither the names of Advanced Micro Devices, Inc, +// nor the names of its contributors may be used to endorse or promote +// products derived from this Software without specific prior written +// permission. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS WITH THE SOFTWARE. +// +//////////////////////////////////////////////////////////////////////////////// + +/** + * Self-contained assembler that uses the LLVM MC API to assemble AMDGCN + * instructions + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if LLVM_VERSION_MAJOR > 13 +#include +#else +#include +#endif + +#include +#include "OSWrapper.hpp" +#include "Assemble.hpp" + +using namespace llvm; + +Assembler::Assembler(const uint32_t Gfxv) { + SetTargetAsic(Gfxv); + TextData = nullptr; + TextSize = 0; + LLVMInit(); +} + +Assembler::~Assembler() { + FlushText(); + llvm_shutdown(); +} + +const char* Assembler::GetInstrStream() { + return TextData; +} + +const size_t Assembler::GetInstrStreamSize() { + return TextSize; +} + +int Assembler::CopyInstrStream(char* OutBuf, const size_t BufSize) { + if (TextSize > BufSize) + return -2; + + std::copy(TextData, TextData + TextSize, OutBuf); + return 0; +} + +const char* Assembler::GetTargetAsic() { + return MCPU; +} + +/** + * Set MCPU via GFX Version from Thunk + * LLVM Target IDs use decimal for Maj/Min, hex for Step + */ +void Assembler::SetTargetAsic(const uint32_t Gfxv) { + const uint8_t Major = (Gfxv >> 16) & 0xff; + const uint8_t Minor = (Gfxv >> 8) & 0xff; + const uint8_t Step = Gfxv & 0xff; + + snprintf(MCPU, ASM_MCPU_LEN, "gfx%d%d%x", Major, Minor, Step); +} + +/** + * Initialize LLVM targets and assembly printers/parsers + */ +void Assembler::LLVMInit() { + LLVMInitializeAMDGPUTargetInfo(); + LLVMInitializeAMDGPUTargetMC(); + LLVMInitializeAMDGPUAsmParser(); +} + +/** + * Flush/reset TextData and TextSize to initial state + */ +void Assembler::FlushText() { + if (TextData) + delete[] TextData; + TextData = nullptr; + TextSize = 0; +} + +/** + * Print hex of ELF object to stdout (debug) + */ +void Assembler::PrintELFHex(const std::string Data) { + outs() << "ASM Info: assembled ELF hex data (length " << Data.length() << "):\n"; + outs() << "0x00:\t"; + for (size_t i = 0; i < Data.length(); ++i) { + char c = Data[i]; + outs() << format_hex(static_cast(c), 4); + if ((i+1) % 16 == 0) + outs() << "\n" << format_hex(i+1, 4) << ":\t"; + else + outs() << " "; + } + outs() << "\n"; +} + +/** + * Print hex of raw instruction stream to stdout (debug) + */ +void Assembler::PrintTextHex() { + outs() << "ASM Info: assembled .text hex data (length " << TextSize << "):\n"; + outs() << "0x00:\t"; + for (size_t i = 0; i < TextSize; i++) { + outs() << format_hex(static_cast(TextData[i]), 4); + if ((i+1) % 16 == 0) + outs() << "\n" << format_hex(i+1, 4) << ":\t"; + else + outs() << " "; + } + outs() << "\n"; +} + +/** + * Extract raw instruction stream from .text section in ELF object + * + * @param RawData Raw C string of ELF object + * @return 0 on success + */ +int Assembler::ExtractELFText(const char* RawData) { + const Elf64_Ehdr* ElfHeader; + const Elf64_Shdr* SectHeader; + const Elf64_Shdr* SectStrTable; + const char* SectStrAddr; + unsigned NumSects, SectIdx; + + if (!(ElfHeader = reinterpret_cast(RawData))) { + outs() << "ASM Error: elf data is invalid or corrupted\n"; + return -1; + } + if (ElfHeader->e_ident[EI_CLASS] != ELFCLASS64) { + outs() << "ASM Error: elf object must be of 64-bit type\n"; + return -1; + } + + SectHeader = reinterpret_cast(RawData + ElfHeader->e_shoff); + SectStrTable = &SectHeader[ElfHeader->e_shstrndx]; + SectStrAddr = static_cast(RawData + SectStrTable->sh_offset); + + // Loop through sections, break on .text + NumSects = ElfHeader->e_shnum; + for (SectIdx = 0; SectIdx < NumSects; SectIdx++) { + std::string SectName = std::string(SectStrAddr + SectHeader[SectIdx].sh_name); + if (SectName == std::string(".text")) { + TextSize = SectHeader[SectIdx].sh_size; + TextData = new char[TextSize]; + memcpy(TextData, RawData + SectHeader[SectIdx].sh_offset, TextSize); + break; + } + } + + if (SectIdx >= NumSects) { + outs() << "ASM Error: couldn't locate .text section\n"; + return -1; + } + + return 0; +} + +/** + * Assemble shader, fill member vars, and copy to output buffer + * + * @param AssemblySource Shader source represented as a raw C string + * @param OutBuf Raw instruction stream output buffer + * @param BufSize Size of OutBuf (defaults to PAGE_SIZE) + * @return Value of RunAssemble() (0 on success) + */ +int Assembler::RunAssembleBuf(const char* const AssemblySource, char* OutBuf, + const size_t BufSize) { + int ret = RunAssemble(AssemblySource); + return ret ? ret : CopyInstrStream(OutBuf, BufSize); +} + +/** + * Assemble shader and fill member vars + * + * @param AssemblySource Shader source represented as a raw C string + * @return 0 on success + */ +int Assembler::RunAssemble(const char* const AssemblySource) { + // Ensure target ASIC has been set + if (!MCPU) { + outs() << "ASM Error: target asic is uninitialized\n"; + return -1; + } + + // Delete TextData for any previous runs + FlushText(); + +#if 0 + outs() << "ASM Info: running assembly for target: " << MCPU << "\n"; + outs() << "ASM Info: source:\n"; + outs() << AssemblySource << "\n"; +#endif + + // Initialize MCOptions and target triple + const MCTargetOptions MCOptions; + Triple TheTriple; + + const Target* TheTarget = + TargetRegistry::lookupTarget(ArchName, TheTriple, Error); + if (!TheTarget) { + outs() << Error; + return -1; + } + + TheTriple.setArchName(ArchName); + TheTriple.setVendorName(VendorName); + TheTriple.setOSName(OSName); + + TripleName = TheTriple.getTriple(); + TheTriple.setTriple(Triple::normalize(TripleName)); + + // Create MemoryBuffer for assembly source + StringRef AssemblyRef(AssemblySource); + std::unique_ptr BufferPtr = + MemoryBuffer::getMemBuffer(AssemblyRef, "", false); + if (!BufferPtr->getBufferSize()) { + outs() << "ASM Error: assembly source is empty\n"; + return -1; + } + + // Instantiate SrcMgr and transfer BufferPtr ownership + SourceMgr SrcMgr; + SrcMgr.AddNewSourceBuffer(std::move(BufferPtr), SMLoc()); + + // Initialize MC interfaces and base class objects + std::unique_ptr MRI( + TheTarget->createMCRegInfo(TripleName)); + if (!MRI) { + outs() << "ASM Error: no register info for target " << MCPU << "\n"; + return -1; + } +#if LLVM_VERSION_MAJOR > 9 + std::unique_ptr MAI( + TheTarget->createMCAsmInfo(*MRI, TripleName, MCOptions)); +#else + std::unique_ptr MAI( + TheTarget->createMCAsmInfo(*MRI, TripleName)); +#endif + if (!MAI) { + outs() << "ASM Error: no assembly info for target " << MCPU << "\n"; + return -1; + } + std::unique_ptr MCII( + TheTarget->createMCInstrInfo()); + if (!MCII) { + outs() << "ASM Error: no instruction info for target " << MCPU << "\n"; + return -1; + } + std::unique_ptr STI( + TheTarget->createMCSubtargetInfo(TripleName, MCPU, std::string())); + if (!STI || !STI->isCPUStringValid(MCPU)) { + outs() << "ASM Error: no subtarget info for target " << MCPU << "\n"; + return -1; + } + + // Set up the MCContext for creating symbols and MCExpr's +#if LLVM_VERSION_MAJOR > 12 + MCContext Ctx(TheTriple, MAI.get(), MRI.get(), STI.get(), &SrcMgr, &MCOptions); +#else + MCObjectFileInfo MOFI; + MCContext Ctx(MAI.get(), MRI.get(), &MOFI, &SrcMgr, &MCOptions); + MOFI.InitMCObjectFileInfo(TheTriple, true, Ctx); +#endif + + // Finalize setup for output object code stream + std::string Data; + std::unique_ptr DataStream(std::make_unique(Data)); + std::unique_ptr BOS(std::make_unique(*DataStream)); + raw_pwrite_stream* OS = BOS.get(); + +#if LLVM_VERSION_MAJOR > 14 + MCCodeEmitter* CE = TheTarget->createMCCodeEmitter(*MCII, Ctx); +#else + MCCodeEmitter* CE = TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx); +#endif + MCAsmBackend* MAB = TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions); + + std::unique_ptr Streamer(TheTarget->createMCObjectStreamer( + TheTriple, Ctx, + std::unique_ptr(MAB), MAB->createObjectWriter(*OS), + std::unique_ptr(CE), *STI, MCOptions.MCRelaxAll, + MCOptions.MCIncrementalLinkerCompatible, /*DWARFMustBeAtTheEnd*/ false)); + + std::unique_ptr Parser( + createMCAsmParser(SrcMgr, Ctx, *Streamer, *MAI)); + + // Set parser to target parser and run + std::unique_ptr TAP( + TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions)); + if (!TAP) { + outs() << "ASM Error: no assembly parsing support for target " << MCPU << "\n"; + return -1; + } + Parser->setTargetParser(*TAP); + + if (Parser->Run(true)) { + outs() << "ASM Error: assembly parser failed\n"; + return -1; + } + + BOS.reset(); + DataStream->flush(); + + int ret = ExtractELFText(Data.data()); + if (ret < 0 || !TextData) { + outs() << "ASM Error: .text extraction failed\n"; + return ret; + } + +#if 0 + PrintELFHex(Data); + PrintTextHex(); +#endif + + return 0; +} diff --git a/projects/rocr-runtime/tests/kfdtest/src/Assemble.hpp b/projects/rocr-runtime/tests/kfdtest/src/Assemble.hpp new file mode 100644 index 0000000000..d61229a5a5 --- /dev/null +++ b/projects/rocr-runtime/tests/kfdtest/src/Assemble.hpp @@ -0,0 +1,84 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// The University of Illinois/NCSA +// Open Source License (NCSA) +// +// Copyright (c) 2022, Advanced Micro Devices, Inc. All rights reserved. +// +// Developed by: +// +// AMD Research and AMD HSA Software Development +// +// Advanced Micro Devices, Inc. +// +// www.amd.com +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal with the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimers. +// - Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimers in +// the documentation and/or other materials provided with the distribution. +// - Neither the names of Advanced Micro Devices, Inc, +// nor the names of its contributors may be used to endorse or promote +// products derived from this Software without specific prior written +// permission. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS WITH THE SOFTWARE. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef _ASSEMBLE_H_ +#define _ASSEMBLE_H_ + +#define ASM_MCPU_LEN 16 + +class Assembler { + private: + const char* ArchName = "amdgcn"; + const char* VendorName = "amd"; + const char* OSName = "amdhsa"; + char MCPU[ASM_MCPU_LEN]; + + std::string TripleName; + std::string Error; + + char* TextData; + size_t TextSize; + + void SetTargetAsic(const uint32_t Gfxv); + + void LLVMInit(); + void FlushText(); + void PrintELFHex(const std::string Data); + int ExtractELFText(const char* RawData); + + public: + Assembler(const uint32_t Gfxv); + ~Assembler(); + + void PrintTextHex(); + const char* GetTargetAsic(); + + const char* GetInstrStream(); + const size_t GetInstrStreamSize(); + int CopyInstrStream(char* OutBuf, const size_t BufSize = PAGE_SIZE); + + int RunAssemble(const char* const AssemblySource); + int RunAssembleBuf(const char* const AssemblySource, char* OutBuf, + const size_t BufSize = PAGE_SIZE); +}; + +#endif // _ASSEMBLE_H_