authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-02 15:57:41+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-02 15:57:41+02:00
loge9df86aed0b5e52c7fa5ebe51e60602d5a31c351
tree0692d8b30de61e20a3ffc5a805174d7607cb409c
parent43d84420f3bca06be86397049c3b940358131f75
parentd78f096c499093f89c3b8f9ced1beb204c22ebec

Merge pull request 'std.heap.ArenaAllocator: decrease fuzz test workload per run' (#31596) from justusk/zig:fuzz-arena-2 into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31596 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

2 files changed, 139 insertions(+), 146 deletions(-)

lib/std/debug/Info.zig+4
...@@ -99,6 +99,10 @@ pub fn resolveAddresses(...@@ -99,6 +99,10 @@ pub fn resolveAddresses(
99 // due to split debug information. For now, we'll just resolve the addreses one by one.99 // due to split debug information. For now, we'll just resolve the addreses one by one.
100 for (sorted_pc_addrs, output) |pc_addr, *src_loc| {100 for (sorted_pc_addrs, output) |pc_addr, *src_loc| {
101 const dwarf, const dwarf_pc_addr = mf.getDwarfForAddress(gpa, io, pc_addr) catch |err| switch (err) {101 const dwarf, const dwarf_pc_addr = mf.getDwarfForAddress(gpa, io, pc_addr) catch |err| switch (err) {
102 error.MissingDebugInfo => {
103 src_loc.* = .invalid;
104 continue;
105 },
102 error.InvalidMachO, error.InvalidDwarf => return error.InvalidDebugInfo,106 error.InvalidMachO, error.InvalidDwarf => return error.InvalidDebugInfo,
103 else => |e| return e,107 else => |e| return e,
104 };108 };
lib/std/heap/ArenaAllocator.zig+135-146
...@@ -682,45 +682,40 @@ test "reset while retaining a buffer" {...@@ -682,45 +682,40 @@ test "reset while retaining a buffer" {
682 try std.testing.expectEqual(2, arena_allocator.queryCapacity());682 try std.testing.expectEqual(2, arena_allocator.queryCapacity());
683}683}
684684
685test "fuzz" {685test "fuzz multi threaded" {
686 @disableInstrumentation();686 @disableInstrumentation();
687 if (@import("builtin").single_threaded) return error.SkipZigTest;687 if (@import("builtin").single_threaded) return error.SkipZigTest;
688688
689 const gpa = std.heap.smp_allocator;689 const gpa = std.heap.smp_allocator;
690690
691 var io_instance: std.Io.Threaded = .init(gpa, .{});
692 defer io_instance.deinit();
693
691 var arena_state: ArenaAllocator.State = .init;694 var arena_state: ArenaAllocator.State = .init;
692 // No need to deinit arena_state, all allocations are in `sample_buffer`!695 // No need to deinit arena_state, all allocations are in `sample_buffer`!
693696
694 const control_buffer = try gpa.alloc(u8, 64 << 10 << 10);697 const buffer_size = FuzzContext.max_alloc_count * FuzzContext.max_alloc_size;
698
699 const control_buffer = try gpa.alloc(u8, buffer_size);
695 defer gpa.free(control_buffer);700 defer gpa.free(control_buffer);
696 var control_instance: std.heap.FixedBufferAllocator = .init(control_buffer);701 var control_instance: std.heap.FixedBufferAllocator = .init(control_buffer);
697702
698 const sample_buffer = try gpa.alloc(u8, 64 << 10 << 10);703 const sample_buffer = try gpa.alloc(u8, buffer_size);
699 defer gpa.free(sample_buffer);704 defer gpa.free(sample_buffer);
700 var sample_instance: FuzzAllocator = .init(sample_buffer);705 var sample_instance: FuzzAllocator = .init(sample_buffer);
701706
702 var allocs: FuzzContext.Allocs = try .initCapacity(gpa, FuzzContext.max_alloc_count);
703 defer allocs.deinit(gpa);
704
705 try std.testing.fuzz(FuzzContext.Init{707 try std.testing.fuzz(FuzzContext.Init{
706 .gpa = gpa,708 .threaded_instance = &io_instance,
707 .allocs = &allocs,
708 .arena_state = &arena_state,709 .arena_state = &arena_state,
709 .control_instance = &control_instance,710 .control_instance = &control_instance,
710 .sample_instance = &sample_instance,711 .sample_instance = &sample_instance,
711 }, fuzzArenaAllocator, .{});712 }, fuzzMultiThreaded, .{});
712}713}
713714
714fn fuzzArenaAllocator(fuzz_init: FuzzContext.Init, smith: *std.testing.Smith) anyerror!void {715fn fuzzMultiThreaded(fuzz_init: FuzzContext.Init, smith: *std.testing.Smith) anyerror!void {
715 @disableInstrumentation();716 @disableInstrumentation();
716 const testing = std.testing;717 const testing = std.testing;
717718 const io = fuzz_init.threaded_instance.io();
718 // We use a 'fresh' `Threaded` instance every time to reset threadlocals to
719 // their default values.
720
721 var io_instance: std.Io.Threaded = .init(fuzz_init.gpa, .{});
722 defer io_instance.deinit();
723 const io = io_instance.io();
724719
725 fuzz_init.sample_instance.prepareFailures(smith);720 fuzz_init.sample_instance.prepareFailures(smith);
726721
...@@ -731,59 +726,57 @@ fn fuzzArenaAllocator(fuzz_init: FuzzContext.Init, smith: *std.testing.Smith) an...@@ -731,59 +726,57 @@ fn fuzzArenaAllocator(fuzz_init: FuzzContext.Init, smith: *std.testing.Smith) an
731 defer fuzz_init.arena_state.* = arena_instance.state;726 defer fuzz_init.arena_state.* = arena_instance.state;
732727
733 var ctx: FuzzContext = .init(728 var ctx: FuzzContext = .init(
734 io,
735 control_allocator,729 control_allocator,
736 arena_instance.allocator(),730 arena_instance.allocator(),
737 fuzz_init.allocs,
738 );731 );
739 defer ctx.deinit();732 defer ctx.deinit();
740733
741 ctx.rwl.lockUncancelable(io);
742
743 var group: std.Io.Group = .init;734 var group: std.Io.Group = .init;
744 defer group.cancel(io);735 defer group.cancel(io);
745736
737 var n_allocs: usize = 0;
746 var n_actions: usize = 0;738 var n_actions: usize = 0;
747 while (!smith.eosWeightedSimple(99, 1) and n_actions < FuzzContext.max_action_count) {739 while (!smith.eosWeightedSimple(99, 1) and n_actions < FuzzContext.max_action_count) {
748 errdefer comptime unreachable;740 errdefer comptime unreachable;
749741
750 const ActionTag = @typeInfo(FuzzContext.Action).@"union".tag_type.?;742 const weights: []const testing.Smith.Weight = if (n_allocs == FuzzContext.max_alloc_count)
751 const weights: []const testing.Smith.Weight = weights: {743 &.{
752 if (ctx.allocs.len == ctx.allocs.capacity)744 .value(FuzzContext.Action, .resize, 1),
753 break :weights &.{745 .value(FuzzContext.Action, .remap, 1),
754 .value(ActionTag, .resize, 1),746 .value(FuzzContext.Action, .free, 1),
755 .value(ActionTag, .remap, 1),747 }
756 .value(ActionTag, .free, 1),748 else
757 };749 &.{
758 break :weights testing.Smith.baselineWeights(ActionTag) ++750 .value(FuzzContext.Action, .resize, 1),
759 .{testing.Smith.Weight.value(ActionTag, .alloc, 2)};751 .value(FuzzContext.Action, .remap, 1),
760 };752 .value(FuzzContext.Action, .free, 1),
761 const action: FuzzContext.Action = switch (smith.valueWeighted(ActionTag, weights)) {753 .value(FuzzContext.Action, .alloc, 3),
762 .alloc => action: {754 };
763 const alloc_index = ctx.allocs.addOneBounded() catch continue;755 switch (smith.valueWeighted(FuzzContext.Action, weights)) {
764 ctx.allocs.items(.len)[alloc_index] = .free;756 .alloc => {
765 break :action .{ .alloc = .{757 const alloc_index = n_allocs;
766 .len = nextLen(smith),758 n_allocs += 1;
767 .alignment = smith.valueRangeAtMost(759 ctx.allocs[alloc_index].common.len = .free;
760 group.concurrent(io, FuzzContext.doOneAlloc, .{
761 &ctx,
762 nextLen(smith),
763 smith.valueRangeAtMost(
768 Alignment,764 Alignment,
769 .@"1",765 .@"1",
770 .fromByteUnits(2 * std.heap.page_size_max),766 .fromByteUnits(2 * std.heap.page_size_max),
771 ),767 ),
772 .index = alloc_index,768 @enumFromInt(alloc_index),
773 } };769 }) catch unreachable;
774 },770 },
775 .resize => .{ .resize = .{ .new_len = nextLen(smith) } },771 .resize => group.concurrent(io, FuzzContext.doOneResize, .{ &ctx, nextLen(smith) }) catch unreachable,
776 .remap => .{ .remap = .{ .new_len = nextLen(smith) } },772 .remap => group.concurrent(io, FuzzContext.doOneRemap, .{ &ctx, nextLen(smith) }) catch unreachable,
777 .free => .free,773 .free => group.concurrent(io, FuzzContext.doOneFree, .{&ctx}) catch unreachable,
778 };774 }
779 group.concurrent(io, FuzzContext.doOneAction, .{ &ctx, action }) catch break;
780 n_actions += 1;775 n_actions += 1;
781 }776 }
782777
783 ctx.rwl.unlock(io);
784
785 try group.await(io);778 try group.await(io);
786 try ctx.check();779 try ctx.check(n_allocs);
787780
788 // This also covers the `deinit` logic since `free_all` uses it internally.781 // This also covers the `deinit` logic since `free_all` uses it internally.
789782
...@@ -806,73 +799,71 @@ fn fuzzArenaAllocator(fuzz_init: FuzzContext.Init, smith: *std.testing.Smith) an...@@ -806,73 +799,71 @@ fn fuzzArenaAllocator(fuzz_init: FuzzContext.Init, smith: *std.testing.Smith) an
806 }799 }
807800
808 fuzz_init.control_instance.reset();801 fuzz_init.control_instance.reset();
809 fuzz_init.allocs.clearRetainingCapacity();
810}
811fn nextLen(smith: *std.testing.Smith) usize {
812 @disableInstrumentation();
813 return usizeRange(smith, 1, 16 << 10 << 10);
814}802}
815fn usizeRange(smith: *std.testing.Smith, at_least: usize, at_most: usize) usize {803fn nextLen(smith: *std.testing.Smith) @typeInfo(FuzzContext.Alloc.Len).@"enum".tag_type {
816 @disableInstrumentation();804 @disableInstrumentation();
817 const Int = @Int(.unsigned, @min(64, @bitSizeOf(usize)));805 const BackingInt = @typeInfo(FuzzContext.Alloc.Len).@"enum".tag_type;
818 return smith.valueRangeAtMost(Int, @intCast(at_least), @intCast(at_most));806 return smith.valueRangeAtMost(BackingInt, 1, FuzzContext.max_alloc_size);
819}807}
820808
821const FuzzContext = struct {809const FuzzContext = struct {
822 io: std.Io,
823 rwl: std.Io.RwLock,
824
825 control_allocator: Allocator,810 control_allocator: Allocator,
826 sample_allocator: Allocator,811 sample_allocator: Allocator,
827812
828 allocs: *Allocs,813 last_alloc_index: Alloc.Index,
814 allocs: [max_alloc_count]Alloc,
829815
830 const max_alloc_count = 4096;816 const max_alloc_count = 64;
831 const max_action_count = 2 * max_alloc_count;817 const max_action_count = 2 * max_alloc_count;
832818
833 const Allocs = std.MultiArrayList(struct {819 const max_alloc_size = 16 << 10;
820
821 const Alloc = struct {
834 control_ptr: [*]u8,822 control_ptr: [*]u8,
835 sample_ptr: [*]u8,823 sample_ptr: [*]u8,
836 len: Len,824 common: packed struct(usize) {
837 alignment: Alignment,825 len: Len,
838 });826 alignment: Alignment,
827 _: @Int(.unsigned, padding_bits) = 0,
828 },
829
830 const Len = enum(@Int(.unsigned, len_bits)) {
831 free = (1 << len_bits) - 1,
832 _,
833 };
834 const len_bits = @min(64, @bitSizeOf(usize)) - @bitSizeOf(Alignment);
835 const padding_bits = @bitSizeOf(usize) - (len_bits + @bitSizeOf(Alignment));
839836
840 const Len = enum(usize) {837 const Index = enum(usize) {
841 free = std.math.maxInt(usize),838 none = std.math.maxInt(usize),
842 _,839 _,
840 };
843 };841 };
844842
845 const Action = union(enum(u8)) {843 const Action = enum {
846 alloc: struct { len: usize, alignment: Alignment, index: usize },844 alloc,
847 resize: struct { new_len: usize },845 resize,
848 remap: struct { new_len: usize },846 remap,
849 free,847 free,
850 };848 };
851849
852 threadlocal var tls_next: u8 = 0;
853 threadlocal var tls_last_index: ?usize = null;
854
855 const Init = struct {850 const Init = struct {
856 gpa: Allocator,851 threaded_instance: *std.Io.Threaded,
857 allocs: *FuzzContext.Allocs,
858 arena_state: *ArenaAllocator.State,852 arena_state: *ArenaAllocator.State,
859 control_instance: *std.heap.FixedBufferAllocator,853 control_instance: *std.heap.FixedBufferAllocator,
860 sample_instance: *FuzzAllocator,854 sample_instance: *FuzzAllocator,
861 };855 };
862856
863 fn init(857 fn init(
864 io: std.Io,
865 control_allocator: Allocator,858 control_allocator: Allocator,
866 sample_allocator: Allocator,859 sample_allocator: Allocator,
867 allocs: *Allocs,
868 ) FuzzContext {860 ) FuzzContext {
869 @disableInstrumentation();861 @disableInstrumentation();
870 return .{862 return .{
871 .io = io,
872 .rwl = .init,
873 .control_allocator = control_allocator,863 .control_allocator = control_allocator,
874 .sample_allocator = sample_allocator,864 .sample_allocator = sample_allocator,
875 .allocs = allocs,865 .last_alloc_index = .none,
866 .allocs = undefined,
876 };867 };
877 }868 }
878869
...@@ -881,35 +872,22 @@ const FuzzContext = struct {...@@ -881,35 +872,22 @@ const FuzzContext = struct {
881 ctx.* = undefined;872 ctx.* = undefined;
882 }873 }
883874
884 fn check(ctx: *const FuzzContext) !void {875 fn check(ctx: *const FuzzContext, n_allocs: usize) !void {
885 @disableInstrumentation();876 @disableInstrumentation();
886 for (0..ctx.allocs.len) |index| {877 for (ctx.allocs[0..n_allocs]) |allocation| {
887 const len: usize = switch (ctx.allocs.items(.len)[index]) {878 const len: usize = switch (allocation.common.len) {
888 .free => continue,879 .free => continue,
889 _ => |len| @intFromEnum(len),880 _ => |len| @intFromEnum(len),
890 };881 };
891 const control = ctx.allocs.items(.control_ptr)[index][0..len];882 const control = allocation.control_ptr[0..len];
892 const sample = ctx.allocs.items(.sample_ptr)[index][0..len];883 const sample = allocation.sample_ptr[0..len];
893 try std.testing.expectEqualSlices(u8, control, sample);884 try std.testing.expectEqualSlices(u8, control, sample);
894 }885 }
895 }886 }
896887
897 fn doOneAction(ctx: *FuzzContext, action: Action) std.Io.Cancelable!void {888 fn doOneAlloc(ctx: *FuzzContext, len: usize, alignment: Alignment, index: Alloc.Index) void {
898 @disableInstrumentation();
899 ctx.rwl.lockSharedUncancelable(ctx.io);
900 defer ctx.rwl.unlockShared(ctx.io);
901
902 switch (action) {
903 .alloc => |act| ctx.doOneAlloc(act.len, act.alignment, act.index),
904 .resize => |act| ctx.doOneResize(act.new_len),
905 .remap => |act| ctx.doOneRemap(act.new_len),
906 .free => ctx.doOneFree(),
907 }
908 }
909
910 fn doOneAlloc(ctx: *FuzzContext, len: usize, alignment: Alignment, index: usize) void {
911 @disableInstrumentation();889 @disableInstrumentation();
912 assert(ctx.allocs.items(.len)[index] == .free);890 assert(ctx.allocs[@intFromEnum(index)].common.len == .free);
913891
914 const control_ptr = ctx.control_allocator.rawAlloc(len, alignment, @returnAddress()) orelse892 const control_ptr = ctx.control_allocator.rawAlloc(len, alignment, @returnAddress()) orelse
915 return;893 return;
...@@ -918,38 +896,42 @@ const FuzzContext = struct {...@@ -918,38 +896,42 @@ const FuzzContext = struct {
918 return;896 return;
919 };897 };
920898
921 ctx.allocs.set(index, .{899 ctx.allocs[@intFromEnum(index)] = .{
922 .control_ptr = control_ptr,900 .control_ptr = control_ptr,
923 .sample_ptr = sample_ptr,901 .sample_ptr = sample_ptr,
924 .len = @enumFromInt(len),902 .common = .{
925 .alignment = alignment,903 .len = @enumFromInt(len),
926 });904 .alignment = alignment,
927905 },
928 for (control_ptr[0..len], sample_ptr[0..len]) |*control, *sample| {906 };
929 control.* = tls_next;907
930 sample.* = tls_next;908 for (control_ptr[0..len], sample_ptr[0..len], 0..) |*control, *sample, i| {
931 tls_next +%= 1;909 control.* = @truncate(i);
910 sample.* = @truncate(i);
932 }911 }
933912
934 tls_last_index = index;913 @atomicStore(Alloc.Index, &ctx.last_alloc_index, index, .release);
935 }914 }
936 fn doOneResize(ctx: *FuzzContext, new_len: usize) void {915 fn doOneResize(ctx: *FuzzContext, new_len: usize) void {
937 @disableInstrumentation();916 @disableInstrumentation();
938 const index = tls_last_index orelse return;
939 const len = ctx.allocs.items(.len)[index];
940 assert(len != .free);
941 const memory = ctx.allocs.items(.sample_ptr)[index][0..@intFromEnum(len)];
942 const alignment = ctx.allocs.items(.alignment)[index];
943917
944 assert(alignment.check(@intFromPtr(ctx.allocs.items(.control_ptr)[index])));918 const index = @atomicRmw(Alloc.Index, &ctx.last_alloc_index, .Xchg, .none, .acquire);
945 assert(alignment.check(@intFromPtr(ctx.allocs.items(.sample_ptr)[index])));919 if (index == .none) return;
920
921 const allocation = &ctx.allocs[@intFromEnum(index)];
922 assert(allocation.common.len != .free);
923 const memory = allocation.sample_ptr[0..@intFromEnum(allocation.common.len)];
924 const alignment = allocation.common.alignment;
925
926 assert(alignment.check(@intFromPtr(allocation.control_ptr)));
927 assert(alignment.check(@intFromPtr(allocation.sample_ptr)));
946928
947 // Since `resize` is fallible, we have to ensure that `control_allocator`929 // Since `resize` is fallible, we have to ensure that `control_allocator`
948 // is always successful by reserving the memory we need beforehand.930 // is always successful by reserving the memory we need beforehand.
949 const new_control_ptr = ctx.control_allocator.rawAlloc(new_len, alignment, @returnAddress()) orelse931 const new_control_ptr = ctx.control_allocator.rawAlloc(new_len, alignment, @returnAddress()) orelse
950 return;932 return;
951 if (ctx.sample_allocator.rawResize(memory, alignment, new_len, @returnAddress())) {933 if (ctx.sample_allocator.rawResize(memory, alignment, new_len, @returnAddress())) {
952 const old_control = ctx.allocs.items(.control_ptr)[index][0..memory.len];934 const old_control = allocation.control_ptr[0..memory.len];
953 const overlap = @min(memory.len, new_len);935 const overlap = @min(memory.len, new_len);
954 @memcpy(new_control_ptr[0..overlap], old_control[0..overlap]);936 @memcpy(new_control_ptr[0..overlap], old_control[0..overlap]);
955 ctx.control_allocator.rawFree(old_control, alignment, @returnAddress());937 ctx.control_allocator.rawFree(old_control, alignment, @returnAddress());
...@@ -958,23 +940,27 @@ const FuzzContext = struct {...@@ -958,23 +940,27 @@ const FuzzContext = struct {
958 return;940 return;
959 }941 }
960942
961 ctx.allocs.set(index, .{943 ctx.allocs[@intFromEnum(index)] = .{
962 .control_ptr = new_control_ptr,944 .control_ptr = new_control_ptr,
963 .sample_ptr = memory.ptr,945 .sample_ptr = memory.ptr,
964 .len = @enumFromInt(new_len),946 .common = .{
965 .alignment = alignment,947 .len = @enumFromInt(new_len),
966 });948 .alignment = alignment,
949 },
950 };
967951
968 if (new_len > memory.len) {952 if (new_len > memory.len) {
969 for (953 for (
970 ctx.allocs.items(.control_ptr)[index][memory.len..new_len],954 allocation.control_ptr[memory.len..new_len],
971 ctx.allocs.items(.sample_ptr)[index][memory.len..new_len],955 allocation.sample_ptr[memory.len..new_len],
972 ) |*control, *sample| {956 0..,
973 control.* = tls_next;957 ) |*control, *sample, i| {
974 sample.* = tls_next;958 control.* = @truncate(i);
975 tls_next +%= 1;959 sample.* = @truncate(i);
976 }960 }
977 }961 }
962
963 @atomicStore(Alloc.Index, &ctx.last_alloc_index, index, .release);
978 }964 }
979 fn doOneRemap(ctx: *FuzzContext, new_len: usize) void {965 fn doOneRemap(ctx: *FuzzContext, new_len: usize) void {
980 @disableInstrumentation();966 @disableInstrumentation();
...@@ -982,26 +968,29 @@ const FuzzContext = struct {...@@ -982,26 +968,29 @@ const FuzzContext = struct {
982 }968 }
983 fn doOneFree(ctx: *FuzzContext) void {969 fn doOneFree(ctx: *FuzzContext) void {
984 @disableInstrumentation();970 @disableInstrumentation();
985 const index = tls_last_index orelse return;
986 const len = ctx.allocs.items(.len)[index];
987 assert(len != .free);
988 const memory = ctx.allocs.items(.sample_ptr)[index][0..@intFromEnum(len)];
989 const alignment = ctx.allocs.items(.alignment)[index];
990971
991 assert(alignment.check(@intFromPtr(ctx.allocs.items(.control_ptr)[index])));972 const index = @atomicRmw(Alloc.Index, &ctx.last_alloc_index, .Xchg, .none, .acquire);
992 assert(alignment.check(@intFromPtr(ctx.allocs.items(.sample_ptr)[index])));973 if (index == .none) return;
993974
994 ctx.control_allocator.rawFree(ctx.allocs.items(.control_ptr)[index][0..memory.len], alignment, @returnAddress());975 const allocation = &ctx.allocs[@intFromEnum(index)];
995 ctx.sample_allocator.rawFree(ctx.allocs.items(.sample_ptr)[index][0..memory.len], alignment, @returnAddress());976 assert(allocation.common.len != .free);
977 const len: usize = @intFromEnum(allocation.common.len);
978 const alignment = allocation.common.alignment;
996979
997 ctx.allocs.set(index, .{980 assert(alignment.check(@intFromPtr(allocation.control_ptr)));
981 assert(alignment.check(@intFromPtr(allocation.sample_ptr)));
982
983 ctx.control_allocator.rawFree(allocation.control_ptr[0..len], alignment, @returnAddress());
984 ctx.sample_allocator.rawFree(allocation.sample_ptr[0..len], alignment, @returnAddress());
985
986 ctx.allocs[@intFromEnum(index)] = .{
998 .control_ptr = undefined,987 .control_ptr = undefined,
999 .sample_ptr = undefined,988 .sample_ptr = undefined,
1000 .len = .free,989 .common = .{
1001 .alignment = undefined,990 .len = .free,
1002 });991 .alignment = .@"1",
1003992 },
1004 tls_last_index = null;993 };
1005 }994 }
1006};995};
1007996