diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fb92f0..89d910d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -24,6 +32,7 @@ jobs: version: master - name: Check formatting + if: runner.os == 'Linux' run: zig fmt --check . - name: Run tests @@ -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 diff --git a/build.zig b/build.zig index b9bf165..58653ca 100644 --- a/build.zig +++ b/build.zig @@ -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); diff --git a/src/Page.zig b/src/Page.zig index ca7267d..d2275ae 100644 --- a/src/Page.zig +++ b/src/Page.zig @@ -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 { diff --git a/src/huge_alignment.zig b/src/huge_alignment.zig index a8fecfc..7bc195f 100644 --- a/src/huge_alignment.zig +++ b/src/huge_alignment.zig @@ -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"), +}; diff --git a/src/huge_alignment/linux.zig b/src/huge_alignment/linux.zig new file mode 100644 index 0000000..3c0ec33 --- /dev/null +++ b/src/huge_alignment/linux.zig @@ -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"); diff --git a/src/huge_alignment/windows.zig b/src/huge_alignment/windows.zig new file mode 100644 index 0000000..4874aec --- /dev/null +++ b/src/huge_alignment/windows.zig @@ -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()); +} diff --git a/src/libzimalloc.zig b/src/libzimalloc.zig index d109098..aa13965 100644 --- a/src/libzimalloc.zig +++ b/src/libzimalloc.zig @@ -75,7 +75,7 @@ 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| { @@ -83,7 +83,7 @@ export fn posix_memalign(ptr: *?*anyopaque, alignment: usize, size: usize) c_int return 0; } - return @intFromEnum(std.os.E.NOMEM); + return @intFromEnum(std.posix.E.NOMEM); } export fn memalign(alignment: usize, size: usize) ?*anyopaque {