Skip to content

Commit

Permalink
Add cache hash note to shader ELF
Browse files Browse the repository at this point in the history
When LLPC checks shader and pipeline caches, it uses the hash for
the identifier to check if required ELF files exist in the cache file.
Since the cache file keeps the mapping between the hashes and the ELF
files, we do not keep the hash value in the ELF file.

On the other hand, we need the cache creator proposed in
GPUOpen-Drivers/xgl#64 to build the cache
for relocatable shader ELFs, but the cache creator does not have any
information about the hash. It is because the hash is generated by
LLPC (or amdllpc) and the cache creator is a seperate program.

We want to let the LLPC compiler create a new note section for the
cache hash.  It can be used for the cache creator to create the cache
file with the correct mapping between the hash and ELF.

ELF layouts before/after adding new ".note" section and its section header
for cache hash:

|-------------|        |-------------|
| ELF header  |        | ELF header  |
|-------------|        |-------------|
| Sections    |        | Sections    |
| ...         |        | ...         |
|-------------|  ==>   |-------------|
| Section     |        | Section     |
| headers     |        | headers     |
| ...         |        | ...         |
|-------------|        |-------------|
| Sections    |        | New section |
| ...         |---|    | header for  |
|-------------|   |    | hash note   |    This new section header's
                  |    | section    +|--| offset must be the new
   Remaining      |    |-------------|  | note section for cache hash
   sections after |    | New note    |  |
   section header |    | section for |<-/
   table must be  |    | cache hash  |
   shifted        |    |-------------|
                  \--->| Sections    |
                       | ...         |
                       |-------------|
  • Loading branch information
jaebaek committed Jan 8, 2021
1 parent 97ab8bc commit bdba75b
Show file tree
Hide file tree
Showing 8 changed files with 426 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lgc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/interface
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/imported
${CMAKE_CURRENT_SOURCE_DIR}/../util
)

# lgc/builder
Expand Down Expand Up @@ -175,6 +176,7 @@ target_sources(LLVMlgc PRIVATE
# lgc/util
target_sources(LLVMlgc PRIVATE
util/AddressExtender.cpp
util/CacheHashNoteUtils.cpp
util/Debug.cpp
util/GfxRegHandlerBase.cpp
util/GfxRegHandler.cpp
Expand Down
78 changes: 78 additions & 0 deletions lgc/include/lgc/util/CacheHashNoteUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
***********************************************************************************************************************
*
* Copyright (c) 2021 Google LLC. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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
* AUTHORS 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 IN THE
* SOFTWARE.
*
**********************************************************************************************************************/
/**
***********************************************************************************************************************
* @file CacheHashNoteUtils.h
* @brief LLPC header file: contains the definition of LLPC utility function
* addHashSectionToElf.
*
* @details The function addHashSectionToElf adds a note section
* "llpc_cache_hash" (see CacheHashNoteName) in the given ELF to
* keep the hash code for the cache, which will be used by cache
* creator to identify the mapping between the hash and the shader
* ELF.
*
* @sa llpc/docs/CacheHashNoteUtils.md
***********************************************************************************************************************
*/

#pragma once
#include "vkgcElfReader.h"
#include "vkgcMetroHash.h"

namespace lgc {

// =====================================================================================================================
// Adds a note section "llpc_cache_hash" (see CacheHashNoteName) in the given
// ELF to keep the hash code for the cache.
//
// ELF layouts before/after adding new ".note" section and its section header
// for cache hash:
//
// |-------------| |-------------|
// | ELF header | | ELF header |
// |-------------| |-------------|
// | Sections | | Sections |
// | ... | | ... |
// |-------------| ==> |-------------|
// | Section | | Section |
// | headers | | headers |
// | ... | | ... |
// |-------------| |-------------|
// | Sections | | New section |
// | ... |---| | header for |
// |-------------| | | hash note | This new section header's
// | | section +|--| offset must be the new
// Remaining | |-------------| | note section for cache hash
// sections after | | New note | |
// section header | | section for |<-/
// table must be | | cache hash |
// shifted | |-------------|
// \--->| Sections |
// | ... |
// |-------------|
void addHashSectionToElf(Vkgc::ElfPackage &elf, const MetroHash::Hash &cacheHash);

} // namespace lgc
302 changes: 302 additions & 0 deletions lgc/util/CacheHashNoteUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
/*
***********************************************************************************************************************
*
* Copyright (c) 2021 Google LLC. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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
* AUTHORS 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 IN THE
* SOFTWARE.
*
**********************************************************************************************************************/
#include "lgc/util/CacheHashNoteUtils.h"
#include "vkgcDefs.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Object/ELF.h"
#include "llvm/Support/Alignment.h"
#include "llvm/Support/BinaryByteStream.h"
#include "llvm/Support/BinaryStreamReader.h"
#include "llvm/Support/BinaryStreamWriter.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;
using namespace MetroHash;
using namespace Vkgc;

// The implementation of ELF rewriting is based on "Linux Programmer's Manual ELF(5)".
// In particular, see "Notes (Nhdr)" of the document for the note section.

namespace {

// It is similar to struct NoteHeader defined in llpc/util/vkgcElfReader.h, but
// it allows us to use Elf_Nhdr_Impl<object::ELF64LE>::Align and it does not
// have the size limitation of note name.
using NoteHeader = object::Elf_Nhdr_Impl<object::ELF64LE>;

// =====================================================================================================================
// A section to shift and its new offset.
struct SectionShiftInfo {
SmallString<64> section;
ELF::Elf64_Off newOffset;
};

// =====================================================================================================================
// Create a note section with the note "llpc_cache_hash" that has the cache hash
// as the description.
//
// Reference: Linux Programmer's Manual ELF(5) "Notes (Nhdr)".
//
// @param cacheHash : Hash code to be added to the new .note section.
// @param [out] noteSectionStream : The new .note section with the cache hash.
void createNoteSectionWithCacheHash(const MetroHash::Hash &cacheHash, AppendingBinaryByteStream &noteSectionStream) {
BinaryStreamWriter noteSectionWriter(noteSectionStream);

// Write the note header for the cache hash.
NoteHeader noteHeader;
noteHeader.n_namesz = sizeof(CacheHashNoteName);
noteHeader.n_descsz = sizeof(cacheHash);
// TODO: Define a note type to specify cache hash. It must be defined in
// llvm/include/llvm/BinaryFormat/ELF.h.
// Note types with values between 0 and 32 (inclusive) are reserved.
noteHeader.n_type = 0;
auto error =
noteSectionWriter.writeFixedString(StringRef(reinterpret_cast<const char *>(&noteHeader), sizeof(noteHeader)));
assert(!error);

// Write the cache hash note name terminated by zero and zeros for the alignment.
error = noteSectionWriter.writeFixedString(StringRef(CacheHashNoteName, sizeof(CacheHashNoteName)));
assert(!error);
error = noteSectionWriter.writeFixedString(
StringRef("\0\0\0", offsetToAlignment(noteSectionWriter.getLength(), Align::Constant<NoteHeader::Align>())));
assert(!error);

// Write the cache hash and zeros for the alignment.
error =
noteSectionWriter.writeFixedString(StringRef(reinterpret_cast<const char *>(cacheHash.bytes), sizeof(cacheHash)));
assert(!error);
error = noteSectionWriter.writeFixedString(
StringRef("\0\0\0", offsetToAlignment(noteSectionWriter.getLength(), Align::Constant<NoteHeader::Align>())));
assert(!error);

// Write the note header for the llpc version note. The llpc version note can
// be used to determine the hash algorithm.
constexpr uint32_t majorLlpcVersion = LLPC_INTERFACE_MAJOR_VERSION;
constexpr uint32_t minorLlpcVersion = LLPC_INTERFACE_MINOR_VERSION;
noteHeader.n_namesz = sizeof(LlpcVersionNoteName);
noteHeader.n_descsz = sizeof(majorLlpcVersion) + sizeof(minorLlpcVersion);
// TODO: Define a note type to specify llpc version. It must be defined in
// llvm/include/llvm/BinaryFormat/ELF.h.
// Note types with values between 0 and 32 (inclusive) are reserved.
noteHeader.n_type = 0;
error =
noteSectionWriter.writeFixedString(StringRef(reinterpret_cast<const char *>(&noteHeader), sizeof(noteHeader)));
assert(!error);

// Write the llpc version note name terminated by zero and zeros for the alignment.
error = noteSectionWriter.writeFixedString(StringRef(LlpcVersionNoteName, sizeof(LlpcVersionNoteName)));
assert(!error);
error = noteSectionWriter.writeFixedString(
StringRef("\0\0\0", offsetToAlignment(noteSectionWriter.getLength(), Align::Constant<NoteHeader::Align>())));
assert(!error);

// Write the llpc version and zeros for the alignment.
error = noteSectionWriter.writeFixedString(
StringRef(reinterpret_cast<const char *>(&majorLlpcVersion), sizeof(majorLlpcVersion)));
assert(!error);
error = noteSectionWriter.writeFixedString(
StringRef(reinterpret_cast<const char *>(&minorLlpcVersion), sizeof(minorLlpcVersion)));
assert(!error);
error = noteSectionWriter.writeFixedString(
StringRef("\0\0\0", offsetToAlignment(noteSectionWriter.getLength(), Align::Constant<NoteHeader::Align>())));
assert(!error);
}

// =====================================================================================================================
// Fill zeros for the alignment of the note section.
//
// @param lastOffset : The last offset just before the note section.
// @param [out] zeros : The zeros for the alignment.
void fillZerosForNoteSectionAlignment(const ELF::Elf64_Off lastOffset, SmallString<4> &zeros) {
const unsigned align = offsetToAlignment(lastOffset, Align::Constant<NoteHeader::Align>());
zeros.append(align, '\0');
}

// =====================================================================================================================
// Create a note section header for the cache hash note section.
//
// @param offsetOfSection : The offset of the note section.
// @param sectionSize : The size of the note section.
// @param sectionHeaders : The ArrayRef for the section headers.
// @returns : The newly created note section header.
ELF::Elf64_Shdr createNoteSectionHeader(const ELF::Elf64_Off offsetOfSection, const ELF::Elf64_Xword sectionSize,
const ArrayRef<ELF::Elf64_Shdr> sectionHeaders) {
ELF::Elf64_Shdr noteSectionHeader = {};
noteSectionHeader.sh_offset = offsetOfSection;
noteSectionHeader.sh_type = ELF::SHT_NOTE;
noteSectionHeader.sh_addralign = NoteHeader::Align;
noteSectionHeader.sh_size = sectionSize;

// Find an existing note section to reuse its name ".note".
auto existingNoteSection = llvm::find_if(
sectionHeaders, [](const ELF::Elf64_Shdr &sectionHeader) { return sectionHeader.sh_type == ELF::SHT_NOTE; });
noteSectionHeader.sh_name = existingNoteSection->sh_name;

// TODO: Find a way to add ".note" to the string table when there is no
// existing ".note" section.
assert(noteSectionHeader.sh_name != 0);

return noteSectionHeader;
}

// =====================================================================================================================
// Returns a vector of sections that need to be shifted with their new offset.
//
// @param elf : The original ELF.
// @param sectionHeaders : The ArrayRef for the section headers.
// @param endOfSectionHeaderTable : The offset of the end of the section header table.
// All sections after this offset will be shifted.
// @param initialOffset : The offset at which to place the sections that
// need to be shifted.
// @param [out] sectionAndNewOffset : The shift information of sections. It includes
// the contents of sections to be shifted and the
// new offset to write those sections.
void getNewOffsetsForSections(ElfPackage &elf, const ArrayRef<ELF::Elf64_Shdr> sectionHeaders,
const ELF::Elf64_Off endOfSectionHeaderTable, ELF::Elf64_Off initialOffset,
SmallVectorImpl<SectionShiftInfo> &sectionAndNewOffset) {
// If a section is located after the section header table (i.e., its offset
// is bigger than endOfSectionHeaderTable), it will be shifted. We keep the
// section's contents and its new offset. This information will be used when
// we rewrite the ELF.
for (auto it = sectionHeaders.begin(); it != sectionHeaders.end(); ++it) {
if (it->sh_offset < endOfSectionHeaderTable)
continue;
sectionAndNewOffset.push_back({SmallString<64>(StringRef(elf.c_str() + it->sh_offset, it->sh_size)),
alignTo(initialOffset, Align(it->sh_addralign))});
initialOffset = sectionAndNewOffset.back().newOffset + it->sh_size;
}
}

// =====================================================================================================================
// Rewrite ELF to add the new note section and its header for the cache hash.
//
// @param [in/out] elf : ELF to be rewritten with the new .note section
// for the cache hash.
// @param offsetOfNewSectionHeader : The offset where the new section header will be
// placed.
// @param newNoteSectionHeader : The section header for the new note section.
// @param zerosForAlignment : Zeros for the note section alignment between the
// end of section header table and the section for
// cache hash note.
// @param noteSectionStream : The note secion containing the cache hash.
// @param sectionAndNewOffset : The information to shift sections after the
// section header table.
void rewriteELFWithCacheHash(ElfPackage &elf, const ELF::Elf64_Off offsetOfNewSectionHeader,
const ELF::Elf64_Shdr &newNoteSectionHeader, const SmallString<4> &zerosForAlignment,
AppendingBinaryByteStream &noteSectionStream,
SmallVectorImpl<SectionShiftInfo> &sectionAndNewOffset) {
// Strip sections after the section header table.
elf.resize(offsetOfNewSectionHeader);

// Increase the number of section headers in ELF header.
reinterpret_cast<ELF::Elf64_Ehdr *>(elf.data())->e_shnum++;

// Write the section header for the new note section.
raw_svector_ostream elfStream(elf);
elfStream << StringRef(reinterpret_cast<const char *>(&newNoteSectionHeader), sizeof(ELF::Elf64_Shdr));

// Write zeros for alignment before the new section.
elfStream << zerosForAlignment.str();

// Write the new note section.
ArrayRef<uint8_t> noteSection;
auto error = noteSectionStream.readBytes(0, noteSectionStream.getLength(), noteSection);
assert(!error);
elfStream << StringRef(reinterpret_cast<const char *>(noteSection.data()), noteSection.size());

// Sort SectionShiftInfo by the new offset of each section in the increasing
// order.
sort(sectionAndNewOffset,
[](const SectionShiftInfo &i0, const SectionShiftInfo &i1) { return i0.newOffset < i1.newOffset; });

// Shift the sections after section header table.
for (auto it = sectionAndNewOffset.begin(); it != sectionAndNewOffset.end(); ++it) {
elf.append(it->newOffset - elf.size(), '\0');
elfStream << it->section.str();
}
}

} // anonymous namespace

// -add-hash-to-elf
static cl::opt<bool> AddHashToELF("add-hash-to-elf",
cl::desc("Add a .note section to ELF for hash used to lookup cache"),
cl::init(false));

namespace lgc {

// =====================================================================================================================
// Adds a note section with the note "llpc_cache_hash" in the given ELF to keep
// the hash code for the cache.
//
// @param [in/out] elf : ELF to be updated with the new .note section for the
// cache hash.
// @param cacheHash : Hash code to be added to the new .note section.
void addHashSectionToElf(ElfPackage &elf, const MetroHash::Hash &cacheHash) {
// If '-add-hash-to-elf' option is not enabled, return without any change.
if (!AddHashToELF)
return;

// Get ELF header that contains information for section header table offset
// and the number of section headers.
//
// Reference: http://www.skyfree.org/linux/references/ELF_Format.pdf
ELF::Elf64_Ehdr ehdr = {};
memcpy(&ehdr, elf.data(), sizeof(ELF::Elf64_Ehdr));

// The offset of new section header must be the end of the current section
// header table.
const ELF::Elf64_Off offsetOfNewSectionHeader = ehdr.e_shoff + ehdr.e_shnum * sizeof(ELF::Elf64_Shdr);
const ELF::Elf64_Off endOfNewSectionHeader = offsetOfNewSectionHeader + sizeof(ELF::Elf64_Shdr);

// String with '\0' for the alignment of the new note section.
SmallString<4> zerosForAlignment;
fillZerosForNoteSectionAlignment(endOfNewSectionHeader, zerosForAlignment);

// The offset of the new note section.
const ELF::Elf64_Off offsetOfNewSection = endOfNewSectionHeader + zerosForAlignment.size();

// Create the new note section for cache hash.
AppendingBinaryByteStream noteSectionStream(support::little);
createNoteSectionWithCacheHash(cacheHash, noteSectionStream);

// Create the note section header for the new cache hash note section.
ArrayRef<ELF::Elf64_Shdr> sectionHeaders(
reinterpret_cast<ELF::Elf64_Shdr *>(reinterpret_cast<char *>(elf.data()) + ehdr.e_shoff), ehdr.e_shnum);
ELF::Elf64_Shdr newNoteSectionHeader =
createNoteSectionHeader(offsetOfNewSection, noteSectionStream.getLength(), sectionHeaders);

// Get the shift information of sections after the section header table.
SmallVector<SectionShiftInfo> sectionAndNewOffset;
getNewOffsetsForSections(elf, sectionHeaders, offsetOfNewSectionHeader,
offsetOfNewSection + noteSectionStream.getLength(), sectionAndNewOffset);

// Rewrite ELF.
rewriteELFWithCacheHash(elf, offsetOfNewSectionHeader, newNoteSectionHeader, zerosForAlignment, noteSectionStream,
sectionAndNewOffset);
}

} // namespace lgc
Loading

0 comments on commit bdba75b

Please sign in to comment.