Skip to content

Apply the sbrk pointer's location to the wasm, avoiding the need to import it#9397

Merged
kripken merged 67 commits into
incomingfrom
wasi5_small
Sep 13, 2019
Merged

Apply the sbrk pointer's location to the wasm, avoiding the need to import it#9397
kripken merged 67 commits into
incomingfrom
wasi5_small

Conversation

@kripken

@kripken kripken commented Sep 6, 2019

Copy link
Copy Markdown
Member

Importing DYNAMICTOP_PTR is not compatible with wasi, and it's smaller anyhow to just have the value in the binary.

This does so by computing the sbrk ptr's static location in the JS compiler, as always, and noting it in the metadata. We then call the binaryen post-emscripten pass with that data later and it applies it.

As a nice result, we can get rid of a bunch of code for importing DYNAMICTOP_PTR, and replace the sbrk implementation in JS with a very simple one in C.

Please note:

  • Since our malloc's threadsafety is done using locks, this sbrk does no additional guarding. That's more efficient than before (we used atomics in sbrk). However, in theory someone might call sbrk not through malloc - I'm not sure that's worth supporting or not?
  • This slightly regresses other.test_minimal_runtime_code_size expectations, since now we use a standard sbrk in C, while before there were a bunch of custom options for the library.js version like USES_DYNAMIC_ALLOC=2. That mode 2 isn't documented so I'm not sure how to reproduce similar behavior in the new implementation. (Also it looks like MALLOC=none would achieve the same as USES_DYNAMIC_ALLOC=0, so maybe I'm misunderstanding this option, or maybe they are redundant?) In any case, the regression is quite small, and this is just on fastcomp - this sbrk refactoring helps on the wasm backend side. cc @juj
  • This depends on [wasm2js] Fix memory.size WebAssembly/binaryen#2330 , SafeHeap: Prepare for emscripten_get_sbrk_ptr WebAssembly/binaryen#2331, and Handle sbrk import by emscripten+upstream in SafeHeap WebAssembly/binaryen#2332 to land and roll before tests can pass.

After this PR, I can open a PR with a WASI option, as this is the last main blocker for getting "hello world" to be a valid wasi program.

kripken added a commit to WebAssembly/binaryen that referenced this pull request Sep 6, 2019
To allow #2331 to roll, I forgot that upstream and fastcomp handle sbrk differently. This fixes that, and handles the upstream case where we import sbrk itself from JS.

We can simplify this after emscripten-core/emscripten#9397 lands, however, it may also be nice to keep the backwards compatibility for running on existing wasm files in the wild.
Comment thread emcc.py Outdated
if shared.Settings.BINARYEN_PASSES:
passes += parse_passes(shared.Settings.BINARYEN_PASSES)
else:
# Safe heap must run before post-emscripten, so post-emscripten can apply the sbrk ptr

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be enforced in binaryen itself? Or, are there other places where we might want to enforce this invariant? If a user (of binaryen, or emscripten via -s BINARYEN_PASSES) is manually specifying passes, and does this the other way around, should we do anything?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmm, good question.

In theory a user may want the opposite order, if they don't want such functions to be affected. That seems unlikely though, so perhaps a warning in Binaryen would be useful. We currently don't have a mechanism for that, though (it just runs the passes blindly) but maybe we should add one. Another possible example is people running asyncify without optimizations (so not an order issue, but one pass that works better with others, maybe also worth a warning).

Comment thread src/library.js
#endif // ALLOW_MEMORY_GROWTH
},

#if MINIMAL_RUNTIME && !ASSERTIONS && !ALLOW_MEMORY_GROWTH

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yay deleting code 🎉

@kripken

kripken commented Sep 7, 2019

Copy link
Copy Markdown
Member Author

Some more fixes:

  • Emterpreter fixes: We don't need to emterpret the new sbrk and emscripten_get_sbrk_ptr, and also it is safe to call the latter at all times (it just returns a number), so the special Emterpreter assertions that check if we are running invalid code during a sleep can ignore it.
  • I added support for ignoring errno (SUPPORT_ERRNO=0) in the new sbrk. That reduces part of the minimal_runtime size regression here, since it uses that flag in the test.

@kripken

kripken commented Sep 13, 2019

Copy link
Copy Markdown
Member Author

Ok, I worried some parts of this might be controversial, so kept open for a week, but looks like there are no concerns, landing.

@kripken kripken merged commit afc1616 into incoming Sep 13, 2019
@kripken kripken deleted the wasi5_small branch September 13, 2019 00:51
@dschuff

dschuff commented Sep 13, 2019

Copy link
Copy Markdown
Member

Hey, I arrived just in time for a post-commit drive-by comment!
I think sbrk needs to be thread-safe. It's a pretty well-known syscall (even more so since it's the only one we have for allocating memory) and it's common enough to implement one's own allocator that I think we can't assume that everyone is always using one of our mallocs. The cmpxchg behavior seems fine, and we can #ifdef it out the way dlmalloc does.

Comment thread system/lib/sbrk.c
intptr_t old_brk = *sbrk_ptr;
// TODO: overflow checks
intptr_t new_brk = old_brk + increment;
#if __wasm__

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't this be #ifdef __wasm__? I don't think fastcomp defines __wasm__ == 0?

Comment thread system/lib/sbrk.c
if (new_brk > old_size) {
// Try to grow memory.
intptr_t diff = new_brk - old_size;
if (!emscripten_resize_heap(new_brk)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems odd that we are using __builtin_wasm_memory_size but not __builtin_wasm_grow_memory. Why the discrepancy?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The tricky thing is that growing memory requires us to update the JS views. Doing the resizing in JS makes that easy.

@kripken

kripken commented Sep 13, 2019

Copy link
Copy Markdown
Member Author

Thanks @dschuff, will open a followup PR for those issues.

@kripken kripken mentioned this pull request Sep 13, 2019
kripken added a commit that referenced this pull request Sep 13, 2019
Comment thread tools/system_libs.py
}[self.malloc])]
}[self.malloc])
sbrk = shared.path_from_root('system', 'lib', 'sbrk.c')
return [malloc, sbrk]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why isn't this just part of libc? Why compile it N times into each libmalloc if its the same code in each one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I guess it could be part of libc too.

In general there aren't N builds, though, there is normally just one build of malloc. Unless the user builds with flags that actually require a different sbrk build, like pthreads or errno etc. So it wouldn't be the same code in each one.

belraquib pushed a commit to belraquib/emscripten that referenced this pull request Dec 23, 2020
…mport it (emscripten-core#9397)

Importing DYNAMICTOP_PTR is not compatible with wasi, and it's smaller anyhow to just have the value in the binary.

This does so by computing the sbrk ptr's static location in the JS compiler, as always, and noting it in the metadata. We then call the binaryen post-emscripten pass with that data later and it applies it.

As a nice result, we can get rid of a bunch of code for importing DYNAMICTOP_PTR, and replace the sbrk implementation in JS with a very simple one in C.

 * Since our malloc's threadsafety is done using locks, this sbrk does no additional guarding. That's more efficient than before (we used atomics in sbrk). However, in theory someone might call sbrk not through malloc, which this does not handle atm.

 * This slightly regresses other.test_minimal_runtime_code_size expectations, since now we use a standard sbrk in C, while before there were a bunch of custom options for the library.js version like USES_DYNAMIC_ALLOC=2. That mode 2 isn't documented so I'm not sure how to reproduce similar behavior in the new implementation. (Also it looks like MALLOC=none would achieve the same as USES_DYNAMIC_ALLOC=0, so maybe I'm misunderstanding this option, or maybe they are redundant?) In any case, the regression is quite small, and this is just on fastcomp - this sbrk refactoring helps on the wasm backend side.

 * Emterpreter fixes: We don't need to emterpret the new sbrk and emscripten_get_sbrk_ptr, and also it is safe to call the latter at all times (it just returns a number), so the special Emterpreter assertions that check if we are running invalid code during a sleep can ignore it.
belraquib pushed a commit to belraquib/emscripten that referenced this pull request Dec 23, 2020
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.

4 participants