diff --git a/src/util/meson.build b/src/util/meson.build index c9a0e3d28bd..3f2f4b7b540 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -99,6 +99,8 @@ files_mesa_util = files( 'ralloc.h', 'rand_xor.c', 'rand_xor.h', + 'range_minimum_query.c', + 'range_minimum_query.h', 'rb_tree.c', 'rb_tree.h', 'register_allocate.c', @@ -400,6 +402,7 @@ if with_tests 'tests/mesa-sha1_test.cpp', 'tests/os_mman_test.cpp', 'tests/perf/u_trace_test.cpp', + 'tests/range_minimum_query_test.cpp', 'tests/rb_tree_test.cpp', 'tests/register_allocate_test.cpp', 'tests/roundeven_test.cpp', diff --git a/src/util/range_minimum_query.c b/src/util/range_minimum_query.c new file mode 100644 index 00000000000..0be55fc70f4 --- /dev/null +++ b/src/util/range_minimum_query.c @@ -0,0 +1,72 @@ +/* + * Copyright 2025 Valve Corporation + * SPDX-License-Identifier: MIT + */ + +#include "range_minimum_query.h" + +#include "bitscan.h" +#include "macros.h" +#include "ralloc.h" +#include "u_math.h" + +void +range_minimum_query_table_resize(struct range_minimum_query_table *table, + void *mem_ctx, uint32_t width) +{ + const uint64_t height = util_logbase2_64(width) + 1; + const uint64_t size = width * height; + assert(size < UINT32_MAX); + + table->table = reralloc_array_size(mem_ctx, table->table, + sizeof(uint32_t), size); + table->width = width; + table->height = height; +} + +static void +elementwise_minimum(uint32_t *restrict out, + uint32_t *const a, + uint32_t *const b, + uint32_t count) +{ + for (uint32_t i = 0; i < count; i++) { + out[i] = MIN2(a[i], b[i]); + } +} + +static uint32_t +rmq_distance(int32_t level) +{ + return 1 << level; +} + +void +range_minimum_query_table_preprocess(struct range_minimum_query_table *table) +{ + for (uint32_t i = 1; i < table->height; i++) { + uint32_t in_distance = rmq_distance(i - 1); + uint32_t *in_row = table->table + table->width * (i - 1); + uint32_t *out_row = table->table + table->width * i; + elementwise_minimum(out_row, in_row, in_row + in_distance, + table->width - in_distance); + } +} + +uint32_t +range_minimum_query(struct range_minimum_query_table *const table, + uint32_t left_idx, uint32_t right_idx) +{ + assert(left_idx < right_idx); + const uint32_t distance = right_idx - left_idx; + + uint32_t level = util_logbase2(distance); + assert(rmq_distance(level) <= distance); + assert(distance < 2 * rmq_distance(level)); + assert(level < table->height); + + uint32_t *const row = table->table + table->width * level; + uint32_t left = row[left_idx]; + uint32_t right = row[right_idx - rmq_distance(level)]; + return MIN2(left, right); +} diff --git a/src/util/range_minimum_query.h b/src/util/range_minimum_query.h new file mode 100644 index 00000000000..fc9d9ee343a --- /dev/null +++ b/src/util/range_minimum_query.h @@ -0,0 +1,65 @@ +/* + * Copyright 2025 Valve Corporation + * SPDX-License-Identifier: MIT + */ + +#ifndef RANGE_MINIMUM_QUERY_H +#define RANGE_MINIMUM_QUERY_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Find the smallest integer in a portion of an array. + * + * We use the simple RMQ algorithm that uses O(n log n) preprcessing time and + * O(1) query time (see eg. Bender and Colton section 3). + * + * Bender, M.A., Farach-Colton, M. (2000). The LCA Problem Revisited. In: + * Gonnet, G.H., Viola, A. (eds) LATIN 2000: Theoretical Informatics. LATIN + * 2000. Lecture Notes in Computer Science, vol 1776. Springer, Berlin, + * Heidelberg. https://doi.org/10.1007/10719839_9 + */ + +struct range_minimum_query_table { + uint32_t *table; + uint32_t width, height; +}; + +static inline void +range_minimum_query_table_init(struct range_minimum_query_table *table) +{ + memset((void*) table, 0, sizeof(*table)); +} + +void +range_minimum_query_table_resize(struct range_minimum_query_table *table, + void *mem_ctx, uint32_t width); + +/** + * Perform preprocessing on the table to ready it for queries. + * + * Takes O(n log n) time. + */ +void +range_minimum_query_table_preprocess(struct range_minimum_query_table *table); + +/** + * Find the smallest value in the array among indices in the half-open interval + * [left_idx, right_idx). + * + * Takes O(1) time. + */ +uint32_t +range_minimum_query(struct range_minimum_query_table *const table, + uint32_t left_idx, uint32_t right_idx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/util/tests/range_minimum_query_test.cpp b/src/util/tests/range_minimum_query_test.cpp new file mode 100644 index 00000000000..de71def5469 --- /dev/null +++ b/src/util/tests/range_minimum_query_test.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Valve Corporation + * SPDX-License-Identifier: MIT + */ + +#include + +#include + +#include "macros.h" +#include "ralloc.h" +#include "range_minimum_query.h" + +static uint32_t +rmq_naive(struct range_minimum_query_table *const table, + uint32_t left_idx, uint32_t right_idx) +{ + uint32_t result = UINT32_MAX; + for (uint32_t i = left_idx; i < right_idx; i++) { + result = MIN2(result, table->table[i]); + } + return result; +} + +TEST(range_minimum_query_test, range_minimum_query_test) +{ + void* context = ralloc_context(nullptr); + + std::mt19937 gen(1337); + + struct range_minimum_query_table table; + range_minimum_query_table_init(&table); + + for (uint32_t width = 0; width < 256; width++) { + range_minimum_query_table_resize(&table, context, width); + + std::uniform_int_distribution<> distrib(0, 100); + for (uint32_t i = 0; i < width; i++) { + table.table[i] = distrib(gen); + // printf("%i\n", table.table[i]); + } + + range_minimum_query_table_preprocess(&table); + for (uint32_t i = 0; i < width; i++) { + for (uint32_t j = i + 1; j < width; j++) { + // printf("%i, %i\n", i, j); + EXPECT_EQ(range_minimum_query(&table, i, j), + rmq_naive(&table, i, j)); + } + } + } + + ralloc_free(context); +}