Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v2
- uses: goto-bus-stop/setup-zig@v2
- uses: mlugg/setup-zig@v1
with:
version: 0.12.0
version: 0.14.0
- run: zig build test
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
zig-cache/
.zig-cache/
3 changes: 2 additions & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.{
.name = "zig-stable-array",
.name = .stable_array,
.version = "0.1.0",
.fingerprint = 0xee760748bd6028de,
.dependencies = .{},
.paths = .{
"build.zig",
Expand Down
58 changes: 38 additions & 20 deletions stable_array.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ const builtin = @import("builtin");
const os = std.os;
const posix = std.posix;
const mem = std.mem;
const heap = std.heap;
const assert = std.debug.assert;

const AllocError = std.mem.Allocator.Error;

const darwin = struct {
extern "c" fn madvise(ptr: [*]align(mem.page_size) u8, length: usize, advice: c_int) c_int;
extern "c" fn madvise(ptr: [*]align(heap.page_size_min) u8, length: usize, advice: c_int) c_int;
};

pub fn StableArray(comptime T: type) type {
return StableArrayAligned(T, @alignOf(T));
}

pub fn StableArrayAligned(comptime T: type, comptime alignment: u29) type {
pub fn StableArrayAligned(comptime T: type, comptime _alignment: u29) type {
if (@sizeOf(T) == 0) {
@compileError("StableArray does not support types of size 0. Use ArrayList instead.");
}
Expand All @@ -27,13 +28,25 @@ pub fn StableArrayAligned(comptime T: type, comptime alignment: u29) type {
pub const VariableSlice = [*]align(alignment) T;

pub const k_sizeof: usize = if (alignment > @sizeOf(T)) alignment else @sizeOf(T);
pub const page_size: usize = heap.pageSize();
pub const alignment = _alignment;

items: Slice,
capacity: usize,
max_virtual_alloc_bytes: usize,

pub fn getPageSize(self: *Self) usize {
_ = self;
return Self.page_size;
}

pub fn getAlignment(self: *Self) usize {
_ = self;
return Self.alignment;
}

pub fn init(max_virtual_alloc_bytes: usize) Self {
assert(@mod(max_virtual_alloc_bytes, mem.page_size) == 0); // max_virtual_alloc_bytes must be a multiple of mem.page_size
assert(@mod(max_virtual_alloc_bytes, page_size) == 0); // max_virtual_alloc_bytes must be a multiple of page_size
return Self{
.items = &[_]T{},
.capacity = 0,
Expand Down Expand Up @@ -208,8 +221,8 @@ pub fn StableArrayAligned(comptime T: type, comptime alignment: u29) type {
} else {
const base_addr: usize = @intFromPtr(self.items.ptr);
const offset_addr: usize = base_addr + new_capacity_bytes;
const addr: [*]align(mem.page_size) u8 = @ptrFromInt(offset_addr);
if (comptime builtin.target.isDarwin()) {
const addr: [*]align(heap.page_size_min) u8 = @ptrFromInt(offset_addr);
if (comptime builtin.os.tag.isDarwin()) {
const MADV_DONTNEED = 4;
const err: c_int = darwin.madvise(addr, bytes_to_free, MADV_DONTNEED);
switch (@as(posix.E, @enumFromInt(err))) {
Expand Down Expand Up @@ -243,7 +256,7 @@ pub fn StableArrayAligned(comptime T: type, comptime alignment: u29) type {
const w = os.windows;
w.VirtualFree(@as(*anyopaque, @ptrCast(self.items.ptr)), 0, w.MEM_RELEASE);
} else {
var slice: []align(mem.page_size) const u8 = undefined;
var slice: []align(heap.page_size_min) const u8 = undefined;
slice.ptr = @alignCast(@as([*]u8, @ptrCast(self.items.ptr)));
slice.len = self.max_virtual_alloc_bytes;
posix.munmap(slice);
Expand Down Expand Up @@ -340,7 +353,7 @@ pub fn StableArrayAligned(comptime T: type, comptime alignment: u29) type {
}

fn calcBytesUsedForCapacity(capacity: usize) usize {
return mem.alignForward(usize, k_sizeof * capacity, mem.page_size);
return mem.alignForward(usize, k_sizeof * capacity, page_size);
}
};
}
Expand All @@ -355,56 +368,61 @@ test "init" {
a.deinit();

var b = StableArrayAligned(u8, 16).init(TEST_VIRTUAL_ALLOC_SIZE);
assert(b.getAlignment() == 16);
assert(b.items.len == 0);
assert(b.capacity == 0);
assert(b.max_virtual_alloc_bytes == TEST_VIRTUAL_ALLOC_SIZE);
b.deinit();

assert(a.getPageSize() == b.getPageSize());
}

test "append" {
var a = StableArray(u8).init(TEST_VIRTUAL_ALLOC_SIZE);
try a.appendSlice(&[_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
assert(a.calcTotalUsedBytes() == mem.page_size);
assert(a.calcTotalUsedBytes() == a.getPageSize());
for (a.items, 0..) |v, i| {
assert(v == i);
}
a.deinit();

var b = StableArrayAligned(u8, mem.page_size).init(TEST_VIRTUAL_ALLOC_SIZE);
var b = StableArrayAligned(u8, heap.pageSize()).init(TEST_VIRTUAL_ALLOC_SIZE);
try b.appendSlice(&[_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
assert(b.calcTotalUsedBytes() == mem.page_size * 10);
assert(b.calcTotalUsedBytes() == a.getPageSize() * 10);
for (b.items, 0..) |v, i| {
assert(v == i);
}
b.deinit();
}

test "shrinkAndFree" {
const page_size = heap.pageSize();

var a = StableArray(u8).init(TEST_VIRTUAL_ALLOC_SIZE);
try a.appendSlice(&[_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
a.shrinkAndFree(5);

assert(a.calcTotalUsedBytes() == mem.page_size);
assert(a.calcTotalUsedBytes() == page_size); // still using only a page
assert(a.items.len == 5);
for (a.items, 0..) |v, i| {
assert(v == i);
}
a.deinit();

var b = StableArrayAligned(u8, mem.page_size).init(TEST_VIRTUAL_ALLOC_SIZE);
var b = StableArrayAligned(u8, heap.pageSize()).init(TEST_VIRTUAL_ALLOC_SIZE);
try b.appendSlice(&[_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
b.shrinkAndFree(5);
assert(b.calcTotalUsedBytes() == mem.page_size * 5);
assert(b.calcTotalUsedBytes() == page_size * 5); // alignment of each item is 1 page
assert(b.items.len == 5);
for (b.items, 0..) |v, i| {
assert(v == i);
}
b.deinit();

var c = StableArrayAligned(u8, 2048).init(TEST_VIRTUAL_ALLOC_SIZE);
var c = StableArrayAligned(u8, page_size / 2).init(TEST_VIRTUAL_ALLOC_SIZE);
assert(c.getAlignment() == page_size / 2);
try c.appendSlice(&[_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
c.shrinkAndFree(5);
assert(c.calcTotalUsedBytes() == mem.page_size * 3);
assert(c.calcTotalUsedBytes() == page_size * 3);
assert(c.capacity == 6);
assert(c.items.len == 5);
for (c.items, 0..) |v, i| {
Expand All @@ -426,10 +444,10 @@ test "resize" {
}

test "out of memory" {
var a = StableArrayAligned(u8, mem.page_size).init(TEST_VIRTUAL_ALLOC_SIZE);
var a = StableArrayAligned(u8, heap.pageSize()).init(TEST_VIRTUAL_ALLOC_SIZE);
defer a.deinit();

const max_capacity: usize = TEST_VIRTUAL_ALLOC_SIZE / mem.page_size;
const max_capacity: usize = TEST_VIRTUAL_ALLOC_SIZE / a.getPageSize();
try a.appendNTimes(0xFF, max_capacity);
for (a.items) |v| {
assert(v == 0xFF);
Expand Down Expand Up @@ -464,8 +482,8 @@ test "growing retains values" {
var a = StableArray(u8).init(TEST_VIRTUAL_ALLOC_SIZE);
defer a.deinit();

try a.resize(mem.page_size);
try a.resize(a.getPageSize());
a.items[0] = 0xFF;
try a.resize(mem.page_size * 2);
try a.resize(a.getPageSize() * 2);
assert(a.items[0] == 0xFF);
}