-
Notifications
You must be signed in to change notification settings - Fork 203
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
Fixed malloc with concurrent executions of memory.grow #404
base: main
Are you sure you want to change the base?
Conversation
Interesting. dlmalloc's documentation for br = (char*)(CALL_MORECORE(asize));
end = (char*)(CALL_MORECORE(0)); It appears this is not the case. At an initial glance, this PR makes sense to me, though I'm not super familiar with dlmalloc internals. |
#else | ||
// In wasi-libc, this branch should never be taken as CALL_MORECORE(0) may | ||
// lead to incorrect allocations. See try_init_allocator() for more | ||
// information. |
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 inside if (MORECORE_CONTIGUOUS && ...
block.
if we are using MORECORE_CONTIGUOUS=1, it's the bug to fix i guess.
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.
In general I would say setting MORECORE_CONTIGUOUS
to 1 is correct. The comments in malloc.c
say this:
MORECORE_CONTIGUOUS default: 1 (true) if HAVE_MORECORE
If true, take advantage of fact that consecutive calls to MORECORE
with positive arguments always return contiguous increasing
addresses. This is true of unix sbrk. It does not hurt too much to
set it true anyway, since malloc copes with non-contiguities.
Setting it false when definitely non-contiguous saves time
and possibly wasted space it would take to discover this though.
So, in general I think we definitely want to benefit from the fact that the WebAssembly memory is contiguous and only fall back to the non-contiguous malloc if some other allocator uses memory.grow in the same program, as well.
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.
ok. it makes sense.
i vaguely concern that discontiguous fallback logic will never be tested well though.
@@ -4139,13 +4147,23 @@ static void* sys_alloc(mstate m, size_t nb) { | |||
ssize < nb + SYS_ALLOC_PADDING) { | |||
size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - ssize); | |||
if (esize < HALF_MAX_SIZE_T) { | |||
#ifdef __wasilibc_unmodified_upstream | |||
char* end = (char*)CALL_MORECORE(esize); | |||
if (end != CMFAIL) | |||
ssize += esize; |
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.
it seems like an upstream bug to assume contiguity.
does dlmalloc still have a maintained upstream?
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.
dmalloc upstream has not been updated since 2012.
The comments at the top of dlmalloc say that it should handle the case of non-contiguous memory, though it will opportunistically detect contiguous memory and make use of it. If it has a bug, it'd be good to have a comment here mentioning that we're fixing an upstream bug.
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.
I don't think this is a bug, as it works correctly if dlmalloc is the only part of the program that uses sbrk (or MORECORE). That's just not necessarily the case in wasm where we want to allow other functions to grow the memory.
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.
That's just not necessarily the case in wasm where we want to allow other functions to grow the memory.
Can you remind me where is this requirement coming from? Would it not simplify thing considerably if were to declare it undefined behavior the call sbkr() or memory.grow on a memory that was owned/used/controlled by malloc? i.e. can we not consider malloc as owning the entire region of memory from __heap_base to the end of the memory?
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.
I agree with giving malloc all the memory between __heap_base
and __heap_end
(which is not the end of the entire memory!), but I don't think we should prohibit other functions from growing the memory.
The use-case for me is to allow programs compiled with wasi-libc to have their own memory allocators in addition to malloc. When an application has very specific memory allocation patterns, such as all allocations having the same size, it is often more efficient to write a specialized allocator for a specific part of the program. On a regular linux target, you would then use mmap to allocate entire pages, so you don't need to mess with sbrk. However, in WebAssembly, sbrk/memory.grow is the only way to allocate new memory, so custom allocators need to able to use it without breaking malloc.
In any case, all memory pages allocated by malloc using memory.grow will continue to be used exclusively by malloc.
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.
I do wonder if its worth it (or possible) to support that kind of thing.
In that first example wouldn't make more sense if
mypage
was just statically allocated in the assembly file? e.g:.section .data.mypage,"",@ mypage: .size mypage, 65536
no. because the linear memory is shared with another module.
Then the linker could place it and it would no runtime cost to allocate.
I'm not sure what the second example is used for, but I wonder if it couldn't depend on the
malloc
symbol existing? Or at least weakly depend on it so its used when it exists?
it's basically same as the first example.
i don't think it's possible unless malloc is exported by the module.
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.
a. do not support it (memory.grow outside of this malloc) at all
b. support it only if it's done at the very beginning of the module lifetime (eg. around wasm start function)
c. support it only if it's done before the first call of malloc
d. support it only if it's serialized with normal malloc/free calls
e. support it anytime, even from another thread
@MoritzS
can't you make your use case backed by malloc as @sbc100 suggested? why not?
if you can't, which of the above do you need for your use case?
@alexcrichton @sunfishcode
which does the preview1 adapter need?
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.
I do wonder if its worth it (or possible) to support that kind of thing.
In that first example wouldn't make more sense ifmypage
was just statically allocated in the assembly file? e.g:.section .data.mypage,"",@ mypage: .size mypage, 65536
no. because the linear memory is shared with another module.
If you have two modules sharing the same memory, isn't its reasonable to assume that one of them will export a malloc function and the other one will use it. The need to coordinate somehow, and having them both call memory.grow and while assume that other can just deal with it seems.. fragile. How do you know the allocator in other other module is ok with having a non-contiguous heap? Isn't that fairly big assumption? Why not just mandate that the other module export an allocator for you to use?
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.
I do wonder if its worth it (or possible) to support that kind of thing.
In that first example wouldn't make more sense ifmypage
was just statically allocated in the assembly file? e.g:.section .data.mypage,"",@ mypage: .size mypage, 65536
no. because the linear memory is shared with another module.
If you have two modules sharing the same memory, isn't its reasonable to assume that one of them will export a malloc function and the other one will use it. The need to coordinate somehow, and having them both call memory.grow and while assume that other can just deal with it seems.. fragile. How do you know the allocator in other other module is ok with having a non-contiguous heap? Isn't that fairly big assumption? Why not just mandate that the other module export an allocator for you to use?
it isn't possible to assume proper export of malloc functions when you want to deal with the existing wasi modules.
i totally agree it's a fragile hack.
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.
which does the preview1 adapter need?
Sorry I haven't been keeping up with the rest of the context here, but the preview1 adapter won't ever be used with threads so it's ok if something odd goes wrong. Ideally wasi-libc would allow other modules to use memory.grow
instructions themselves, however, and continue to work with that.
// In wasi-libc, this branch should never be taken as CALL_MORECORE(0) may | ||
// lead to incorrect allocations. See try_init_allocator() for more | ||
// information. | ||
assert(ss != 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.
but ss can be 0 here if there was not enough space in the initial heap, can't it?
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.
@MoritzS Would you be able to comment on this question?
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.
That is true, however we still cannot take the if-branch because using CALL_MORECORE(0)
will almost always be incorrect (and that's the whole point of why try_init_allocator()
exists). So, maybe we should make sure that try_init_allocator()
either allocates at least one page if the initial heap is too small, or maybe even directly abort?
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.
what's wrong with assuming the discontiguous case if ss == 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.
You are right. I initially assumed that ss
can only be 0 in cases where the memory allocation failed, but that is not the case. I will change the code so that it falls back to the non-contiguous case if ss
is 0.
Other PRs have fixed issues with malloc not being the only code that may use the
memory.grow
instruction (#377, #394). This merge requests fixes some issues that can occur when running the WebAssembly module multi-threaded and other threads may callmemory.grow
concurrently whilemalloc
is executed.