54136f60a0
Overload Assembler::RunAssembleBuf to take in an extra Gfxv parameter. Using this overload will temporarily set the target ASIC to Gfxv before calling RunAssemble, and copy back the original MCPU literal upon completion. The copy to reset the original MCPU in this case is safe as the MCPU length is always known. This will be useful in multi-device test cases whereby the devices are not necessarily the same gfx version. The overload is explicitly for the RunAssembleBuf wrapper rather than RunAssemble to ensure the default MCPU is always reset independent of errors in RunAssemble. Signed-off-by: Graham Sider <Graham.Sider@amd.com> Change-Id: I7fe5a962876314b6df32e4b7160174949d98f9e3
389 lines
12 KiB
C++
389 lines
12 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 <llvm/Config/llvm-config.h>
|
|
#include <llvm/MC/MCAsmBackend.h>
|
|
#include <llvm/MC/MCAsmInfo.h>
|
|
#include <llvm/MC/MCCodeEmitter.h>
|
|
#include <llvm/MC/MCContext.h>
|
|
#include <llvm/MC/MCInstPrinter.h>
|
|
#include <llvm/MC/MCInstrInfo.h>
|
|
#include <llvm/MC/MCObjectFileInfo.h>
|
|
#include <llvm/MC/MCObjectWriter.h>
|
|
#include <llvm/MC/MCParser/AsmLexer.h>
|
|
#include <llvm/MC/MCParser/MCTargetAsmParser.h>
|
|
#include <llvm/MC/MCRegisterInfo.h>
|
|
#include <llvm/MC/MCStreamer.h>
|
|
#include <llvm/MC/MCSubtargetInfo.h>
|
|
#include <llvm/Support/CommandLine.h>
|
|
#include <llvm/Support/InitLLVM.h>
|
|
#include <llvm/Support/MemoryBuffer.h>
|
|
#include <llvm/Support/SourceMgr.h>
|
|
#include <llvm/Support/TargetSelect.h>
|
|
#if LLVM_VERSION_MAJOR > 13
|
|
#include <llvm/MC/TargetRegistry.h>
|
|
#else
|
|
#include <llvm/Support/TargetRegistry.h>
|
|
#endif
|
|
|
|
#include <linux/elf.h>
|
|
#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<uint8_t>(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<uint8_t>(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<const Elf64_Ehdr*>(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<const Elf64_Shdr*>(RawData + ElfHeader->e_shoff);
|
|
SectStrTable = &SectHeader[ElfHeader->e_shstrndx];
|
|
SectStrAddr = static_cast<const char*>(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)
|
|
* @param Gfxv Optional overload to temporarily set target ASIC
|
|
* @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);
|
|
}
|
|
int Assembler::RunAssembleBuf(const char* const AssemblySource, char* OutBuf,
|
|
const size_t BufSize, const uint32_t Gfxv) {
|
|
const char* defaultMCPU = GetTargetAsic();
|
|
SetTargetAsic(Gfxv);
|
|
int ret = RunAssemble(AssemblySource);
|
|
strncpy(MCPU, defaultMCPU, ASM_MCPU_LEN);
|
|
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<MemoryBuffer> 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<const MCRegisterInfo> 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<const MCAsmInfo> MAI(
|
|
TheTarget->createMCAsmInfo(*MRI, TripleName, MCOptions));
|
|
#else
|
|
std::unique_ptr<const MCAsmInfo> MAI(
|
|
TheTarget->createMCAsmInfo(*MRI, TripleName));
|
|
#endif
|
|
if (!MAI) {
|
|
outs() << "ASM Error: no assembly info for target " << MCPU << "\n";
|
|
return -1;
|
|
}
|
|
std::unique_ptr<MCInstrInfo> MCII(
|
|
TheTarget->createMCInstrInfo());
|
|
if (!MCII) {
|
|
outs() << "ASM Error: no instruction info for target " << MCPU << "\n";
|
|
return -1;
|
|
}
|
|
std::unique_ptr<MCSubtargetInfo> 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<raw_string_ostream> DataStream(std::make_unique<raw_string_ostream>(Data));
|
|
std::unique_ptr<buffer_ostream> BOS(std::make_unique<buffer_ostream>(*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<MCStreamer> Streamer(TheTarget->createMCObjectStreamer(
|
|
TheTriple, Ctx,
|
|
std::unique_ptr<MCAsmBackend>(MAB), MAB->createObjectWriter(*OS),
|
|
std::unique_ptr<MCCodeEmitter>(CE), *STI, MCOptions.MCRelaxAll,
|
|
MCOptions.MCIncrementalLinkerCompatible, /*DWARFMustBeAtTheEnd*/ false));
|
|
|
|
std::unique_ptr<MCAsmParser> Parser(
|
|
createMCAsmParser(SrcMgr, Ctx, *Streamer, *MAI));
|
|
|
|
// Set parser to target parser and run
|
|
std::unique_ptr<MCTargetAsmParser> 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;
|
|
}
|