From bcf6901b8d5acf72c3017f9552892f7bd5a15325 Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Thu, 19 Oct 2023 21:58:03 +0100 Subject: Initial Add a wrapper allocator that tracks currently used and max memory --- .gitignore | 1 + build.zig | 47 +++++++++++++++++++++++++++++ build.zig.zon | 4 +++ src/main.zig | 6 ++++ src/max_allocator.zig | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/main.zig create mode 100644 src/max_allocator.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3cef7be --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +zig-cache/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..bba13ba --- /dev/null +++ b/build.zig @@ -0,0 +1,47 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + const lib = b.addStaticLibrary(.{ + .name = "mf-zigtools", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + // This declares intent for the library to be installed into the standard + // location when the user invokes the "install" step (the default step when + // running `zig build`). + b.installArtifact(lib); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const main_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_main_tests = b.addRunArtifact(main_tests); + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build test` + // This will evaluate the `test` step rather than the default, which is "install". + const test_step = b.step("test", "Run library tests"); + test_step.dependOn(&run_main_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..042c070 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,4 @@ +.{ + .name = "mf-zigtools", + .version = "0.0.1", +} \ No newline at end of file diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..d5d8c78 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,6 @@ +const ma = @import("max_allocator.zig"); +pub const MaxAllocator = ma.MaxAllocator; +pub const maxAllocator = ma.maxAllocator; +test { + _ = ma; +} diff --git a/src/max_allocator.zig b/src/max_allocator.zig new file mode 100644 index 0000000..00b8630 --- /dev/null +++ b/src/max_allocator.zig @@ -0,0 +1,83 @@ +const std = @import("std"); + +// Wrapper allocator that counts what the largest amount allocated at any time was from backing_allocator. +pub const MaxAllocator = struct { + backing_allocator: std.mem.Allocator, + currently_allocated: usize = 0, + max_allocated: usize = 0, + + pub fn allocator(self: *@This()) std.mem.Allocator { + return std.mem.Allocator{ + .ptr = self, + .vtable = &.{ + .alloc = rawAlloc, + .resize = rawResize, + .free = rawFree, + }, + }; + } + + fn rawAlloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 { + var self: *@This() = @alignCast(@ptrCast(ctx)); + var res = self.backing_allocator.rawAlloc(len, ptr_align, ret_addr); + if (res != null) { + self.currently_allocated += len; + std.log.warn("+{}", .{len}); + self.max_allocated = @max(self.currently_allocated, self.max_allocated); + } + return res; + } + + fn rawResize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool { + var self: *@This() = @alignCast(@ptrCast(ctx)); + const res = self.backing_allocator.rawResize(buf, buf_align, new_len, ret_addr); + if (res) { + self.currently_allocated -= buf.len; + std.log.warn("-{}", .{buf.len}); + self.currently_allocated += new_len; + std.log.warn("+{}", .{new_len}); + self.max_allocated = @max(self.currently_allocated, self.max_allocated); + } + return res; + } + + fn rawFree(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void { + var self: *@This() = @alignCast(@ptrCast(ctx)); + self.backing_allocator.rawFree(buf, buf_align, ret_addr); + self.currently_allocated -= buf.len; + std.log.warn("-{}", .{buf.len}); + } +}; + +pub fn maxAllocator(backing_allocator: std.mem.Allocator) MaxAllocator { + return MaxAllocator{ .backing_allocator = backing_allocator }; +} + +test { + const buflen = 2048; + var buf = [_]u8{0} ** buflen; + var fba = std.heap.FixedBufferAllocator.init(&buf); + var ma = maxAllocator(fba.allocator()); + var a = ma.allocator(); + var x = try a.alloc(u8, 100); + try std.testing.expectEqual(@as(usize, 100), ma.max_allocated); + try std.testing.expectEqual(@as(usize, 100), ma.currently_allocated); + var y = try a.alloc(u8, 10); + try std.testing.expectEqual(@as(usize, 110), ma.max_allocated); + try std.testing.expectEqual(@as(usize, 110), ma.currently_allocated); + a.free(x); + var z = try a.alloc(u8, 50); + try std.testing.expectEqual(@as(usize, 110), ma.max_allocated); + try std.testing.expectEqual(@as(usize, 60), ma.currently_allocated); + + try std.testing.expect(a.resize(z, 1024)); + try std.testing.expectEqual(@as(usize, 1034), ma.max_allocated); + try std.testing.expectEqual(@as(usize, 1034), ma.currently_allocated); + + a.free(y); + a.free(z); + + try std.testing.expectEqual(@as(usize, 1034), ma.max_allocated); + try std.testing.expectEqual(@as(usize, 0), ma.currently_allocated); // works on aarch64, not on x86_64 :thinking-face: + try std.testing.expectEqual(@as(usize, 0), fba.end_index); +} -- cgit v1.2.3-ZIG