authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2026-06-09 22:37:23+03:30
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-06-11 05:40:50+02:00
log57ca3512e429f962edc15ea2b24a57f9c778320d
tree2ac7674c3bda934ba2c1261780000e197a38e75c
parent340cf85faadbac0c51e072f588b70bd77f7c6c7b

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.

1 files changed, 2 insertions(+), 3 deletions(-)

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