| // 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; |
| } |