-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
emmalloc: Add an option to not define the standard exports #20487
Conversation
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.
lgtm % test naming/nits
// change in usage. | ||
assert(emmalloc_dynamic_heap_size() == initial); | ||
#else | ||
// malloc == emmalloc_malloc, so emmalloc will report additional usage (of the |
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 could maybe simplify this test by simply asserting that malloc == emmalloc_malloc
as the command says? The you wouldn't need the comment to describe the code as it would more self explanatory?
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.
By taking the address, you mean? I wasn't sure that is entirely reliable in general. In particular I'd worry about there being some kind of trampoline but it still going to the same target. (With aliases that wouldn't happen, but we might refactor to use another method.)
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.
If we use another method I think its fine to update this test at that point... but that other method would seem like a bad idea since it would incur a cost.
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 think there would be a risk that we could forget to update this test... it would be a needle in the haystack of the testsuite, that is, we'd need to somehow realize it needs to be updated then. Most likely we'd forget and it could start to pass incorrectly.
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 surely the tests would fail because the assert "malloc == emmalloc" would fail right away, no?
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 other words, I think it's safer to test this by checking the functionality - that emmalloc
actually runs, or does not - and not an indirect property that is connected to that atm, if that makes sense.
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.
Fair enough. This lgtm.
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 realize you're right that malloc == emmalloc
would fail one way at least, if we check both ways (as this PR currently does, so one path checks ==
and one !=
). So my argument isn't as strong as I thought. Still, I think this is a pretty simple way to check the underlying functionality so I do feel it is safer.
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 tried to add emmalloc == malloc
testing as you suggested, because I thought it would be good to have both, but actually that fails atm, it turns out! We do have a trampoline:
emscripten/system/lib/emmalloc.c
Lines 836 to 839 in 4608c2e
void * EMMALLOC_EXPORT malloc(size_t size) | |
{ | |
return emmalloc_malloc(size); | |
} |
So in a debug build the two are obviously different. It turns out that even in a release build they are, because LLVM allocates two function pointer addresses, one for each. Optimizations (inlining + merging similar functions) end up making them identical, but there are still two function pointers, and the table ends up with two slots with the same function name in it...
It might be worth using an alias rather than a trampoline here, but I'm not sure if there was a reason not to do that. Anyhow, let's leave that separate from this PR, so I'll land this.
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 can't get emmalloc == malloc
testing to work in a followup.
I fixed the code to not have trampolines, and the function pointers are identical, and it works in a debug build, but the LLVM optimizer assumes they are not equal all the time. I seem to remember something about that being undefined behavior, that in some architectures function pointers behave very oddly... so I guess it's that.
# emmalloc == malloc. | ||
'with_std_exp': (['-DTEST_EMMALLOC_IS_MALLOC'],), | ||
}) | ||
def test_emmalloc_in_addition(self, args): |
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 love the name of this test (or the NO_STD_EXPORTS
) but I'm struggling to come up with anything better.
I'm not sure you need the with_std_exp
variant of this test since that is the default and tested elsewhere. Can we simplify this test by just verifying that it works as expected in the "NO_EXPORT" case?
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.
Maybe test_emmalloc_explicit
or test_emmalloc_standalone
? I don't really love them either.
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 considered "standalone", but I felt it is too similar to "standalone wasm" and so it's confusing. I could rename to "explicit" but I think "in addition" is slightly more clear?
I agree with_std_exp
is tested elsewhere, but I like that testing both here in relation to that one flag shows the flag exactly controls that behavior. So if we ever get it wrong it won't be a random test that fails, but here. I'd also worry about us removing/modifying the other tests, though that seems like likely.
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.
How about "alongside" instead of "in addition" or "explicit"? This does test that we can have emmalloc alongside another malloc at the same time.
|
||
#ifndef EMMALLOC_NO_STD_EXPORTS | ||
void * EMMALLOC_EXPORT malloc(size_t size) | ||
{ | ||
return emmalloc_malloc(size); | ||
} |
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.
BTW, do you know why we are not using aliases here? Seems like it would be code size and performance win to not go through this wrapper. Perhaps we should make that change as a separate change?
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.
Ah, I just saw this comment now after commenting above on this. I'm not sure why we trampoline here, and as I said there, optimizations remove 99% of the overhead but we do keep a table slot for them, so yeah, this might be worth improving.
# build emmalloc using -DEMMALLOC_NO_STD_EXPORTS it will not export malloc | ||
# etc., and only provide the emmalloc_malloc etc. family of functions that | ||
# we can use. | ||
emmalloc = path_from_root('system', 'lib', 'emmalloc.c') |
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.
When you build emmalloc in addition to malloc here.. how does it work? How does emmalloc get it underlying memory? Are they both able to grab memory from the sbrk region somehow?
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.
Both use sbrk to allocate. Neither frees anything to sbrk, so things work out.
(This won't be an issue for the intended use of this, though, which is that the second allocator calls emmalloc which calls sbrk.)
Rather than define malloc as a small function that calls emmalloc_malloc, just make it an alias, etc. In an optimized build the small functions get inlined away, but we can save that work, and also it can have downsides (e.g. they have different function pointers, which aliases do not, see discussion in #20487). For that to work, we need to make the aliases weak, to match the fact that the trampolines were weak.
With this PR if
emmalloc.c
is built with-DEMMALLOC_NO_STD_EXPORTS
then we donot define
malloc
,free
, etc. That means we only provideemmalloc_malloc
,emmalloc_free
, etc., the prefixed versions. They can then be used alongside anothermalloc impl.
This will be useful in a later PR that adds a two-tiered allocator: a fast multithreaded
one, and underneath it
emmalloc
, which will function as the "system allocator" for it.That is,
emmalloc
will play the role ofVirtualAlloc
on windows ormmap
on POSIX,a way for the main allocator to get system memory. (We can't just use
sbrk
for thatpurpose because we also want to free memory to the system.) For that goal,
emmalloc
seems suitable as it is compact (we don't need it to be super-fast; this isthe system allocator that will be called rarely, compared to the fast one before it).
And for
emmalloc
to be used like that we need this PR so that we can buildemmalloc
alongside another allocator (that other allocator will definemalloc
etc.itself).
Helps #18369