/* SPDX-License-Identifier: LGPL-2.1-only */ /* Copyright (C) 2020-2021 Gediminas Jakutis */ #include #include #include #include #include #include #include "defs.h" #include "stream.h" #include "cache.h" static int cache_readfile(struct stream * const in); int cache_create(struct stream * const in) { int ret = 0; void *cache; try(in->settings->access != cached, err, EINVAL, "cannot create cache: stream is uncached"); try(!(cache = calloc(in->n, in->settings->stride)), err, ENOMEM, "out of memory"); in->cache = cache; in->cnode = NULL; err: return ret; } int cache_populate(struct stream * const in) { int ret = 0; size_t i; struct entry_l *tmp; struct entry_l tmp_store; try(in->settings->access != cached, err, EINVAL, "cannot populate cache: stream is uncached"); try(!in->cache, err, EINVAL, "stream has no cache allocated"); /* if reading a a randstream, fall back to the one-element-at-a-time mode */ if (in->type == stream_randread) { for (i = 0; i < in->n && !ret; ++i) { errno = 0; tmp = in->get(in, &tmp_store); if (tmp) { /* non-cache reads CAN fail */ in->put(in, tmp); } else { ret = errno; break; } } } else if (in->type == stream_in) { try_s((ret == cache_readfile(in)), err); } else { try(1, err, EINVAL, "cannot populate a non-reading stream cache"); } err: return ret; } int cache_destroy(struct stream * const in) { int ret = 0; try(in->settings->access != cached, err, EINVAL, "cannot destroy cache: stream is uncached"); free(in->cache); in->cache = NULL; err: return ret; } int cache_transfer(struct stream * const src, struct stream * const dest) { int ret = 0; try(src->settings->access != cached || dest->settings->access != cached, err, EINVAL, "cannot transfer caches of uncached streams"); try(!src->cache, err, EINVAL, "no cache to transfer"); try(dest->cache, err, EINVAL, "cannot transfer cache: recipient cache already exists"); dest->cache = src->cache; src->cache = NULL; err: return ret; } /* Optimized copying routine for cached array streams. * The layout let's us basically just use memcpy instead of slow and wasteful * get-put loop, all while completely avoiding the inefficient get-loop for seeking. */ int cache_block_copy(struct stream * const restrict src, struct stream * const restrict dest) { int ret = 0; try(src->settings->access != cached || dest->settings->access != cached, err, EINVAL, "cannot cache-copy between uncached streams"); try(!src->cache, err, EINVAL, "no cache to transfer"); try(src->n < dest->settings->to, err, EINVAL, "invalid copy size"); try(!dest->cache, err, EINVAL, "no cache to transfer to"); memcpy(dest->cache, src->cache_a + dest->settings->ss, (dest->settings->to - dest->settings->ss) * dest->settings->stride); dest->n = dest->settings->to - dest->settings->ss; err: return ret; } struct entry_l *cached_get_array(struct stream * const in, struct entry_l * const store) { struct entry_l *ret = NULL; if (in->index < in->n) { store->val = in->cache_a[in->index].val; ++in->index; ret = store; } return ret; } struct entry_l *cached_get_list(struct stream * const in, struct entry_l * const store) { struct entry_l *ret = NULL; if (in->cnode) { ret = in->cnode; in->cnode = ret->next; *store = *ret; ret = store; } return ret; } int cached_put_array(struct stream * const in, const struct entry_l * const data) { int ret = 0; if (in->index < in->n) { in->cache_a[in->index].val = data->val; ++in->index; } return ret; } /* This is were fun with the fact mergesort is NOT an in-place algorightm begins. * Since we only ever need to "put" on a) generating data and b) merging lists, * we basically have to generate the list anew each and every time. For generating, * it's a no-brainer, but for lists, while reusing existing nodes COULD be done in * a chached variant, file variant cannot do this, as cross-file links are not * something that could be handled without going into pretty insane (and laughably * inefficient) lenghts either way. * * And thus alas, we have no option but to just make a list anew */ int cached_put_list(struct stream * const restrict in, const struct entry_l * const node) { int ret = 0; try(in->index >= in->n, err, EINVAL, "can't add element: out of cache bounds"); if (!in->cnode) { /* if this is the very first one */ in->cnode = in->cache_l; } else { in->cnode->next = in->cnode + 1; /* lol says librin, lmao */ in->cnode = in->cnode->next; } in->cnode->val = node->val; in->cnode->next = NULL; err: return ret; } static int cache_readfile(struct stream * const in) { ssize_t ret = 0; size_t remaining = in->n * in->settings->stride; ssize_t bytesread = 0; size_t i; try(in->fd < 3, err, EINVAL, "no file open for reading"); do { ret = read(in->fd, in->cache + bytesread, remaining); if (ret < 0) { try(errno != EAGAIN, err, errno, "Writing to stream failed with %zi", ret); } else { bytesread += ret; remaining -= ret; } } while (ret); /* if this is a list, we need to adjust the link pointers from file offsets * to buffer addresses. 'Cept for the last one, which needs to be NULL. */ if (in->settings->format == list) { for (i = 0; i < (in->n - 1); ++i) { in->cache_l[i].nextaddr = in->cache + in->cache_l[i].offset; } in->cnode = in->cache_l; } err: return ret; }