-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new allocator interface #5064
new allocator interface #5064
Conversation
a81d7f6
to
f65aa38
Compare
e48a530
to
edf9ab7
Compare
If you can get this to work, I'm happy to redo my work for #4739 This change will make everything cleaner. |
edf9ab7
to
a25b6f5
Compare
@fengb yes I think I'll have it working soon. |
2b9cd79
to
0183e67
Compare
5e39f77
to
2d8b90f
Compare
a9a2831
to
c250aea
Compare
83a47ed
to
e1b7dfe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice work @marler8997. Thank you for your patience on what turned out to be a big project. I think this is very close to merge-ready.
@fengb and/or @kubkon would you mind providing a review on the changes to WasmPageAllocator?
} | ||
const full_len = init: { | ||
if (comptime supports_malloc_size) { | ||
const s = malloc_size(ptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you double check that this does not cause false positives with external tools such as valgrind? I'm concerned that valgrind would detect utilization of these extra bytes as writes to invalid memory addresses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TLDR; looks like valgrind shims malloc_usable_size
to return the exact size originally requested from malloc
.
Ok I verified valgrind handles this (at least on my linux box). Here's the program:
// zig build-exe leak.zig -lc
const std = @import("std");
pub fn main() !void {
const allocator = std.heap.c_allocator;
var i : usize = 1;
while (i <= 100) : (i += 1) {
const m = try allocator.callAllocFn(i, 1, 1);
defer _ = allocator.callResizeFn(m, 0, 0) catch unreachable;
std.debug.warn("alloc {} returned {}\n", .{i, m.len});
//@memset(m.ptr, 0xbb, m.len + 1); // use this to cause an error
@memset(m.ptr, 0xbb, m.len);
}
}
Here we use the C allocator to make allocations, and utilize the full size (will call malloc_usable_size
under the hood). When I run the binary by itself, I can see it usually returns more memory than I requested. However, when I run it with valgrind
, it always returns the exact size requested. This is telling me that valgrind is shimming malloc_usable_size
and causing it to always return the size originally requested in malloc
. Kinda interesting.
I also verified that writing 1 byte over the size will cause valgrind
to detect memory corruption (uncomment the bad @memset
call).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for testing this out! This makes me feel comfortable with using malloc_usable_size
.
@@ -2190,6 +2377,13 @@ test "alignForward" { | |||
testing.expect(alignForward(17, 8) == 24); | |||
} | |||
|
|||
pub fn alignBackwardAnyAlign(i: usize, alignment: usize) usize { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add doc comments to explain why this is needed rather than just using alignBackward
?
Looks like it removes the restriction " /// The alignment must be a power of 2 and greater than 0. " ?
Is this necessary at the callsites? Or would it be appropriate to add that restriction to the callsites as well, and have them call alignBackward
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is necessary because allocating arrays require passing the element size for len_align
which don't have to be powers of 2. When I was using alignBackwards
originally, there were cases where array element sizes were not a power of 2 (i.e. I saw 56
). I will add a doc comment explaining how the function differs from alignBackward
.
lib/std/testing.zig
Outdated
@@ -11,7 +11,7 @@ pub var allocator_instance = LeakCountAllocator.init(&base_allocator_instance.al | |||
pub const failing_allocator = &failing_allocator_instance.allocator; | |||
pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0); | |||
|
|||
pub var base_allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]); | |||
pub var base_allocator_instance = std.mem.sanityWrap(std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The doc comments for the allocator say
/// Detects and asserts if the std.mem.Allocator interface is violated
Which looks to me like it's for testing implementations of the Allocator interface, rather than testing the usage of the Allocator high level API functions. In this case I don't think it makes sense to put the wrapper here, since for testing purposes we are not trying to test if the LeakCountAllocator code is correct, we are trying to test if the user code that is using the Allocator provided by the LeakCountAllocator is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ValidationAllocator
validates both the usage of the allocator and the implementation.
i.e. for alloc
, ValidationAllocator
ensures len > 0
, ptr_align
is valid, len
is aligned by len_align
and len >= len_align
. Once it returns, it validates that the len
complies with the requested len_align
and that the pointer is aligned by ptr_align
.
Will update ddoc comment with:
/// Detects and asserts if the std.mem.Allocator interface is violated by the caller
/// or the allocator.
4c200cf
to
6e93c4e
Compare
const old_ptr = @ptrCast(*c_void, old_mem.ptr); | ||
const buf = c.realloc(old_ptr, new_size) orelse return old_mem[0..new_size]; | ||
return @ptrCast([*]u8, buf)[0..new_size]; | ||
fn cResize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this get moved to the std.c
module as e.g. std.c.allocator
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no. std.c is for libc functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it for things reliant on libc? (as this is)
const buf = c.realloc(old_ptr, new_size) orelse return error.OutOfMemory; | ||
return @ptrCast([*]u8, buf)[0..new_size]; | ||
fn cAlloc(self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 { | ||
assert(ptr_align <= @alignOf(c_longdouble)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use aligned_alloc
(C11+) or posix_memalign
(older POSIX interface)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
separate PR. we tried that before and it depends on libc versions. now that we have the ability to check, it's time to revisit, but that's out of scope of this changeset.
For the record, this also closed #4431. |
Related Issue: #4431 (Improve Allocator interface so client can avoid needless copying)
Replaces the current allocator interface with the following:
This new interface makes 2 big changes. The first is that it provides a way for an allocator to report the full capacity of any allocation. This allows clients to take advantage of the entire memory space of any allocation.
The second change with the new interface is that it allows the caller to have more control over resizing and when/how to relocate memory. The previous interface had a
reallocFn
which would attempt to resize an allocation and would move and copy the entire buffer to a larger space when needed. The new interface allows clients to determine when memory should be moved and what data to preserve when doing so. Therealloc
implementation is now common between all allocators using a combination of function calls from the new interface.