Skip to content
Open
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
13 changes: 11 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ on:

jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
include:
- os: ubuntu-latest
artifact: libzimalloc.so
- os: windows-latest
artifact: zimalloc.*
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -24,6 +32,7 @@ jobs:
version: master

- name: Check formatting
if: runner.os == 'Linux'
run: zig fmt --check .

- name: Run tests
Expand All @@ -41,7 +50,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: libzimalloc
path: zig-out-*/lib/libzimalloc.so
path: zig-out-*/lib/${{ matrix.artifact }}

mimalloc-bench-smoke-test:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
});
tests.root_module.addImport("build_options", zimalloc_options);
if (target.result.os.tag == .windows) {
tests.linkSystemLibrary("onecore.lib");
}

const tests_run = b.addRunArtifact(tests);

Expand Down
12 changes: 11 additions & 1 deletion src/Page.zig
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,17 @@ pub fn deinit(self: *Page) !void {
segment.init_set.unset(page_index);

const page_bytes = segment.pageSlice(page_index);
try std.os.madvise(page_bytes.ptr, page_bytes.len, std.os.MADV.DONTNEED);
switch (@import("builtin").os.tag) {
.linux => try std.posix.madvise(page_bytes.ptr, page_bytes.len, std.posix.MADV.DONTNEED),
// TODO: test this windows impl, presumably it's broken
.windows => _ = try std.os.windows.VirtualAlloc(
page_bytes.ptr,
page_bytes.len,
std.os.windows.MEM_RESET,
std.os.windows.PAGE_NOACCESS,
),
else => |tag| @compileError(@tagName(tag) ++ " is not supported yet"),
}
}

pub fn getPtrInFreeSlot(self: *const Page) *align(constants.min_slot_alignment) anyopaque {
Expand Down
71 changes: 6 additions & 65 deletions src/huge_alignment.zig
Original file line number Diff line number Diff line change
@@ -1,66 +1,7 @@
/// The `size` is rounded up to a multiple of `std.mem.page_size`.
/// Can be freed with std.os.unmap
pub fn allocateOptions(
size: usize,
alignment: usize,
prot: u32,
flags: std.os.MAP,
) ?[]align(std.mem.page_size) u8 {
assert.withMessage(@src(), alignment > std.mem.page_size, "alignment is not greater than the page size");
assert.withMessage(@src(), std.mem.isValidAlign(alignment), "alignment is not a power of two");
const builtin = @import("builtin");

const mmap_length = size + alignment - 1;
const unaligned = std.os.mmap(null, mmap_length, prot, flags, -1, 0) catch return null;
const unaligned_address = @intFromPtr(unaligned.ptr);
const aligned_address = std.mem.alignForward(usize, unaligned_address, alignment);

const aligned_size = std.mem.alignForward(usize, size, std.mem.page_size);

if (aligned_address == unaligned_address) {
std.os.munmap(@alignCast(unaligned[aligned_size..]));
return unaligned[0..aligned_size];
} else {
const offset = aligned_address - unaligned_address;
assert.withMessage(@src(), std.mem.isAligned(offset, std.mem.page_size), "offset is not aligned");

std.os.munmap(unaligned[0..offset]);
std.os.munmap(@alignCast(unaligned[offset + aligned_size ..]));
return @alignCast(unaligned[offset..][0..aligned_size]);
}
}

/// Makes a readable, writeable, anonymous private mapping with size rounded up to
/// a multiple of `std.mem.page_size`. Should be freed with `deallocate()`.
pub fn allocate(size: usize, alignment: usize) ?[]align(std.mem.page_size) u8 {
return allocateOptions(
size,
alignment,
std.os.PROT.READ | std.os.PROT.WRITE,
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
);
}

/// Rounds `buf.len` up to a multiple of `std.mem.page_size`.
pub fn deallocate(buf: []align(std.mem.page_size) const u8) void {
const aligned_len = std.mem.alignForward(usize, buf.len, std.mem.page_size);
std.os.munmap(buf.ptr[0..aligned_len]);
}

pub fn resizeAllocation(buf: []align(std.mem.page_size) u8, new_len: usize) bool {
const old_aligned_len = std.mem.alignForward(usize, buf.len, std.mem.page_size);
const new_aligned_len = std.mem.alignForward(usize, new_len, std.mem.page_size);

if (new_aligned_len == old_aligned_len) {
return true;
} else if (new_aligned_len < old_aligned_len) {
const trailing_ptr: [*]align(std.mem.page_size) u8 = @alignCast(buf.ptr + new_aligned_len);
std.os.munmap(trailing_ptr[0 .. old_aligned_len - new_aligned_len]);
return true;
} else {
return false;
}
}

const std = @import("std");

const assert = @import("assert.zig");
pub usingnamespace switch (builtin.os.tag) {
.linux => @import("huge_alignment/linux.zig"),
.windows => @import("huge_alignment/windows.zig"),
else => |tag| @compileError(@tagName(tag) ++ "is not supported yet"),
};
66 changes: 66 additions & 0 deletions src/huge_alignment/linux.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/// The `size` is rounded up to a multiple of `std.mem.page_size`.
/// Can be freed with std.os.unmap
fn allocateOptions(
size: usize,
alignment: usize,
prot: u32,
flags: std.posix.MAP,
) ?[]align(std.mem.page_size) u8 {
assert.withMessage(@src(), alignment > std.mem.page_size, "alignment is not greater than the page size");
assert.withMessage(@src(), std.mem.isValidAlign(alignment), "alignment is not a power of two");

const mmap_length = size + alignment - 1;
const unaligned = std.posix.mmap(null, mmap_length, prot, flags, -1, 0) catch return null;
const unaligned_address = @intFromPtr(unaligned.ptr);
const aligned_address = std.mem.alignForward(usize, unaligned_address, alignment);

const aligned_size = std.mem.alignForward(usize, size, std.mem.page_size);

if (aligned_address == unaligned_address) {
std.posix.munmap(@alignCast(unaligned[aligned_size..]));
return unaligned[0..aligned_size];
} else {
const offset = aligned_address - unaligned_address;
assert.withMessage(@src(), std.mem.isAligned(offset, std.mem.page_size), "offset is not aligned");

std.posix.munmap(unaligned[0..offset]);
std.posix.munmap(@alignCast(unaligned[offset + aligned_size ..]));
return @alignCast(unaligned[offset..][0..aligned_size]);
}
}

/// Makes a readable, writeable, anonymous private mapping with size rounded up to
/// a multiple of `std.mem.page_size`. Should be freed with `deallocate()`.
pub fn allocate(size: usize, alignment: usize) ?[]align(std.mem.page_size) u8 {
return allocateOptions(
size,
alignment,
std.posix.PROT.READ | std.posix.PROT.WRITE,
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
);
}

/// Rounds `buf.len` up to a multiple of `std.mem.page_size`.
pub fn deallocate(buf: []align(std.mem.page_size) const u8) void {
const aligned_len = std.mem.alignForward(usize, buf.len, std.mem.page_size);
std.posix.munmap(buf.ptr[0..aligned_len]);
}

pub fn resizeAllocation(buf: []align(std.mem.page_size) u8, new_len: usize) bool {
const old_aligned_len = std.mem.alignForward(usize, buf.len, std.mem.page_size);
const new_aligned_len = std.mem.alignForward(usize, new_len, std.mem.page_size);

if (new_aligned_len == old_aligned_len) {
return true;
} else if (new_aligned_len < old_aligned_len) {
const trailing_ptr: [*]align(std.mem.page_size) u8 = @alignCast(buf.ptr + new_aligned_len);
std.posix.munmap(trailing_ptr[0 .. old_aligned_len - new_aligned_len]);
return true;
} else {
return false;
}
}

const std = @import("std");

const assert = @import("../assert.zig");
142 changes: 142 additions & 0 deletions src/huge_alignment/windows.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/// Makes a readable, writeable, anonymous private mapping with size rounded up to
/// a multiple of `std.mem.page_size`. Should be freed with `deallocate()`.
pub fn allocate(size: usize, alignment: usize) ?[]align(std.mem.page_size) u8 {
return allocateOptions(
size,
alignment,
windows.PAGE_READWRITE,
windows.MEM_COMMIT | windows.MEM_RESERVE,
);
}

/// Rounds `buf.len` up to a multiple of `std.mem.page_size`.
pub fn deallocate(buf: []align(std.mem.page_size) u8) void {
windows.VirtualFree(@ptrCast(buf.ptr), 0, windows.MEM_RELEASE);
}

pub fn resizeAllocation(buf: []align(std.mem.page_size) u8, new_len: usize) bool {
const old_aligned_len = std.mem.alignForward(usize, buf.len, std.mem.page_size);
const new_aligned_len = std.mem.alignForward(usize, new_len, std.mem.page_size);

if (new_aligned_len == old_aligned_len) {
return true;
} else if (new_aligned_len < old_aligned_len) {
const trailing_ptr: [*]align(std.mem.page_size) u8 = @alignCast(buf.ptr + new_aligned_len);
windows.VirtualFree(trailing_ptr, old_aligned_len - new_aligned_len, windows.MEM_DECOMMIT);
return true;
} else {
return false;
}
}

const kernel32 = struct {
extern "kernel32" fn VirtualAlloc2(Process: ?HANDLE, BaseAddress: ?PVOID, Size: SIZE_T, AllocationType: ULONG, PageProtection: ULONG, ExtendedParameters: [*]MEM_EXTENDED_PARAMETER, ParameterCount: ULONG) callconv(WINAPI) ?PVOID;
};

pub const VirtualAlloc2Error = error{Unexpected};

fn VirtualAlloc2(
process: ?HANDLE,
base_address: ?PVOID,
size: SIZE_T,
allocation_type: ULONG,
prot: ULONG,
extended_parameters: []MEM_EXTENDED_PARAMETER,
) VirtualAlloc2Error!PVOID {
return kernel32.VirtualAlloc2(
process,
base_address,
size,
allocation_type,
prot,
extended_parameters.ptr,
@intCast(extended_parameters.len),
) orelse {
switch (windows.kernel32.GetLastError()) {
// TODO: handle errors that can happen
else => |err| return windows.unexpectedError(err),
}
};
}

fn allocateOptions(
size: usize,
alignment: usize,
prot: ULONG,
alloc_type: ULONG,
) ?[]align(std.mem.page_size) u8 {
assert.withMessage(@src(), alignment > std.mem.page_size, "alignment is not greater than the page size");
assert.withMessage(@src(), std.mem.isValidAlign(alignment), "alignment is not a power of two");

var mem_address_requirements = _MEM_ADDRESS_REQUIREMENTS{
.lowest_address = null,
.highest_address = null,
.alignment = alignment,
};

var extended_parameters = [_]MEM_EXTENDED_PARAMETER{
.{
.dummy = .{ .Type = .address_requirements },
.param = .{ .Pointer = &mem_address_requirements },
},
};

const aligned_size = std.mem.alignForward(usize, size, std.mem.page_size);
const ptr = VirtualAlloc2(null, null, aligned_size, alloc_type, prot, &extended_parameters) catch
return null;
return @alignCast(@as([*]u8, @ptrCast(ptr))[0..aligned_size]);
}

// TODO: figure out how to deallocate - problem is that VirtualFree with MEM_RELEASE requires
// the originally returned base pointer which does not play well with forcing alignment greater
// than VirtualAlloc guarantees

const std = @import("std");

const windows = std.os.windows;

const DWORD64 = windows.DWORD64;
const DWORD = windows.DWORD;
const SIZE_T = windows.SIZE_T;
const HANDLE = windows.HANDLE;
const PVOID = windows.PVOID;
const ULONG = windows.ULONG;
const WINAPI = windows.WINAPI;

const MEM_EXTENDED_PARAMETER_BITS = 8;
const MEM_EXTENDED_PARAMETER = extern struct {
dummy: packed struct(u64) {
Type: MEM_EXTENDED_PARAMETER_TYPE,
RESERVED: std.meta.Int(.unsigned, 64 - MEM_EXTENDED_PARAMETER_BITS) = 0,
},
param: extern union {
ULong64: DWORD64,
Pointer: PVOID,
Size: SIZE_T,
Handle: HANDLE,
ULong: DWORD,
},
};

const MEM_EXTENDED_PARAMETER_TYPE = enum(std.meta.Int(.unsigned, MEM_EXTENDED_PARAMETER_BITS)) {
invalid,
address_requirements,
numa_node,
partition_handle,
user_physical_handle,
attribute_flags,
image_machine,
max,
};

const _MEM_ADDRESS_REQUIREMENTS = extern struct {
lowest_address: ?PVOID,
highest_address: ?PVOID,
alignment: SIZE_T,
};

const assert = @import("../assert.zig");

comptime {
_ = std.testing.refAllDecls(@This());
}
4 changes: 2 additions & 2 deletions src/libzimalloc.zig
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ export fn posix_memalign(ptr: *?*anyopaque, alignment: usize, size: usize) c_int
}

if (@popCount(alignment) != 1 or alignment < @sizeOf(*anyopaque)) {
return @intFromEnum(std.os.E.INVAL);
return @intFromEnum(std.posix.E.INVAL);
}

if (allocateBytes(size, alignment, @returnAddress(), false, false, false)) |p| {
ptr.* = p;
return 0;
}

return @intFromEnum(std.os.E.NOMEM);
return @intFromEnum(std.posix.E.NOMEM);
}

export fn memalign(alignment: usize, size: usize) ?*anyopaque {
Expand Down