zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 57ca3512e429f962edc15ea2b24a57f9c778320d (tree)
parent 340cf85faadbac0c51e072f588b70bd77f7c6c7b
Author: Ali Cheraghi <alichraghi@proton.me>
Date:   Tue,  9 Jun 2026 22:37:23 +0330

std.sort.pdq: fix stack overflow on 32-bit targets

our pdq implementation uses an explicit stack of `Range` entries to avoid recursion.
The size of this stack was set to `log2(maxInt(usize) + 1)`. on 32-bit
targets like `wasm32-freestanding`, this would hit the maximum size,
causing OOB. there were two issues:

- `max_limit`, which controls how many imbalanced partitions are allowed
  before the algorithm falls back to heapsort, was computed as
  `floorPowerOfTwo(n) + 1` instead of `log2(n)` as in the original c++
  reference implementation. e.g. for `n = 1_000_000`, this allowed 524289
  bad partitions before heapsort would kick in, instead of 19.
  this meant deeply imbalanced partitions could accumulate stack pushes
  essentially without limit. However, i didn't observe any meaningful
  difference in benchmarks.

- the worst-case stack depth is bounded by `log2(n) + max_limit`,
  which approaches `2 * log2(n)`. The reference c++ and go implementation
  doesn't have this problem because they don't use explicit stack buffer.

Diffstat:
Mlib/std/sort/pdq.zig | 5++---
1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/lib/std/sort/pdq.zig b/lib/std/sort/pdq.zig @@ -44,12 +44,11 @@ pub fn pdqContext(a: usize, b: usize, context: anytype) void { // slices of up to this length get sorted using insertion sort. const max_insertion = 24; // number of allowed imbalanced partitions before switching to heap sort. - const max_limit = std.math.floorPowerOfTwo(usize, b - a) + 1; + const max_limit = if (b > a) math.log2_int(usize, b - a) else 0; // set upper bound on stack memory usage. const Range = struct { a: usize, b: usize, limit: usize, leftmost: bool }; - const stack_size = math.log2(math.maxInt(usize) + 1); - var stack: [stack_size]Range = undefined; + var stack: [2 * @bitSizeOf(usize)]Range = undefined; var range = Range{ .a = a, .b = b, .limit = max_limit, .leftmost = true }; var top: usize = 0;