Skip to content
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

Stop including malloc/free by default #12213

Merged
merged 40 commits into from
Sep 15, 2020
Merged

Stop including malloc/free by default #12213

merged 40 commits into from
Sep 15, 2020

Conversation

kripken
Copy link
Member

@kripken kripken commented Sep 14, 2020

Before this we would always include malloc/free, and rely on metadce to remove them
when not needed. But this has downsides:

  1. Slower compile times.
  2. Larger builds when not using metadce.
  3. It prevented us from writing a nice sbrk using __heap_base, as
    if malloc is included first and removed by metadce that would still
    leave some data in the memory initialization, increasing code size
    without the workaround we had in sbrk to avoid it.

Much of the work here is to get deps_info.json to handle things properly and
include malloc/free when needed. I did this twice to double-check, but it is still
someone error-prone, as we need each code path that reached a malloc from
JS to be identified properly.

Some places use malloc/free in locations that are not part of the JS deps
system, and those now use makeMallocAbort. In those places we know
we should not reach them if malloc is not included, so we just have an
abort there. That would catch errors with deps_info.json.

In assertions builds, also help users by adding malloc/free in JS that show
a clear error if called, if malloc/free was not included but the user called
them anyhow.

With this PR we can remove the hack for sbrk, but I wanted to keep that
for a later PR as this is already not small.

#11860 makes this practical, but it is still tricky...

@kripken kripken requested a review from sbc100 September 14, 2020 20:56
@@ -1846,10 +1846,14 @@ var LibrarySDL = {
SDL_SetError: function() {},

SDL_malloc__sig: 'ii',
SDL_malloc: 'malloc',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did this work previously?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It worked thanks to how aliases work: a: "b" means a is an alias of b, so it just emits

function a() { b() }

Basically it does an indirection. And that happens to work if b is in JS or in compiled code. It is confusing though which is why I changed it.

Thinking on it now, though, even better would be to implement these in C. However it's for SDL1 and not 2 so it matters less.

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

@@ -208,10 +208,14 @@ function lengthBytesUTF32(str) {
// Allocate heap space for a JS string, and write it there.
// It is the responsibility of the caller to free() that memory.
function allocateUTF8(str) {
#if '_malloc' in IMPLEMENTED_FUNCTIONS
var size = lengthBytesUTF8(str) + 1;
var ret = _malloc(size);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised not to see the pattern used more in the library code? Is that idea to wrap all malloc calls in #if '_malloc' in IMPLEMENTED_FUNCTIONS

I wonder if this could be rewritten as:

var size = lengthBytesUTF8(str) + 1;
var ret = {{{ makeMalloc("allocateUTF8") }}} (size);
if (ret) stringToUTF8Array(str, HEAP8, ret, size);
return ret;

Thus avoiding the ifdef here?
var size = lengthBytesUTF8(str) + 1;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good idea to use a macro here, done.

This doesn't happen more because the deps_info mechanism is usually enough. But in 2 places we can't use deps (in the FS we don't want any FS usage to force malloc, and allocUTF8 isn't tracked by deps - so in both cases we don't have the fine-grained tracking we need for deps_info).

@@ -9670,5 +9652,50 @@ def test_standalone_export_main(self):
# We should consider making this a warning since the `_main` export is redundant.
self.run_process([EMCC, '-sEXPORTED_FUNCTIONS=[_main]', '-sSTANDALONE_WASM', '-c', path_from_root('tests', 'core', 'test_hello_world.c')])

def test_unincluded_malloc(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_missing_malloc_export ?

}
''')
self.run_process([EMCC, 'unincluded_malloc.c'])
self.assertContained('malloc was not included, but is needed in allocateUTF8. Adding "_malloc" to EXPORTED_FUNCTIONS should fix that. This may be a bug in the compiler, please file an issue.', self.run_js('a.out.js'))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use self.do_runf(<filename>, <expected_string>) ?

self.assertContained("malloc() called but not included in the build - add '_malloc' to EXPORTED_FUNCTIONS", out)
self.assertContained("free() called but not included in the build - add '_free' to EXPORTED_FUNCTIONS", out)

def test_unincluded_malloc_2(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_missing_malloc_export_indirect_call? Or something more meaningful than "2" ?

@kripken kripken merged commit 40f669e into master Sep 15, 2020
@kripken kripken deleted the nomalloc branch September 15, 2020 00:12
kripken added a commit that referenced this pull request Sep 15, 2020
kripken added a commit that referenced this pull request Sep 15, 2020
After we no longer include malloc/free by default (#12213) we can remove the
hack in sbrk which avoided using static memory. This gives some nice code
size improvements.

However this can't be as simple as we'd hope because of dynamic linking
plus SAFE_HEAP. In that mode we may try to use sbrk_val before the
dynamic linker has set it up. This is because SAFE_HEAP is not aware of
dynamic linking - it just instruments all loads and stores, period, including
ones that happen during dynamic linking startup. To avoid ordering issues
there, initialize sbrk_val manually.
kripken added a commit that referenced this pull request Sep 17, 2020
This removes the mmap dependency from musl's
locale code, and by doing that fixes a dependency bug with libc++ using
locale code that causes mmap to be included, and mmap from JS needs
malloc/free which we no longer include since #12213

That seems nicer anyhow, as mmap was only used there when the user
provided MUSL_LOCPATH - I don't think it's worth it for us to support
that nonstandard option.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants