#include "ShaderCachePack.hpp" #include "ShaderCacheDirectory.hpp" #include #include #include #include #include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #else #include #include #include #include #endif namespace ShaderRecompiler::ShaderDiskCache { namespace { using Clock = std::chrono::steady_clock; std::uint64_t elapsed(Clock::time_point start) { return std::chrono::duration_cast(Clock::now() - start).count(); } } std::array PackKeyHash(std::span key) { return {HashBytes(key, 0x5eed0001ull), HashBytes(key, 0x5eed0002ull)}; } std::vector PrebuiltCachePaths(const std::filesystem::path& cacheRoot) { if (cacheRoot.empty()) return {}; return {cacheRoot / "prebuilt"}; } CachePack::CachePack(const std::filesystem::path& path) { static_assert(std::endian::native == std::endian::little); #ifdef _WIN32 std::error_code pathError; const auto filename = NativeCachePath_nid_no_patch(path, pathError); if (pathError) { error = "cannot resolve"; return; } const auto handle = CreateFileW(filename.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle == INVALID_HANDLE_VALUE) { error = "cannot open"; return; } LARGE_INTEGER size{}; if (!GetFileSizeEx(handle, &size) || size.QuadPart < static_cast(sizeof(PackHeader)) || static_cast(size.QuadPart) > std::numeric_limits::max()) { CloseHandle(handle); error = "invalid size"; return; } mapping = CreateFileMappingW(handle, nullptr, PAGE_READONLY, 0, 0, nullptr); fileHandle = handle; if (!mapping) { error = "cannot map"; return; } const auto* data = static_cast(MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0)); if (!data) { error = "cannot map view"; return; } file = {data, static_cast(size.QuadPart)}; #else const auto fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); if (fd < 0) { error = "cannot open"; return; } struct stat size{}; if (fstat(fd, &size) != 0 || size.st_size < static_cast(sizeof(PackHeader)) || static_cast(size.st_size) > std::numeric_limits::max()) { close(fd); error = "invalid size"; return; } auto* data = mmap(nullptr, size.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (data == MAP_FAILED) { error = "cannot map"; return; } file = {static_cast(data), static_cast(size.st_size)}; #endif Validate(); } CachePack::~CachePack() { #ifdef _WIN32 if (!file.empty()) UnmapViewOfFile(file.data()); if (mapping) CloseHandle(mapping); if (fileHandle) CloseHandle(fileHandle); #else if (!file.empty()) munmap(const_cast(file.data()), file.size()); #endif } void CachePack::Validate() { PackHeader header; std::memcpy(&header, file.data(), sizeof(header)); if (header.magic != PackMagic) { error = "unknown pack format"; return; } if (header.sourceVersion != SourceVersion()) { error = "different compiler source version"; return; } if (header.fileBytes != file.size() || header.entryCount > (file.size() - sizeof(header)) / sizeof(PackRecord)) { error = "truncated index"; return; } const auto indexBytes = static_cast(header.entryCount) * sizeof(PackRecord); const auto index = file.subspan(sizeof(header), indexBytes); if (HashBytes(index) != header.indexHash) { error = "index checksum mismatch"; return; } const std::span checked(reinterpret_cast(index.data()), header.entryCount); std::array previous{}; for (const auto& record : checked) { if (record.hash < previous || record.offset < sizeof(header) + indexBytes || record.offset > file.size() || record.size > file.size() - record.offset || record.size < 48) { error = "invalid index record"; return; } previous = record.hash; } records = checked; } LoadStatus CachePack::Load(std::span key, CompiledVariant& variant, PackLoadTimings& timings) const { const auto start = Clock::now(); const auto hash = PackKeyHash(key); const auto first = std::lower_bound(records.begin(), records.end(), hash, [](const auto& record, const auto& value) { return record.hash < value; }); timings.lookupNanoseconds += elapsed(start); auto status = LoadStatus::Absent; for (auto it = first; it != records.end() && it->hash == hash; ++it) { const auto data = file.subspan(it->offset, it->size); const auto decode = Clock::now(); const auto result = DecodeEntry(data, key, variant); timings.decodeNanoseconds += elapsed(decode); timings.bytes += data.size(); if (result == LoadStatus::Loaded) return result; if (result == LoadStatus::Rejected) status = result; } return status; } }