diff --git a/build.zig b/build.zig index 3c4f56b4ed159167fbf052eeccfea210ecdfcc05..fcbf1a46dc6df7d5dc2aa958488b559f0e891516 100644 --- a/build.zig +++ b/build.zig @@ -183,7 +183,7 @@ pub fn build(b: *std.Build) !void { const tracy_callstack = b.option(bool, "tracy-callstack", "Include callstack information with Tracy data. Does nothing if -Dtracy is not provided") orelse (tracy != null); const tracy_allocation = b.option(bool, "tracy-allocation", "Include allocation information with Tracy data. Does nothing if -Dtracy is not provided") orelse (tracy != null); const tracy_callstack_depth: u32 = b.option(u32, "tracy-callstack-depth", "Declare callstack depth for Tracy data. Does nothing if -Dtracy_callstack is not provided") orelse 10; - const debug_gpa = b.option(bool, "debug-allocator", "Force the compiler to use DebugAllocator") orelse false; + const debug_gpa = b.option(bool, "debug-allocator", "Force the compiler to use SafeAllocator") orelse false; const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse (enable_llvm or only_c); const sanitize_thread = b.option(bool, "sanitize-thread", "Enable thread-sanitization") orelse false; const strip = b.option(bool, "strip", "Omit debug information"); diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 6d8b5cca706e4c59a58a4197a5b7b11ab7a5e068..439e46bb6e82bf93bf53652cc506c129781da740 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -26,9 +26,9 @@ pub const std_options: std.Options = .{ pub fn main(init: process.Init.Minimal) !void { // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not // always the case. So, we do need a true gpa for some things. - var debug_gpa_state: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_gpa_state.deinit(); - const gpa = debug_gpa_state.allocator(); + var safe_gpa_state: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + defer _ = safe_gpa_state.deinit(); + const gpa = safe_gpa_state.allocator(); var threaded: std.Io.Threaded = .init(gpa, .{ .environ = init.environ, diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 1359924a9e5003e0eb34538ad09a9c9fb421b37d..69b078a4bcd7a9c1277901d52b47a354fad51437 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -91,8 +91,8 @@ fn mainServer(init: std.process.Init.Minimal) !void { return std.process.exit(0); }, .query_test_metadata => { - testing.allocator_instance = .{}; - defer if (testing.allocator_instance.deinit() == .leak) { + testing.allocator_instance = .init(std.heap.page_allocator, .{}); + defer if (testing.allocator_instance.deinit() != 0) { @panic("internal test runner memory leak"); }; @@ -123,7 +123,10 @@ fn mainServer(init: std.process.Init.Minimal) !void { .run_test => { testing.environ = init.environ; - testing.allocator_instance = .{}; + testing.allocator_instance = .init(std.heap.page_allocator, .{ + .canary = 0xc3a701ba, + .check_write_after_free = true, + }); testing.io_instance = .init(testing.allocator, .{ .argv0 = .init(init.args), .environ = init.environ, @@ -150,8 +153,7 @@ fn mainServer(init: std.process.Init.Minimal) !void { }, }; testing.io_instance.deinit(); - const leak_count = testing.allocator_instance.detectLeaks(); - testing.allocator_instance.deinitWithoutLeakChecks(); + const leak_count = testing.allocator_instance.deinit(); try server.serveTestResults(.{ .index = index, .flags = .{ @@ -173,8 +175,8 @@ fn mainServer(init: std.process.Init.Minimal) !void { // since they are not present. if (!builtin.fuzz) unreachable; - var gpa_instance: std.heap.DebugAllocator(.{}) = .init; - defer if (gpa_instance.deinit() == .leak) { + var gpa_instance: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + defer if (gpa_instance.deinit() != 0) { @panic("internal test runner memory leak"); }; const gpa = gpa_instance.allocator(); @@ -271,14 +273,17 @@ fn mainTerminal(init: std.process.Init.Minimal) void { var leaks: usize = 0; for (test_fn_list, 0..) |test_fn, i| { - testing.allocator_instance = .{}; + testing.allocator_instance = .init(std.heap.page_allocator, .{ + .canary = 0xc3a701ba, + .check_write_after_free = true, + }); testing.io_instance = .init(testing.allocator, .{ .argv0 = .init(init.args), .environ = init.environ, }); defer { testing.io_instance.deinit(); - if (testing.allocator_instance.deinit() == .leak) leaks += 1; + if (testing.allocator_instance.deinit() != 0) leaks += 1; } testing.log_level = .warn; testing.environ = init.environ; @@ -430,8 +435,11 @@ var fuzz_runner: if (builtin.fuzz) struct { error.WriteFailed => panic("failed to write to stdout: {t}", .{stdout_writer.err.?}), }; - testing.allocator_instance = .{}; - defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1); + testing.allocator_instance = .init(std.heap.page_allocator, .{ + .canary = 0xc3a701ba, + .check_write_after_free = true, + }); + defer if (testing.allocator_instance.deinit() != 0) std.process.exit(1); is_fuzz_test = false; builtin.test_functions[fuzz_runner.indexes[i]].func() catch |err| switch (err) { @@ -554,8 +562,11 @@ pub fn fuzz( fn test_one() callconv(.c) bool { @disableInstrumentation(); - testing.allocator_instance = .{}; - defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1); + testing.allocator_instance = .init(std.heap.page_allocator, .{ + .canary = 0xcacce5e0, + .check_write_after_free = true, + }); + defer if (testing.allocator_instance.deinit() != 0) std.process.exit(1); log_err_count = 0; testOne(ctx, @constCast(&testing.Smith{ .in = null })) catch |err| switch (err) { error.SkipZigTest => return true, @@ -582,7 +593,6 @@ pub fn fuzz( if (builtin.fuzz) { // Preserve the calling test's allocator state const prev_allocator_state = testing.allocator_instance; - testing.allocator_instance = .{}; defer testing.allocator_instance = prev_allocator_state; global.ctx = context; diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index edfde79dd14cb27e0621210e3836a93e5bf736ae..37189a2b635f0a8c8774ee3d88d670e01bc37b90 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -39,10 +39,10 @@ fn logOverride( fw.interface.flush() catch panic("failed to write to fuzzer log: {t}", .{fw.err.?}); } -var debug_allocator: std.heap.DebugAllocator(.{}) = .init; +var safe_allocator: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); const gpa = switch (builtin.mode) { - .Debug => debug_allocator.allocator(), - .ReleaseFast, .ReleaseSmall, .ReleaseSafe => std.heap.smp_allocator, + .Debug, .ReleaseSafe => safe_allocator.allocator(), + .ReleaseFast, .ReleaseSmall => std.heap.smp_allocator, }; // Seperate from `exec` to allow initialization before `exec` is. diff --git a/lib/std/heap/ArenaAllocator.zig b/lib/std/heap/ArenaAllocator.zig index ad6c937c2346a1b6c4a3b97408d1a29fdf83c56f..bca6bd2fc081e0839a2f5d4ed8f7e645bc59fb34 100644 --- a/lib/std/heap/ArenaAllocator.zig +++ b/lib/std/heap/ArenaAllocator.zig @@ -671,12 +671,10 @@ test "reset while retaining a buffer" { // Create two internal buffers _ = try a.alloc(u8, 1); - _ = try a.alloc(u8, 1000); - try std.testing.expect(arena_allocator.state.used_list != null); - - // Check that we have at least two buffers - try std.testing.expect(arena_allocator.state.used_list.?.next != null); + while (arena_allocator.state.used_list.?.next == null) { + _ = try a.alloc(u8, 1000); + } // This retains the first allocated buffer try std.testing.expect(arena_allocator.reset(.{ .retain_with_limit = 2 })); diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 7aea379efb5bb45e20de370e9f8433bd04d58a41..2226f25ce6e1de85f5962a4660850de64f3863ce 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -201,9 +201,12 @@ test "Allocator.resize" { defer testing.allocator.free(values); for (values, 0..) |*v, i| v.* = @as(T, @intCast(i)); - if (!testing.allocator.resize(values, values.len + 10)) return error.OutOfMemory; - values = values.ptr[0 .. values.len + 10]; - try testing.expect(values.len == 110); + if (testing.allocator.resize(values, values.len + 10)) { + values = values.ptr[0 .. values.len + 10]; + try testing.expect(values.len == 110); + } else { + // `resize` is not guaranteed to succeed even if there is sufficient memory. + } } const primitiveFloatTypes = .{ @@ -217,9 +220,12 @@ test "Allocator.resize" { defer testing.allocator.free(values); for (values, 0..) |*v, i| v.* = @as(T, @floatFromInt(i)); - if (!testing.allocator.resize(values, values.len + 10)) return error.OutOfMemory; - values = values.ptr[0 .. values.len + 10]; - try testing.expect(values.len == 110); + if (testing.allocator.resize(values, values.len + 10)) { + values = values.ptr[0 .. values.len + 10]; + try testing.expect(values.len == 110); + } else { + // `resize` is not guaranteed to succeed even if there is sufficient memory. + } } } diff --git a/lib/std/start.zig b/lib/std/start.zig index 2537da9800df607c5b871ec6b3b39c0d3c4a9666..9491ef2045ce81f2d46966979c58cdc04833e8ea 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -710,12 +710,11 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { /// General error message for a malformed return type const bad_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', or '!u8'"; -const use_debug_allocator = !is_wasm and switch (builtin.mode) { - .Debug => true, - .ReleaseSafe => !builtin.link_libc, // Not ideal, but the best we have for now. +const use_safe_allocator = !is_wasm and switch (builtin.mode) { + .Debug, .ReleaseSafe => true, .ReleaseFast, .ReleaseSmall => !builtin.link_libc and builtin.single_threaded, // Also not ideal. }; -var debug_allocator: std.heap.DebugAllocator(.{}) = .init; +var safe_allocator: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.Block) u8 { const fn_info = @typeInfo(@TypeOf(root.main)).@"fn"; @@ -725,8 +724,8 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B .environ = .{ .block = environ }, })); - const gpa = if (use_debug_allocator) - debug_allocator.allocator() + const gpa = if (use_safe_allocator) + safe_allocator.allocator() else if (builtin.link_libc) std.heap.c_allocator else if (is_wasm) @@ -736,8 +735,8 @@ inline fn callMain(args: std.process.Args.Vector, environ: std.process.Environ.B else comptime unreachable; - defer if (use_debug_allocator) { - _ = debug_allocator.deinit(); // Leaks do not affect return code. + defer if (use_safe_allocator) { + _ = safe_allocator.deinit(); // Leaks do not affect return code. }; const arena_backing_allocator = if (is_wasm) gpa else std.heap.page_allocator; diff --git a/lib/std/testing.zig b/lib/std/testing.zig index d262ca0c43a52c5364f405cc623b380c252d3915..e8a214fa2362fc37e7f25ea8ab69ec65e24a1354 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -17,19 +17,11 @@ var failing_allocator_instance = FailingAllocator.init(base_allocator_instance.a }); var base_allocator_instance = std.heap.FixedBufferAllocator.init(""); -/// This should only be used in temporary test programs. -pub const allocator = allocator_instance.allocator(); -pub var allocator_instance: std.heap.DebugAllocator(.{ - .stack_trace_frames = if (std.debug.sys_can_stack_trace) 10 else 0, - .resize_stack_traces = true, - // A unique value so that when a default-constructed - // DebugAllocator is incorrectly passed to testing allocator, or - // vice versa, panic occurs. - .canary = @truncate(0x2731e675c3a701ba), -}) = b: { - if (!builtin.is_test) @compileError("testing allocator used when not testing"); - break :b .init; -}; +pub var allocator_instance: std.heap.SafeAllocator = undefined; +pub const allocator = if (builtin.is_test) + allocator_instance.allocator() +else + @compileError("not testing"); pub var io_instance: Io.Threaded = undefined; pub const io = if (builtin.is_test) io_instance.io() else @compileError("not testing"); diff --git a/src/main.zig b/src/main.zig index ccf6ce25d50a7cec688d54cd6c7dd7cf086c7b46..16df63526f3654bacfdfbf116d193ec7a0edd2b2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -157,34 +157,29 @@ pub fn log( std.log.defaultLog(level, scope, format, args); } -const use_debug_allocator = build_options.debug_gpa or +const use_safe_allocator = build_options.debug_gpa or (native_os != .wasi and !builtin.link_libc and switch (builtin.mode) { .Debug, .ReleaseSafe => true, .ReleaseFast, .ReleaseSmall => false, }); -const RootAllocator = if (use_debug_allocator) std.heap.DebugAllocator(.{ +// TODO: The `align(@alignOf(std.heap.SafeAllocator))` can be removed the next time zig1.wasm is updated +var safe_allocator: std.heap.SafeAllocator align(@alignOf(std.heap.SafeAllocator)) = .init(std.heap.page_allocator, .{ .stack_trace_frames = build_options.mem_leak_frames, - .thread_safe = switch (build_options.io_mode) { - .threaded => true, - .evented => false, - }, -}) else struct { - pub const init: RootAllocator = .{}; - pub fn allocator(_: RootAllocator) Allocator { - if (native_os == .wasi) return std.heap.wasm_allocator; - if (builtin.link_libc) return std.heap.c_allocator; - return std.heap.smp_allocator; - } - pub fn deinit(_: RootAllocator) std.heap.Check { - return .ok; - } -}; +}); pub fn main(init: std.process.Init.Minimal) anyerror!void { - var root_allocator: RootAllocator = .init; - defer _ = root_allocator.deinit(); - const root_gpa = root_allocator.allocator(); + const root_gpa = if (use_safe_allocator) + safe_allocator.allocator() + else if (native_os == .wasi) + std.heap.wasm_allocator + else if (builtin.link_libc) + std.heap.c_allocator + else + std.heap.smp_allocator; + defer if (use_safe_allocator) { + _ = safe_allocator.deinit(); + }; var io_impl: IoImpl = undefined; switch (build_options.io_mode) { .threaded => io_impl = .init(root_gpa, .{ @@ -197,7 +192,7 @@ pub fn main(init: std.process.Init.Minimal) anyerror!void { .argv0 = .init(init.args), .environ = init.environ, - .backing_allocator_needs_mutex = use_debug_allocator, + .backing_allocator_needs_mutex = false, }), } defer io_impl.deinit();