//////////////////////////////////////////////////////////////////////////////// // // 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; }