/* SPDX-License-Identifier: LGPL-2.1-only */ /* Copyright (C) 2020 Gediminas Jakutis */ #include #include #include #include #include #include "defs.h" #include "stream.h" #include "cache.h" 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"); /* yeah... */ in->cache = cache; in->cache_a = cache; in->cache_l = cache; err: return ret; } int cache_populate(struct stream * const in) { int ret = 0; size_t i; struct entry_l *tmp; 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_next_element_direct(in); if (tmp) { /* non-cache reads CAN fail */ put(in, tmp); } else { ret = errno; break; } } } else if (in->type == stream_in) { try_s((ret == stream_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_l = NULL; in->cache_a = NULL; 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; dest->cache_a = (struct entry *) src->cache; dest->cache_l = (struct entry_l *) src->cache; src->cache = NULL; src->cache_a = NULL; src->cache_l = NULL; err: return ret; } int cache_block_copy(struct stream const * const src, struct stream * const 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; } int cache_list_copy(struct stream * const src, struct stream * const dest) { int ret = 0; struct entry_l *tmp; 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"); while ((tmp = get(src))) { put(dest, tmp); } cache_rewind(src); err: return ret; } int cache_block_split(struct stream * const src, struct stream * const A, struct stream * const B) { int ret = 0; struct settings tmp_settings; try(src->n < 2, err, EINVAL, "cannot split single element stream."); /* setting up minimal stream basics */ A->n = src->n / 2; B->n = src->n / 2 + (src->n & 1ul); A->index = B->index = 0; A->type = B->type = stream_cache; /* disallow any stream operations other than split/merge on children */ A->fd = B->fd = -1; /* if we're splitting, these are for holding cache only */ /* we only care about these three functions for these temporary streams */ A->get_next_element_cache = B->get_next_element_cache = src->get_next_element_cache; A->place_next_element_cache = B->place_next_element_cache = src->place_next_element_cache; A->split = B->split = src->split; A->rewind = B->rewind = src->rewind; tmp_settings = *src->settings; A->settings = B->settings = &tmp_settings; /* setting up A */ tmp_settings.ss = 0; tmp_settings.to = A->n; try_s((ret = cache_create(A)), err); try_s((ret = cache_block_copy(src, A)), err); /* setting up B */ tmp_settings.ss = A->n; tmp_settings.to = src->n; try_s((ret = cache_create(B)), err); try_s((ret = cache_block_copy(src, B)), err); A->settings = B->settings = src->settings; err: return ret; } int cache_list_split(struct stream * const src, struct stream * const A, struct stream * const B) { int ret = ENOSYS; (void) src; (void) A; (void) B; rin_warn("stub!"); return ret; } struct entry_l *cached_get_array(struct stream * const in) { struct entry_l *ret = NULL; if (in->index < in->n) { ret = (struct entry_l *)(in->cache_a + in->index); ++in->index; } return ret; } struct entry_l *cached_get_list(struct stream * const in) { struct entry_l *ret = NULL; if (in->index < in->n) { ret = in->cnode; in->cnode = ret->next; ++in->index; } 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; if (!in->cnode) { /* if this is the very first one */ in->pnode = NULL; in->cnode = in->cache_l; in->cnode->val = node->val; in->cnode->next = NULL; in->index = 0; } else { in->pnode = in->cnode; in->cnode = in->cnode + 1; /* lol says librin, lmao */ in->pnode->next = in->cnode; in->cnode->val = node->val; in->cnode->next = NULL; ++in->index; } return ret; } int cache_rewind(struct stream * const in) { int ret = 0; in->pnode = NULL; in->cnode = in->cnode ? in->cache_l : NULL; in->index = 0; return ret; }