| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright (C) 2021-2023 Oracle. All Rights Reserved. | |
| * Author: Darrick J. Wong <djwong@kernel.org> | |
| */ | |
| #include "xfs.h" | |
| #include "xfs_fs.h" | |
| #include "xfs_shared.h" | |
| #include "xfs_format.h" | |
| #include "scrub/xfile.h" | |
| #include "scrub/xfarray.h" | |
| #include "scrub/scrub.h" | |
| #include "scrub/trace.h" | |
| /* | |
| * Large Arrays of Fixed-Size Records | |
| * ================================== | |
| * | |
| * This memory array uses an xfile (which itself is a memfd "file") to store | |
| * large numbers of fixed-size records in memory that can be paged out. This | |
| * puts less stress on the memory reclaim algorithms during an online repair | |
| * because we don't have to pin so much memory. However, array access is less | |
| * direct than would be in a regular memory array. Access to the array is | |
| * performed via indexed load and store methods, and an append method is | |
| * provided for convenience. Array elements can be unset, which sets them to | |
| * all zeroes. Unset entries are skipped during iteration, though direct loads | |
| * will return a zeroed buffer. Callers are responsible for concurrency | |
| * control. | |
| */ | |
| /* | |
| * Pointer to scratch space. Because we can't access the xfile data directly, | |
| * we allocate a small amount of memory on the end of the xfarray structure to | |
| * buffer array items when we need space to store values temporarily. | |
| */ | |
| static inline void *xfarray_scratch(struct xfarray *array) | |
| { | |
| return (array + 1); | |
| } | |
| /* Compute array index given an xfile offset. */ | |
| static xfarray_idx_t | |
| xfarray_idx( | |
| struct xfarray *array, | |
| loff_t pos) | |
| { | |
| if (array->obj_size_log >= 0) | |
| return (xfarray_idx_t)pos >> array->obj_size_log; | |
| return div_u64((xfarray_idx_t)pos, array->obj_size); | |
| } | |
| /* Compute xfile offset of array element. */ | |
| static inline loff_t xfarray_pos(struct xfarray *array, xfarray_idx_t idx) | |
| { | |
| if (array->obj_size_log >= 0) | |
| return idx << array->obj_size_log; | |
| return idx * array->obj_size; | |
| } | |
| /* | |
| * Initialize a big memory array. Array records cannot be larger than a | |
| * page, and the array cannot span more bytes than the page cache supports. | |
| * If @required_capacity is nonzero, the maximum array size will be set to this | |
| * quantity and the array creation will fail if the underlying storage cannot | |
| * support that many records. | |
| */ | |
| int | |
| xfarray_create( | |
| const char *description, | |
| unsigned long long required_capacity, | |
| size_t obj_size, | |
| struct xfarray **arrayp) | |
| { | |
| struct xfarray *array; | |
| struct xfile *xfile; | |
| int error; | |
| ASSERT(obj_size < PAGE_SIZE); | |
| error = xfile_create(description, 0, &xfile); | |
| if (error) | |
| return error; | |
| error = -ENOMEM; | |
| array = kzalloc(sizeof(struct xfarray) + obj_size, XCHK_GFP_FLAGS); | |
| if (!array) | |
| goto out_xfile; | |
| array->xfile = xfile; | |
| array->obj_size = obj_size; | |
| if (is_power_of_2(obj_size)) | |
| array->obj_size_log = ilog2(obj_size); | |
| else | |
| array->obj_size_log = -1; | |
| array->max_nr = xfarray_idx(array, MAX_LFS_FILESIZE); | |
| trace_xfarray_create(array, required_capacity); | |
| if (required_capacity > 0) { | |
| if (array->max_nr < required_capacity) { | |
| error = -ENOMEM; | |
| goto out_xfarray; | |
| } | |
| array->max_nr = required_capacity; | |
| } | |
| *arrayp = array; | |
| return 0; | |
| out_xfarray: | |
| kfree(array); | |
| out_xfile: | |
| xfile_destroy(xfile); | |
| return error; | |
| } | |
| /* Destroy the array. */ | |
| void | |
| xfarray_destroy( | |
| struct xfarray *array) | |
| { | |
| xfile_destroy(array->xfile); | |
| kfree(array); | |
| } | |
| /* Load an element from the array. */ | |
| int | |
| xfarray_load( | |
| struct xfarray *array, | |
| xfarray_idx_t idx, | |
| void *ptr) | |
| { | |
| if (idx >= array->nr) | |
| return -ENODATA; | |
| return xfile_obj_load(array->xfile, ptr, array->obj_size, | |
| xfarray_pos(array, idx)); | |
| } | |
| /* Is this array element potentially unset? */ | |
| static inline bool | |
| xfarray_is_unset( | |
| struct xfarray *array, | |
| loff_t pos) | |
| { | |
| void *temp = xfarray_scratch(array); | |
| int error; | |
| if (array->unset_slots == 0) | |
| return false; | |
| error = xfile_obj_load(array->xfile, temp, array->obj_size, pos); | |
| if (!error && xfarray_element_is_null(array, temp)) | |
| return true; | |
| return false; | |
| } | |
| /* | |
| * Unset an array element. If @idx is the last element in the array, the | |
| * array will be truncated. Otherwise, the entry will be zeroed. | |
| */ | |
| int | |
| xfarray_unset( | |
| struct xfarray *array, | |
| xfarray_idx_t idx) | |
| { | |
| void *temp = xfarray_scratch(array); | |
| loff_t pos = xfarray_pos(array, idx); | |
| int error; | |
| if (idx >= array->nr) | |
| return -ENODATA; | |
| if (idx == array->nr - 1) { | |
| array->nr--; | |
| return 0; | |
| } | |
| if (xfarray_is_unset(array, pos)) | |
| return 0; | |
| memset(temp, 0, array->obj_size); | |
| error = xfile_obj_store(array->xfile, temp, array->obj_size, pos); | |
| if (error) | |
| return error; | |
| array->unset_slots++; | |
| return 0; | |
| } | |
| /* | |
| * Store an element in the array. The element must not be completely zeroed, | |
| * because those are considered unset sparse elements. | |
| */ | |
| int | |
| xfarray_store( | |
| struct xfarray *array, | |
| xfarray_idx_t idx, | |
| const void *ptr) | |
| { | |
| int ret; | |
| if (idx >= array->max_nr) | |
| return -EFBIG; | |
| ASSERT(!xfarray_element_is_null(array, ptr)); | |
| ret = xfile_obj_store(array->xfile, ptr, array->obj_size, | |
| xfarray_pos(array, idx)); | |
| if (ret) | |
| return ret; | |
| array->nr = max(array->nr, idx + 1); | |
| return 0; | |
| } | |
| /* Is this array element NULL? */ | |
| bool | |
| xfarray_element_is_null( | |
| struct xfarray *array, | |
| const void *ptr) | |
| { | |
| return !memchr_inv(ptr, 0, array->obj_size); | |
| } | |
| /* | |
| * Store an element anywhere in the array that is unset. If there are no | |
| * unset slots, append the element to the array. | |
| */ | |
| int | |
| xfarray_store_anywhere( | |
| struct xfarray *array, | |
| const void *ptr) | |
| { | |
| void *temp = xfarray_scratch(array); | |
| loff_t endpos = xfarray_pos(array, array->nr); | |
| loff_t pos; | |
| int error; | |
| /* Find an unset slot to put it in. */ | |
| for (pos = 0; | |
| pos < endpos && array->unset_slots > 0; | |
| pos += array->obj_size) { | |
| error = xfile_obj_load(array->xfile, temp, array->obj_size, | |
| pos); | |
| if (error || !xfarray_element_is_null(array, temp)) | |
| continue; | |
| error = xfile_obj_store(array->xfile, ptr, array->obj_size, | |
| pos); | |
| if (error) | |
| return error; | |
| array->unset_slots--; | |
| return 0; | |
| } | |
| /* No unset slots found; attach it on the end. */ | |
| array->unset_slots = 0; | |
| return xfarray_append(array, ptr); | |
| } | |
| /* Return length of array. */ | |
| uint64_t | |
| xfarray_length( | |
| struct xfarray *array) | |
| { | |
| return array->nr; | |
| } | |
| /* | |
| * Decide which array item we're going to read as part of an _iter_get. | |
| * @cur is the array index, and @pos is the file offset of that array index in | |
| * the backing xfile. Returns ENODATA if we reach the end of the records. | |
| * | |
| * Reading from a hole in a sparse xfile causes page instantiation, so for | |
| * iterating a (possibly sparse) array we need to figure out if the cursor is | |
| * pointing at a totally uninitialized hole and move the cursor up if | |
| * necessary. | |
| */ | |
| static inline int | |
| xfarray_find_data( | |
| struct xfarray *array, | |
| xfarray_idx_t *cur, | |
| loff_t *pos) | |
| { | |
| unsigned int pgoff = offset_in_page(*pos); | |
| loff_t end_pos = *pos + array->obj_size - 1; | |
| loff_t new_pos; | |
| /* | |
| * If the current array record is not adjacent to a page boundary, we | |
| * are in the middle of the page. We do not need to move the cursor. | |
| */ | |
| if (pgoff != 0 && pgoff + array->obj_size - 1 < PAGE_SIZE) | |
| return 0; | |
| /* | |
| * Call SEEK_DATA on the last byte in the record we're about to read. | |
| * If the record ends at (or crosses) the end of a page then we know | |
| * that the first byte of the record is backed by pages and don't need | |
| * to query it. If instead the record begins at the start of the page | |
| * then we know that querying the last byte is just as good as querying | |
| * the first byte, since records cannot be larger than a page. | |
| * | |
| * If the call returns the same file offset, we know this record is | |
| * backed by real pages. We do not need to move the cursor. | |
| */ | |
| new_pos = xfile_seek_data(array->xfile, end_pos); | |
| if (new_pos == -ENXIO) | |
| return -ENODATA; | |
| if (new_pos < 0) | |
| return new_pos; | |
| if (new_pos == end_pos) | |
| return 0; | |
| /* | |
| * Otherwise, SEEK_DATA told us how far up to move the file pointer to | |
| * find more data. Move the array index to the first record past the | |
| * byte offset we were given. | |
| */ | |
| new_pos = roundup_64(new_pos, array->obj_size); | |
| *cur = xfarray_idx(array, new_pos); | |
| *pos = xfarray_pos(array, *cur); | |
| return 0; | |
| } | |
| /* | |
| * Starting at *idx, fetch the next non-null array entry and advance the index | |
| * to set up the next _load_next call. Returns ENODATA if we reach the end of | |
| * the array. Callers must set @*idx to XFARRAY_CURSOR_INIT before the first | |
| * call to this function. | |
| */ | |
| int | |
| xfarray_load_next( | |
| struct xfarray *array, | |
| xfarray_idx_t *idx, | |
| void *rec) | |
| { | |
| xfarray_idx_t cur = *idx; | |
| loff_t pos = xfarray_pos(array, cur); | |
| int error; | |
| do { | |
| if (cur >= array->nr) | |
| return -ENODATA; | |
| /* | |
| * Ask the backing store for the location of next possible | |
| * written record, then retrieve that record. | |
| */ | |
| error = xfarray_find_data(array, &cur, &pos); | |
| if (error) | |
| return error; | |
| error = xfarray_load(array, cur, rec); | |
| if (error) | |
| return error; | |
| cur++; | |
| pos += array->obj_size; | |
| } while (xfarray_element_is_null(array, rec)); | |
| *idx = cur; | |
| return 0; | |
| } | |
| /* Sorting functions */ | |
| #ifdef DEBUG | |
| # define xfarray_sort_bump_loads(si) do { (si)->loads++; } while (0) | |
| # define xfarray_sort_bump_stores(si) do { (si)->stores++; } while (0) | |
| # define xfarray_sort_bump_compares(si) do { (si)->compares++; } while (0) | |
| # define xfarray_sort_bump_heapsorts(si) do { (si)->heapsorts++; } while (0) | |
| #else | |
| # define xfarray_sort_bump_loads(si) | |
| # define xfarray_sort_bump_stores(si) | |
| # define xfarray_sort_bump_compares(si) | |
| # define xfarray_sort_bump_heapsorts(si) | |
| #endif /* DEBUG */ | |
| /* Load an array element for sorting. */ | |
| static inline int | |
| xfarray_sort_load( | |
| struct xfarray_sortinfo *si, | |
| xfarray_idx_t idx, | |
| void *ptr) | |
| { | |
| xfarray_sort_bump_loads(si); | |
| return xfarray_load(si->array, idx, ptr); | |
| } | |
| /* Store an array element for sorting. */ | |
| static inline int | |
| xfarray_sort_store( | |
| struct xfarray_sortinfo *si, | |
| xfarray_idx_t idx, | |
| void *ptr) | |
| { | |
| xfarray_sort_bump_stores(si); | |
| return xfarray_store(si->array, idx, ptr); | |
| } | |
| /* Compare an array element for sorting. */ | |
| static inline int | |
| xfarray_sort_cmp( | |
| struct xfarray_sortinfo *si, | |
| const void *a, | |
| const void *b) | |
| { | |
| xfarray_sort_bump_compares(si); | |
| return si->cmp_fn(a, b); | |
| } | |
| /* Return a pointer to the low index stack for quicksort partitioning. */ | |
| static inline xfarray_idx_t *xfarray_sortinfo_lo(struct xfarray_sortinfo *si) | |
| { | |
| return (xfarray_idx_t *)(si + 1); | |
| } | |
| /* Return a pointer to the high index stack for quicksort partitioning. */ | |
| static inline xfarray_idx_t *xfarray_sortinfo_hi(struct xfarray_sortinfo *si) | |
| { | |
| return xfarray_sortinfo_lo(si) + si->max_stack_depth; | |
| } | |
| /* Size of each element in the quicksort pivot array. */ | |
| static inline size_t | |
| xfarray_pivot_rec_sz( | |
| struct xfarray *array) | |
| { | |
| return round_up(array->obj_size, 8) + sizeof(xfarray_idx_t); | |
| } | |
| /* Allocate memory to handle the sort. */ | |
| static inline int | |
| xfarray_sortinfo_alloc( | |
| struct xfarray *array, | |
| xfarray_cmp_fn cmp_fn, | |
| unsigned int flags, | |
| struct xfarray_sortinfo **infop) | |
| { | |
| struct xfarray_sortinfo *si; | |
| size_t nr_bytes = sizeof(struct xfarray_sortinfo); | |
| size_t pivot_rec_sz = xfarray_pivot_rec_sz(array); | |
| int max_stack_depth; | |
| /* | |
| * The median-of-nine pivot algorithm doesn't work if a subset has | |
| * fewer than 9 items. Make sure the in-memory sort will always take | |
| * over for subsets where this wouldn't be the case. | |
| */ | |
| BUILD_BUG_ON(XFARRAY_QSORT_PIVOT_NR >= XFARRAY_ISORT_NR); | |
| /* | |
| * Tail-call recursion during the partitioning phase means that | |
| * quicksort will never recurse more than log2(nr) times. We need one | |
| * extra level of stack to hold the initial parameters. In-memory | |
| * sort will always take care of the last few levels of recursion for | |
| * us, so we can reduce the stack depth by that much. | |
| */ | |
| max_stack_depth = ilog2(array->nr) + 1 - (XFARRAY_ISORT_SHIFT - 1); | |
| if (max_stack_depth < 1) | |
| max_stack_depth = 1; | |
| /* Each level of quicksort uses a lo and a hi index */ | |
| nr_bytes += max_stack_depth * sizeof(xfarray_idx_t) * 2; | |
| /* Scratchpad for in-memory sort, or finding the pivot */ | |
| nr_bytes += max_t(size_t, | |
| (XFARRAY_QSORT_PIVOT_NR + 1) * pivot_rec_sz, | |
| XFARRAY_ISORT_NR * array->obj_size); | |
| si = kvzalloc(nr_bytes, XCHK_GFP_FLAGS); | |
| if (!si) | |
| return -ENOMEM; | |
| si->array = array; | |
| si->cmp_fn = cmp_fn; | |
| si->flags = flags; | |
| si->max_stack_depth = max_stack_depth; | |
| si->max_stack_used = 1; | |
| xfarray_sortinfo_lo(si)[0] = 0; | |
| xfarray_sortinfo_hi(si)[0] = array->nr - 1; | |
| trace_xfarray_sort(si, nr_bytes); | |
| *infop = si; | |
| return 0; | |
| } | |
| /* Should this sort be terminated by a fatal signal? */ | |
| static inline bool | |
| xfarray_sort_terminated( | |
| struct xfarray_sortinfo *si, | |
| int *error) | |
| { | |
| /* | |
| * If preemption is disabled, we need to yield to the scheduler every | |
| * few seconds so that we don't run afoul of the soft lockup watchdog | |
| * or RCU stall detector. | |
| */ | |
| cond_resched(); | |
| if ((si->flags & XFARRAY_SORT_KILLABLE) && | |
| fatal_signal_pending(current)) { | |
| if (*error == 0) | |
| *error = -EINTR; | |
| return true; | |
| } | |
| return false; | |
| } | |
| /* Do we want an in-memory sort? */ | |
| static inline bool | |
| xfarray_want_isort( | |
| struct xfarray_sortinfo *si, | |
| xfarray_idx_t start, | |
| xfarray_idx_t end) | |
| { | |
| /* | |
| * For array subsets that fit in the scratchpad, it's much faster to | |
| * use the kernel's heapsort than quicksort's stack machine. | |
| */ | |
| return (end - start) < XFARRAY_ISORT_NR; | |
| } | |
| /* Return the scratch space within the sortinfo structure. */ | |
| static inline void *xfarray_sortinfo_isort_scratch(struct xfarray_sortinfo *si) | |
| { | |
| return xfarray_sortinfo_hi(si) + si->max_stack_depth; | |
| } | |
| /* | |
| * Sort a small number of array records using scratchpad memory. The records | |
| * need not be contiguous in the xfile's memory pages. | |
| */ | |
| STATIC int | |
| xfarray_isort( | |
| struct xfarray_sortinfo *si, | |
| xfarray_idx_t lo, | |
| xfarray_idx_t hi) | |
| { | |
| void *scratch = xfarray_sortinfo_isort_scratch(si); | |
| loff_t lo_pos = xfarray_pos(si->array, lo); | |
| loff_t len = xfarray_pos(si->array, hi - lo + 1); | |
| int error; | |
| trace_xfarray_isort(si, lo, hi); | |
| xfarray_sort_bump_loads(si); | |
| error = xfile_obj_load(si->array->xfile, scratch, len, lo_pos); | |
| if (error) | |
| return error; | |
| xfarray_sort_bump_heapsorts(si); | |
| sort(scratch, hi - lo + 1, si->array->obj_size, si->cmp_fn, NULL); | |
| xfarray_sort_bump_stores(si); | |
| return xfile_obj_store(si->array->xfile, scratch, len, lo_pos); | |
| } | |
| /* Grab a page for sorting records. */ | |
| static inline int | |
| xfarray_sort_get_page( | |
| struct xfarray_sortinfo *si, | |
| loff_t pos, | |
| uint64_t len) | |
| { | |
| int error; | |
| error = xfile_get_page(si->array->xfile, pos, len, &si->xfpage); | |
| if (error) | |
| return error; | |
| /* | |
| * xfile pages must never be mapped into userspace, so we skip the | |
| * dcache flush when mapping the page. | |
| */ | |
| si->page_kaddr = kmap_local_page(si->xfpage.page); | |
| return 0; | |
| } | |
| /* Release a page we grabbed for sorting records. */ | |
| static inline int | |
| xfarray_sort_put_page( | |
| struct xfarray_sortinfo *si) | |
| { | |
| if (!si->page_kaddr) | |
| return 0; | |
| kunmap_local(si->page_kaddr); | |
| si->page_kaddr = NULL; | |
| return xfile_put_page(si->array->xfile, &si->xfpage); | |
| } | |
| /* Decide if these records are eligible for in-page sorting. */ | |
| static inline bool | |
| xfarray_want_pagesort( | |
| struct xfarray_sortinfo *si, | |
| xfarray_idx_t lo, | |
| xfarray_idx_t hi) | |
| { | |
| pgoff_t lo_page; | |
| pgoff_t hi_page; | |
| loff_t end_pos; | |
| /* We can only map one page at a time. */ | |
| lo_page = xfarray_pos(si->array, lo) >> PAGE_SHIFT; | |
| end_pos = xfarray_pos(si->array, hi) + si->array->obj_size - 1; | |
| hi_page = end_pos >> PAGE_SHIFT; | |
| return lo_page == hi_page; | |
| } | |
| /* Sort a bunch of records that all live in the same memory page. */ | |
| STATIC int | |
| xfarray_pagesort( | |
| struct xfarray_sortinfo *si, | |
| xfarray_idx_t lo, | |
| xfarray_idx_t hi) | |
| { | |
| void *startp; | |
| loff_t lo_pos = xfarray_pos(si->array, lo); | |
| uint64_t len = xfarray_pos(si->array, hi - lo); | |
| int error = 0; | |
| trace_xfarray_pagesort(si, lo, hi); | |
| xfarray_sort_bump_loads(si); | |
| error = xfarray_sort_get_page(si, lo_pos, len); | |
| if (error) | |
| return error; | |
| xfarray_sort_bump_heapsorts(si); | |
| startp = si->page_kaddr + offset_in_page(lo_pos); | |
| sort(startp, hi - lo + 1, si->array->obj_size, si->cmp_fn, NULL); | |
| xfarray_sort_bump_stores(si); | |
| return xfarray_sort_put_page(si); | |
| } | |
| /* Return a pointer to the xfarray pivot record within the sortinfo struct. */ | |
| static inline void *xfarray_sortinfo_pivot(struct xfarray_sortinfo *si) | |
| { | |
| return xfarray_sortinfo_hi(si) + si->max_stack_depth; | |
| } | |
| /* Return a pointer to the start of the pivot array. */ | |
| static inline void * | |
| xfarray_sortinfo_pivot_array( | |
| struct xfarray_sortinfo *si) | |
| { | |
| return xfarray_sortinfo_pivot(si) + si->array->obj_size; | |
| } | |
| /* The xfarray record is stored at the start of each pivot array element. */ | |
| static inline void * | |
| xfarray_pivot_array_rec( | |
| void *pa, | |
| size_t pa_recsz, | |
| unsigned int pa_idx) | |
| { | |
| return pa + (pa_recsz * pa_idx); | |
| } | |
| /* The xfarray index is stored at the end of each pivot array element. */ | |
| static inline xfarray_idx_t * | |
| xfarray_pivot_array_idx( | |
| void *pa, | |
| size_t pa_recsz, | |
| unsigned int pa_idx) | |
| { | |
| return xfarray_pivot_array_rec(pa, pa_recsz, pa_idx + 1) - | |
| sizeof(xfarray_idx_t); | |
| } | |
| /* | |
| * Find a pivot value for quicksort partitioning, swap it with a[lo], and save | |
| * the cached pivot record for the next step. | |
| * | |
| * Load evenly-spaced records within the given range into memory, sort them, | |
| * and choose the pivot from the median record. Using multiple points will | |
| * improve the quality of the pivot selection, and hopefully avoid the worst | |
| * quicksort behavior, since our array values are nearly always evenly sorted. | |
| */ | |
| STATIC int | |
| xfarray_qsort_pivot( | |
| struct xfarray_sortinfo *si, | |
| xfarray_idx_t lo, | |
| xfarray_idx_t hi) | |
| { | |
| void *pivot = xfarray_sortinfo_pivot(si); | |
| void *parray = xfarray_sortinfo_pivot_array(si); | |
| void *recp; | |
| xfarray_idx_t *idxp; | |
| xfarray_idx_t step = (hi - lo) / (XFARRAY_QSORT_PIVOT_NR - 1); | |
| size_t pivot_rec_sz = xfarray_pivot_rec_sz(si->array); | |
| int i, j; | |
| int error; | |
| ASSERT(step > 0); | |
| /* | |
| * Load the xfarray indexes of the records we intend to sample into the | |
| * pivot array. | |
| */ | |
| idxp = xfarray_pivot_array_idx(parray, pivot_rec_sz, 0); | |
| *idxp = lo; | |
| for (i = 1; i < XFARRAY_QSORT_PIVOT_NR - 1; i++) { | |
| idxp = xfarray_pivot_array_idx(parray, pivot_rec_sz, i); | |
| *idxp = lo + (i * step); | |
| } | |
| idxp = xfarray_pivot_array_idx(parray, pivot_rec_sz, | |
| XFARRAY_QSORT_PIVOT_NR - 1); | |
| *idxp = hi; | |
| /* Load the selected xfarray records into the pivot array. */ | |
| for (i = 0; i < XFARRAY_QSORT_PIVOT_NR; i++) { | |
| xfarray_idx_t idx; | |
| recp = xfarray_pivot_array_rec(parray, pivot_rec_sz, i); | |
| idxp = xfarray_pivot_array_idx(parray, pivot_rec_sz, i); | |
| /* No unset records; load directly into the array. */ | |
| if (likely(si->array->unset_slots == 0)) { | |
| error = xfarray_sort_load(si, *idxp, recp); | |
| if (error) | |
| return error; | |
| continue; | |
| } | |
| /* | |
| * Load non-null records into the scratchpad without changing | |
| * the xfarray_idx_t in the pivot array. | |
| */ | |
| idx = *idxp; | |
| xfarray_sort_bump_loads(si); | |
| error = xfarray_load_next(si->array, &idx, recp); | |
| if (error) | |
| return error; | |
| } | |
| xfarray_sort_bump_heapsorts(si); | |
| sort(parray, XFARRAY_QSORT_PIVOT_NR, pivot_rec_sz, si->cmp_fn, NULL); | |
| /* | |
| * We sorted the pivot array records (which includes the xfarray | |
| * indices) in xfarray record order. The median element of the pivot | |
| * array contains the xfarray record that we will use as the pivot. | |
| * Copy that xfarray record to the designated space. | |
| */ | |
| recp = xfarray_pivot_array_rec(parray, pivot_rec_sz, | |
| XFARRAY_QSORT_PIVOT_NR / 2); | |
| memcpy(pivot, recp, si->array->obj_size); | |
| /* If the pivot record we chose was already in a[lo] then we're done. */ | |
| idxp = xfarray_pivot_array_idx(parray, pivot_rec_sz, | |
| XFARRAY_QSORT_PIVOT_NR / 2); | |
| if (*idxp == lo) | |
| return 0; | |
| /* | |
| * Find the cached copy of a[lo] in the pivot array so that we can swap | |
| * a[lo] and a[pivot]. | |
| */ | |
| for (i = 0, j = -1; i < XFARRAY_QSORT_PIVOT_NR; i++) { | |
| idxp = xfarray_pivot_array_idx(parray, pivot_rec_sz, i); | |
| if (*idxp == lo) | |
| j = i; | |
| } | |
| if (j < 0) { | |
| ASSERT(j >= 0); | |
| return -EFSCORRUPTED; | |
| } | |
| /* Swap a[lo] and a[pivot]. */ | |
| error = xfarray_sort_store(si, lo, pivot); | |
| if (error) | |
| return error; | |
| recp = xfarray_pivot_array_rec(parray, pivot_rec_sz, j); | |
| idxp = xfarray_pivot_array_idx(parray, pivot_rec_sz, | |
| XFARRAY_QSORT_PIVOT_NR / 2); | |
| return xfarray_sort_store(si, *idxp, recp); | |
| } | |
| /* | |
| * Set up the pointers for the next iteration. We push onto the stack all of | |
| * the unsorted values between a[lo + 1] and a[end[i]], and we tweak the | |
| * current stack frame to point to the unsorted values between a[beg[i]] and | |
| * a[lo] so that those values will be sorted when we pop the stack. | |
| */ | |
| static inline int | |
| xfarray_qsort_push( | |
| struct xfarray_sortinfo *si, | |
| xfarray_idx_t *si_lo, | |
| xfarray_idx_t *si_hi, | |
| xfarray_idx_t lo, | |
| xfarray_idx_t hi) | |
| { | |
| /* Check for stack overflows */ | |
| if (si->stack_depth >= si->max_stack_depth - 1) { | |
| ASSERT(si->stack_depth < si->max_stack_depth - 1); | |
| return -EFSCORRUPTED; | |
| } | |
| si->max_stack_used = max_t(uint8_t, si->max_stack_used, | |
| si->stack_depth + 2); | |
| si_lo[si->stack_depth + 1] = lo + 1; | |
| si_hi[si->stack_depth + 1] = si_hi[si->stack_depth]; | |
| si_hi[si->stack_depth++] = lo - 1; | |
| /* | |
| * Always start with the smaller of the two partitions to keep the | |
| * amount of recursion in check. | |
| */ | |
| if (si_hi[si->stack_depth] - si_lo[si->stack_depth] > | |
| si_hi[si->stack_depth - 1] - si_lo[si->stack_depth - 1]) { | |
| swap(si_lo[si->stack_depth], si_lo[si->stack_depth - 1]); | |
| swap(si_hi[si->stack_depth], si_hi[si->stack_depth - 1]); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Load an element from the array into the first scratchpad and cache the page, | |
| * if possible. | |
| */ | |
| static inline int | |
| xfarray_sort_load_cached( | |
| struct xfarray_sortinfo *si, | |
| xfarray_idx_t idx, | |
| void *ptr) | |
| { | |
| loff_t idx_pos = xfarray_pos(si->array, idx); | |
| pgoff_t startpage; | |
| pgoff_t endpage; | |
| int error = 0; | |
| /* | |
| * If this load would split a page, release the cached page, if any, | |
| * and perform a traditional read. | |
| */ | |
| startpage = idx_pos >> PAGE_SHIFT; | |
| endpage = (idx_pos + si->array->obj_size - 1) >> PAGE_SHIFT; | |
| if (startpage != endpage) { | |
| error = xfarray_sort_put_page(si); | |
| if (error) | |
| return error; | |
| if (xfarray_sort_terminated(si, &error)) | |
| return error; | |
| return xfile_obj_load(si->array->xfile, ptr, | |
| si->array->obj_size, idx_pos); | |
| } | |
| /* If the cached page is not the one we want, release it. */ | |
| if (xfile_page_cached(&si->xfpage) && | |
| xfile_page_index(&si->xfpage) != startpage) { | |
| error = xfarray_sort_put_page(si); | |
| if (error) | |
| return error; | |
| } | |
| /* | |
| * If we don't have a cached page (and we know the load is contained | |
| * in a single page) then grab it. | |
| */ | |
| if (!xfile_page_cached(&si->xfpage)) { | |
| if (xfarray_sort_terminated(si, &error)) | |
| return error; | |
| error = xfarray_sort_get_page(si, startpage << PAGE_SHIFT, | |
| PAGE_SIZE); | |
| if (error) | |
| return error; | |
| } | |
| memcpy(ptr, si->page_kaddr + offset_in_page(idx_pos), | |
| si->array->obj_size); | |
| return 0; | |
| } | |
| /* | |
| * Sort the array elements via quicksort. This implementation incorporates | |
| * four optimizations discussed in Sedgewick: | |
| * | |
| * 1. Use an explicit stack of array indices to store the next array partition | |
| * to sort. This helps us to avoid recursion in the call stack, which is | |
| * particularly expensive in the kernel. | |
| * | |
| * 2. For arrays with records in arbitrary or user-controlled order, choose the | |
| * pivot element using a median-of-nine decision tree. This reduces the | |
| * probability of selecting a bad pivot value which causes worst case | |
| * behavior (i.e. partition sizes of 1). | |
| * | |
| * 3. The smaller of the two sub-partitions is pushed onto the stack to start | |
| * the next level of recursion, and the larger sub-partition replaces the | |
| * current stack frame. This guarantees that we won't need more than | |
| * log2(nr) stack space. | |
| * | |
| * 4. For small sets, load the records into the scratchpad and run heapsort on | |
| * them because that is very fast. In the author's experience, this yields | |
| * a ~10% reduction in runtime. | |
| * | |
| * If a small set is contained entirely within a single xfile memory page, | |
| * map the page directly and run heap sort directly on the xfile page | |
| * instead of using the load/store interface. This halves the runtime. | |
| * | |
| * 5. This optimization is specific to the implementation. When converging lo | |
| * and hi after selecting a pivot, we will try to retain the xfile memory | |
| * page between load calls, which reduces run time by 50%. | |
| */ | |
| /* | |
| * Due to the use of signed indices, we can only support up to 2^63 records. | |
| * Files can only grow to 2^63 bytes, so this is not much of a limitation. | |
| */ | |
| #define QSORT_MAX_RECS (1ULL << 63) | |
| int | |
| xfarray_sort( | |
| struct xfarray *array, | |
| xfarray_cmp_fn cmp_fn, | |
| unsigned int flags) | |
| { | |
| struct xfarray_sortinfo *si; | |
| xfarray_idx_t *si_lo, *si_hi; | |
| void *pivot; | |
| void *scratch = xfarray_scratch(array); | |
| xfarray_idx_t lo, hi; | |
| int error = 0; | |
| if (array->nr < 2) | |
| return 0; | |
| if (array->nr >= QSORT_MAX_RECS) | |
| return -E2BIG; | |
| error = xfarray_sortinfo_alloc(array, cmp_fn, flags, &si); | |
| if (error) | |
| return error; | |
| si_lo = xfarray_sortinfo_lo(si); | |
| si_hi = xfarray_sortinfo_hi(si); | |
| pivot = xfarray_sortinfo_pivot(si); | |
| while (si->stack_depth >= 0) { | |
| lo = si_lo[si->stack_depth]; | |
| hi = si_hi[si->stack_depth]; | |
| trace_xfarray_qsort(si, lo, hi); | |
| /* Nothing left in this partition to sort; pop stack. */ | |
| if (lo >= hi) { | |
| si->stack_depth--; | |
| continue; | |
| } | |
| /* | |
| * If directly mapping the page and sorting can solve our | |
| * problems, we're done. | |
| */ | |
| if (xfarray_want_pagesort(si, lo, hi)) { | |
| error = xfarray_pagesort(si, lo, hi); | |
| if (error) | |
| goto out_free; | |
| si->stack_depth--; | |
| continue; | |
| } | |
| /* If insertion sort can solve our problems, we're done. */ | |
| if (xfarray_want_isort(si, lo, hi)) { | |
| error = xfarray_isort(si, lo, hi); | |
| if (error) | |
| goto out_free; | |
| si->stack_depth--; | |
| continue; | |
| } | |
| /* Pick a pivot, move it to a[lo] and stash it. */ | |
| error = xfarray_qsort_pivot(si, lo, hi); | |
| if (error) | |
| goto out_free; | |
| /* | |
| * Rearrange a[lo..hi] such that everything smaller than the | |
| * pivot is on the left side of the range and everything larger | |
| * than the pivot is on the right side of the range. | |
| */ | |
| while (lo < hi) { | |
| /* | |
| * Decrement hi until it finds an a[hi] less than the | |
| * pivot value. | |
| */ | |
| error = xfarray_sort_load_cached(si, hi, scratch); | |
| if (error) | |
| goto out_free; | |
| while (xfarray_sort_cmp(si, scratch, pivot) >= 0 && | |
| lo < hi) { | |
| hi--; | |
| error = xfarray_sort_load_cached(si, hi, | |
| scratch); | |
| if (error) | |
| goto out_free; | |
| } | |
| error = xfarray_sort_put_page(si); | |
| if (error) | |
| goto out_free; | |
| if (xfarray_sort_terminated(si, &error)) | |
| goto out_free; | |
| /* Copy that item (a[hi]) to a[lo]. */ | |
| if (lo < hi) { | |
| error = xfarray_sort_store(si, lo++, scratch); | |
| if (error) | |
| goto out_free; | |
| } | |
| /* | |
| * Increment lo until it finds an a[lo] greater than | |
| * the pivot value. | |
| */ | |
| error = xfarray_sort_load_cached(si, lo, scratch); | |
| if (error) | |
| goto out_free; | |
| while (xfarray_sort_cmp(si, scratch, pivot) <= 0 && | |
| lo < hi) { | |
| lo++; | |
| error = xfarray_sort_load_cached(si, lo, | |
| scratch); | |
| if (error) | |
| goto out_free; | |
| } | |
| error = xfarray_sort_put_page(si); | |
| if (error) | |
| goto out_free; | |
| if (xfarray_sort_terminated(si, &error)) | |
| goto out_free; | |
| /* Copy that item (a[lo]) to a[hi]. */ | |
| if (lo < hi) { | |
| error = xfarray_sort_store(si, hi--, scratch); | |
| if (error) | |
| goto out_free; | |
| } | |
| if (xfarray_sort_terminated(si, &error)) | |
| goto out_free; | |
| } | |
| /* | |
| * Put our pivot value in the correct place at a[lo]. All | |
| * values between a[beg[i]] and a[lo - 1] should be less than | |
| * the pivot; and all values between a[lo + 1] and a[end[i]-1] | |
| * should be greater than the pivot. | |
| */ | |
| error = xfarray_sort_store(si, lo, pivot); | |
| if (error) | |
| goto out_free; | |
| /* Set up the stack frame to process the two partitions. */ | |
| error = xfarray_qsort_push(si, si_lo, si_hi, lo, hi); | |
| if (error) | |
| goto out_free; | |
| if (xfarray_sort_terminated(si, &error)) | |
| goto out_free; | |
| } | |
| out_free: | |
| trace_xfarray_sort_stats(si, error); | |
| kvfree(si); | |
| return error; | |
| } |