Files
NX2navi/runtime/vk_cache_file.h
chrg6 871b06e2e7 Read a pipeline cache larger than 2 GB on Windows, and log every refused file with its size.
cache_load measured pipeline-cache.bin with ftell into a long, which is 32 bits on Windows: a file past 2 GB measured as -1, so the run started with an empty cache and the log said nothing, not even that the file was over pipeline_cache_max_mb. The read now lives in runtime/vk_cache_file.h: the length comes from the 64-bit tell, the read runs in 64 MB pieces, and a refusal returns its reason and the file's size, which cache_load logs. The Windows atomic write no longer cuts a block of 4 GB or more to a 32-bit count.

tests/pipeline_cache_file_check.py reads a sparse file just over 2 GB through the header. With --probe-prewarm and such a file carrying this GPU's header, the log reads "holds 2048 MB, over NX2NAVI_PIPELINE_CACHE_MAX_MB 512" under the default limit and "2048.0 MB read" with the limit off; the exe before this change logged nothing for the same file.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 04:29:31 -05:00

67 lines
3.0 KiB
C

// Reads pipeline-cache.bin into memory for cache_load (runtime/vk_pipeline.c), with 64-bit sizes on every platform.
// ftell returns a long, which is 32 bits on Windows: a file past 2 GB measured as -1 there, and the run started with an
// empty cache without a word in the log. The length comes from the 64-bit tell, the read runs in pieces no C library
// call has to take whole, and every refusal names its reason and the file's size, so cache_load can log it.
// tests/pipeline_cache_file_check.py reads a sparse file just over 2 GB through it.
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
enum {
CACHE_FILE_OK,
CACHE_FILE_NONE, // no such file: a first run
CACHE_FILE_UNSIZED, // the file's length could not be measured
CACHE_FILE_SHORT, // not longer than the header every cache starts with
CACHE_FILE_OVER_CAP, // longer than the limit; not read
CACHE_FILE_NO_MEMORY, // no block of the file's size
CACHE_FILE_READ_FAIL, // the read ended early
};
#define CACHE_FILE_PIECE ((size_t)64 << 20)
static inline const char *cache_file_why(int why) {
switch (why) {
case CACHE_FILE_UNSIZED: return "its size cannot be measured";
case CACHE_FILE_SHORT: return "it is shorter than a pipeline cache header";
case CACHE_FILE_OVER_CAP: return "it is over the size limit";
case CACHE_FILE_NO_MEMORY: return "there is no memory for a block of its size";
case CACHE_FILE_READ_FAIL: return "reading it ended early";
default: return "";
}
}
// The whole file in a malloc block, or NULL with *why set. *len is the file's length in bytes whenever it could be
// measured, read or not. min: a file of at most this many bytes is refused; cap: so is one longer than cap (0: no limit),
// before any of it is read.
static inline void *cache_file_read(const char *path, uint64_t min, uint64_t cap, uint64_t *len, int *why) {
*len = 0;
FILE *f = fopen(path, "rb");
if (!f) return *why = CACHE_FILE_NONE, NULL;
#ifdef _WIN32
int64_t n = _fseeki64(f, 0, SEEK_END) ? -1 : _ftelli64(f);
if (n >= 0 && _fseeki64(f, 0, SEEK_SET)) n = -1;
#else
int64_t n = fseeko(f, 0, SEEK_END) ? -1 : (int64_t)ftello(f);
if (n >= 0 && fseeko(f, 0, SEEK_SET)) n = -1;
#endif
void *data = NULL;
if (n < 0) *why = CACHE_FILE_UNSIZED;
else if (*len = (uint64_t)n, (uint64_t)n <= min) *why = CACHE_FILE_SHORT;
else if (cap && (uint64_t)n > cap) *why = CACHE_FILE_OVER_CAP;
else if ((uint64_t)n > SIZE_MAX || !(data = malloc((size_t)n))) *why = CACHE_FILE_NO_MEMORY;
else {
size_t got = 0, want = (size_t)n;
while (got < want) {
size_t piece = want - got < CACHE_FILE_PIECE ? want - got : CACHE_FILE_PIECE;
size_t r = fread((char *)data + got, 1, piece, f);
got += r;
if (r != piece) break;
}
if (got == want) *why = CACHE_FILE_OK;
else free(data), data = NULL, *why = CACHE_FILE_READ_FAIL;
}
fclose(f);
return data;
}