2026-04-08 21:46:51 +02:00
|
|
|
#include <cstring>
|
|
|
|
|
#include <mutex>
|
|
|
|
|
#include <string>
|
2026-04-08 20:03:25 -06:00
|
|
|
#include <filesystem>
|
2026-04-08 21:46:51 +02:00
|
|
|
|
2026-05-10 18:54:59 +02:00
|
|
|
#include "../fs_helper.hpp"
|
2026-04-08 21:46:51 +02:00
|
|
|
#include "../internal.hpp"
|
2026-04-08 20:03:25 -06:00
|
|
|
#include "../sqlite_utils.hpp"
|
|
|
|
|
|
|
|
|
|
#include <sqlite3.h>
|
|
|
|
|
#include <fmt/format.h>
|
2026-04-08 21:46:51 +02:00
|
|
|
#if defined(AURORA_CACHE_USE_ZSTD)
|
2026-04-08 20:03:25 -06:00
|
|
|
#include <zstd.h>
|
2026-04-08 21:46:51 +02:00
|
|
|
#endif
|
|
|
|
|
#define XXH_STATIC_LINKING_ONLY
|
|
|
|
|
#include <xxhash.h>
|
|
|
|
|
|
|
|
|
|
namespace aurora::webgpu {
|
|
|
|
|
static Module Log("aurora::gpu::cache");
|
|
|
|
|
|
|
|
|
|
static sqlite3* db;
|
|
|
|
|
static sqlite3_stmt* load_stmt;
|
|
|
|
|
static sqlite3_stmt* store_stmt;
|
|
|
|
|
static bool cache_broken;
|
|
|
|
|
static std::mutex cache_mutex;
|
|
|
|
|
#if defined(AURORA_CACHE_USE_ZSTD)
|
|
|
|
|
static std::vector<uint8_t> compress_buffer;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
constexpr int CACHE_SCHEMA = 2;
|
|
|
|
|
|
|
|
|
|
static void init_abort() {
|
|
|
|
|
cache_broken = true;
|
|
|
|
|
sqlite3_close(db);
|
|
|
|
|
db = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int check(int ret) {
|
|
|
|
|
if (ret != SQLITE_OK) {
|
|
|
|
|
Log.error("SQLite operation failed: {}", sqlite3_errmsg(db));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool ensure_schema_up_to_date() {
|
2026-04-08 20:03:25 -06:00
|
|
|
sqlite::Transaction tx(db, Log, true);
|
2026-04-08 21:46:51 +02:00
|
|
|
if (!tx) {
|
|
|
|
|
Log.error("Failed to open schema check transaction", sqlite3_errmsg(db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 20:03:25 -06:00
|
|
|
auto ret = sqlite::exec(db, "CREATE TABLE IF NOT EXISTS aurora_schema(value INTEGER);");
|
2026-04-08 21:46:51 +02:00
|
|
|
if (ret != SQLITE_OK) {
|
|
|
|
|
Log.error("Failed to create schema table: {}", sqlite3_errmsg(db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool match = false;
|
|
|
|
|
auto cmd = fmt::format("SELECT * FROM aurora_schema WHERE value = {}", CACHE_SCHEMA);
|
2026-04-08 20:03:25 -06:00
|
|
|
ret = sqlite::exec(db, cmd.c_str(), [&match](int, char**, char**) { match = true; }, nullptr);
|
2026-04-08 21:46:51 +02:00
|
|
|
if (ret != SQLITE_OK) {
|
|
|
|
|
Log.error("Failed to check schema table: {}", sqlite3_errmsg(db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd = fmt::format(
|
|
|
|
|
R"(DROP TABLE IF EXISTS cache;
|
|
|
|
|
CREATE TABLE cache (
|
|
|
|
|
key BLOB PRIMARY KEY NOT NULL,
|
|
|
|
|
value BLOB NOT NULL,
|
|
|
|
|
size INTEGER NOT NULL,
|
|
|
|
|
compressed INTEGER NOT NULL
|
|
|
|
|
);
|
|
|
|
|
DELETE FROM aurora_schema;
|
|
|
|
|
INSERT INTO aurora_schema VALUES ({});)",
|
|
|
|
|
CACHE_SCHEMA);
|
2026-04-08 20:03:25 -06:00
|
|
|
ret = sqlite::exec(db, cmd.c_str());
|
2026-04-08 21:46:51 +02:00
|
|
|
if (ret != SQLITE_OK) {
|
|
|
|
|
Log.error("Failed to update schema: {}", sqlite3_errmsg(db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.commit();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool cache_init_core() {
|
|
|
|
|
Log.debug("SQLite version {}", sqlite3_libversion());
|
|
|
|
|
|
2026-05-13 00:45:19 -06:00
|
|
|
std::string file = fs_path_to_string(std::filesystem::path{reinterpret_cast<const char8_t*>(g_config.cachePath)} / "dawn_cache.db");
|
2026-04-08 21:46:51 +02:00
|
|
|
Log.debug("Using dawn cache at {}", file);
|
|
|
|
|
auto ret = sqlite3_open(file.c_str(), &db);
|
|
|
|
|
if (ret != SQLITE_OK) {
|
|
|
|
|
Log.error("Failed to open database: {}", sqlite3_errmsg(db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WAL mode + NORMAL = no need for disk syncs, consistent but not durable is fine.
|
2026-04-08 20:03:25 -06:00
|
|
|
ret = sqlite::exec(db, "PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;");
|
2026-04-08 21:46:51 +02:00
|
|
|
if (ret != SQLITE_OK) {
|
|
|
|
|
Log.error("Failed to set pragmas: {}", sqlite3_errmsg(db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ensure_schema_up_to_date()) {
|
|
|
|
|
Log.error("Failed to validate schema");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v3(db, "SELECT value, size, compressed FROM cache WHERE key = ?", -1, SQLITE_PREPARE_PERSISTENT,
|
|
|
|
|
&load_stmt, nullptr);
|
|
|
|
|
if (ret != SQLITE_OK) {
|
|
|
|
|
Log.error("Failed to prepare statement: {}", sqlite3_errmsg(db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = sqlite3_prepare_v3(db, "REPLACE INTO cache (key, value, size, compressed) VALUES (?, ?, ?, ?)", -1,
|
|
|
|
|
SQLITE_PREPARE_PERSISTENT, &store_stmt, nullptr);
|
|
|
|
|
if (ret != SQLITE_OK) {
|
|
|
|
|
Log.error("Failed to prepare statement: {}", sqlite3_errmsg(db));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool cache_init() {
|
|
|
|
|
if (cache_broken) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (db) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!cache_init_core()) {
|
|
|
|
|
Log.error("SQLite DB init failed");
|
|
|
|
|
init_abort();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log.debug("SQLite cache init succeeded");
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t load_from_cache(void const* key, size_t keySize, void* value, size_t valueSize, void*) {
|
|
|
|
|
std::lock_guard lock(cache_mutex);
|
|
|
|
|
|
|
|
|
|
if (!cache_init()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 20:03:25 -06:00
|
|
|
sqlite::Transaction tx(db, Log);
|
2026-04-08 21:46:51 +02:00
|
|
|
if (!tx) {
|
|
|
|
|
Log.error("Failed to open load transaction");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto keyHash = XXH128(key, keySize, 0);
|
|
|
|
|
check(sqlite3_bind_blob(load_stmt, 1, &keyHash, sizeof(keyHash), SQLITE_TRANSIENT));
|
|
|
|
|
|
|
|
|
|
const auto ret = sqlite3_step(load_stmt);
|
|
|
|
|
size_t foundSize;
|
|
|
|
|
if (ret == SQLITE_ROW) {
|
|
|
|
|
// Hit
|
|
|
|
|
const auto foundPtr = sqlite3_column_blob(load_stmt, 0);
|
|
|
|
|
foundSize = sqlite3_column_int64(load_stmt, 1);
|
|
|
|
|
const bool compressed = sqlite3_column_int(load_stmt, 2) != 0;
|
|
|
|
|
|
|
|
|
|
if (value && valueSize == foundSize) {
|
|
|
|
|
if (compressed) {
|
|
|
|
|
#if defined(AURORA_CACHE_USE_ZSTD)
|
|
|
|
|
const auto compSize = sqlite3_column_bytes(load_stmt, 0);
|
|
|
|
|
const auto zstdRet = ZSTD_decompress(value, valueSize, foundPtr, compSize);
|
|
|
|
|
if (ZSTD_isError(zstdRet)) {
|
|
|
|
|
Log.error("zstd decompression error: {}", ZSTD_getErrorName(zstdRet));
|
|
|
|
|
foundSize = 0;
|
|
|
|
|
} else if (zstdRet != foundSize) {
|
|
|
|
|
Log.error("zstd decompression size mismatch: expected {}, got {}", foundSize, zstdRet);
|
|
|
|
|
foundSize = 0;
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
Log.error("Cache entry is zstd-compressed but zstd support is disabled");
|
|
|
|
|
foundSize = 0;
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
|
|
|
|
if (foundSize != 0 && !foundPtr) {
|
|
|
|
|
Log.error("Cache entry is missing raw value data");
|
|
|
|
|
foundSize = 0;
|
|
|
|
|
} else if (foundSize != 0) {
|
|
|
|
|
std::memcpy(value, foundPtr, foundSize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (ret == SQLITE_DONE) {
|
|
|
|
|
// Miss
|
|
|
|
|
foundSize = 0;
|
|
|
|
|
} else {
|
|
|
|
|
Log.error("Looking up cache key failed: {}", sqlite3_errmsg(db));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
check(sqlite3_reset(load_stmt));
|
|
|
|
|
|
|
|
|
|
return foundSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void store_to_cache(void const* key, size_t keySize, void const* value, size_t valueSize, void*) {
|
|
|
|
|
std::lock_guard lock(cache_mutex);
|
|
|
|
|
|
|
|
|
|
if (!cache_init()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 20:03:25 -06:00
|
|
|
sqlite::Transaction tx(db, Log, true);
|
2026-04-08 21:46:51 +02:00
|
|
|
if (!tx) {
|
|
|
|
|
Log.error("Failed to open store transaction");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const void* storedValue = value;
|
|
|
|
|
sqlite3_uint64 storedValueSize = valueSize;
|
|
|
|
|
int compressed = 0;
|
|
|
|
|
#if defined(AURORA_CACHE_USE_ZSTD)
|
|
|
|
|
const auto bound = ZSTD_compressBound(valueSize);
|
|
|
|
|
if (ZSTD_isError(bound)) {
|
|
|
|
|
Log.error("Failed to calculate ZSTD_compressBound: {}", ZSTD_getErrorName(bound));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (compress_buffer.size() < bound) {
|
|
|
|
|
compress_buffer.resize(bound);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto compressRet = ZSTD_compress(compress_buffer.data(), compress_buffer.size(), value, valueSize, 0);
|
|
|
|
|
if (ZSTD_isError(compressRet)) {
|
|
|
|
|
Log.error("ZSTD compression error: {}", ZSTD_getErrorName(compressRet));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (compressRet < valueSize) {
|
|
|
|
|
storedValue = compress_buffer.data();
|
|
|
|
|
storedValueSize = compressRet;
|
|
|
|
|
compressed = 1;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
const auto keyHash = XXH128(key, keySize, 0);
|
|
|
|
|
check(sqlite3_bind_blob64(store_stmt, 1, &keyHash, sizeof(keyHash), SQLITE_TRANSIENT));
|
|
|
|
|
check(
|
|
|
|
|
sqlite3_bind_blob64(store_stmt, 2, storedValue, storedValueSize, compressed ? SQLITE_STATIC : SQLITE_TRANSIENT));
|
|
|
|
|
check(sqlite3_bind_int64(store_stmt, 3, static_cast<sqlite3_int64>(valueSize)));
|
|
|
|
|
check(sqlite3_bind_int(store_stmt, 4, compressed));
|
|
|
|
|
|
|
|
|
|
const auto ret = sqlite3_step(store_stmt);
|
|
|
|
|
if (ret != SQLITE_DONE) {
|
|
|
|
|
// Error or something
|
|
|
|
|
Log.error("Failed to insert row: {}", sqlite3_errmsg(db));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
check(sqlite3_reset(store_stmt));
|
|
|
|
|
check(sqlite3_bind_null(store_stmt, 2));
|
|
|
|
|
check(sqlite3_bind_null(store_stmt, 4));
|
|
|
|
|
|
|
|
|
|
tx.commit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cache_shutdown() {
|
|
|
|
|
#if defined(AURORA_CACHE_USE_ZSTD)
|
|
|
|
|
compress_buffer.clear();
|
|
|
|
|
#endif
|
|
|
|
|
check(sqlite3_finalize(load_stmt));
|
|
|
|
|
check(sqlite3_finalize(store_stmt));
|
|
|
|
|
check(sqlite3_close(db));
|
|
|
|
|
db = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace aurora::webgpu
|