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

wasm: introduce custom stack allocator for wasmtime #14161

Merged
merged 5 commits into from
Oct 18, 2023

Conversation

rockwotj
Copy link
Contributor

Introduce a custom stack allocator that we plugin to wasmtime using the APIs added in bytecodealliance/wasmtime#7209.

See the individual commits on the overall design and implementation details, as well as the tradeoffs of other options.

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v23.2.x
  • v23.1.x
  • v22.3.x

Release Notes

  • none

@rockwotj rockwotj requested review from BenPope, a team, emaxerrno and dswang as code owners October 13, 2023 16:05
@rockwotj rockwotj requested review from andrewhsu and removed request for a team October 13, 2023 16:05
Signed-off-by: Tyler Rockwood <rockwood@redpanda.com>
Now that we've activated wasmtime's async functionality, a couple of
crates have changed.

Signed-off-by: Tyler Rockwood <rockwood@redpanda.com>
@rockwotj
Copy link
Contributor Author

rockwotj commented Oct 13, 2023

@travisdowns commit eae2763 has the implementation of what we talked about in slack if you're interested in taking a peek (no worries if not)

@rockwotj rockwotj force-pushed the stacks branch 3 times, most recently from 144d63b to 6e10e25 Compare October 14, 2023 03:53
@vbotbuildovich
Copy link
Collaborator

@rockwotj rockwotj requested a review from mmaslankaprv October 18, 2023 03:49
src/v/wasm/allocator.cc Show resolved Hide resolved
src/v/wasm/allocator.cc Outdated Show resolved Hide resolved
src/v/wasm/allocator.h Show resolved Hide resolved
src/v/wasm/wasmtime.cc Show resolved Hide resolved
src/v/wasm/wasmtime.cc Show resolved Hide resolved
// stack space left.
std::ptrdiff_t stack_left = (&dummy_stack_var) - bounds->bottom;
void* stack_ptr = ::alloca(stack_left - max_host_function_stack_usage);
// Prevent the alloca from being optimized away by logging the result.
Copy link
Member

Choose a reason for hiding this comment

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

why is this a concern? is there some sort of data dependency analysis that the compiler can't see?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure? At first I was just calling ::alloca(...) and discarding the result and the stack wasn't overflowing, so I guess clang was optimizing that away or something? Ooh I should also probably make sure there is no tail call optimization or anything too.

By default wasmtime allocates stacks using `mmap`, but there is now an
API to override the stack allocator.

This is the custom allocator that we will plug into wasmtime. We
allocate stacks on demand since they will fit within our 128KB
allocation limit that we recommend. These stacks will also have a
guard page at the bottom of the stack to protect against stack overflow.

We cache stacks (indefinitely) because the call to `mprotect` will break
up any transparent huge pages (THP) that have been allocated by the
seastar allocator, in an effort to not breakup all these pages all over
memory we reuse them aggressively. Another option is to preallocate a
2MB THP on startup and chunk that up for wasmtime stacks, but then that
imposes a limit on the number of VMs we can run on a single core, to
attempt to prevent that limitation we will allocate them dynamically, as
the performance impact should be small anyways for breaking up those
pages.

There is more as comments in the allocator header as well.

Signed-off-by: Tyler Rockwood <rockwood@redpanda.com>
This plugins a sharded stack allocator into a custom wasmtime stack
allocator. The setup and API here is very similar to how the custom
wasmtime (linear) data memory is plugged in.

Signed-off-by: Tyler Rockwood <rockwood@redpanda.com>
We want to ensure that our host functions don't blow the stack when
executing if a guest uses up a bunch of stack space within the Wasm VM.

We do this in our tests by allocating a variable amount on the stack so
that it looks to every host function that the guest has used the maximum
amount of the stack.

Additionally, we only run in this "strict stack mode" in release tests.
We don't want to do this in production builds, and in debug builds ASAN
throws a fit when doing this. Presumably ASAN complains because it
doesn't know we've switched the stack as that happens in Rust land which
isn't instrumented with ASAN checks. Honestly this is fine as stack
usage in debug mode is wildly different than release mode and we're
realistically only using debug mode internally in non production usage.

Signed-off-by: Tyler Rockwood <rockwood@redpanda.com>
@rockwotj
Copy link
Contributor Author

Force push: Address #14161 (comment)

@rockwotj rockwotj requested a review from dotnwat October 18, 2023 15:06
Copy link
Member

@dotnwat dotnwat left a comment

Choose a reason for hiding this comment

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

lgtm.

i think it is worth understanding the bit about alloca being optimized away, but other than that ship it.

@rockwotj
Copy link
Contributor Author

rockwotj commented Oct 18, 2023

i think it is worth understanding the bit about alloca being optimized away

Looks like clang "optimizes" it away: https://godbolt.org/z/eEexvrj4b

Which may or may not be compiler bug... happy to take suggestions on better ways to prevent it from being optimized away (we do want this to work in release mode).

@dotnwat
Copy link
Member

dotnwat commented Oct 18, 2023

Which may or may not be compiler bug... happy to take suggestions on better ways to prevent it from being optimized away (we do want this to work in release mode).

Oh i see on closer inspection you want the extra stack space but you have no use for the returned stack_ptr, and that's probably why it is optimized away without the logging.

I do wonder though if it being optimized away is a mirage? For example. If I write ::alloca(100) and ignore the return value, then wouldn't it be a legitimate optimization for the compiler to behave as if alloca never existed and just allocate the extra space?

@rockwotj
Copy link
Contributor Author

I do wonder though if it being optimized away is a mirage? For example. If I write ::alloca(100) and ignore the return value, then wouldn't it be a legitimate optimization for the compiler to behave as if alloca never existed and just allocate the extra space?

I'm not sure I follow - it's probably fine to ignore the alloca if we ignore the return result, but it does change the semantics of the resulting code.

behave as if alloca never existed and just allocate the extra space

huh these seem at odds?

@rockwotj rockwotj merged commit 0860203 into redpanda-data:dev Oct 18, 2023
9 checks passed
@rockwotj rockwotj deleted the stacks branch October 18, 2023 18:55
@travisdowns
Copy link
Member

@rockwotj - it's a bit hard to be precise because alloca is not part of the C++ standard and so it sort of lives in this grey area where the semantics aren't entirely clear since we can't refer solely to the types of side effects that the standard guarantees will occur.

However, it is not surprising to me that the alloca is optimized away and if it were defined in the standard I would expect it be in a way that allows it be optimized away. For example, in the same way that malloc, free, new and delete can be optimized away. The primary side effect of alloca is to allocate space which you can use, with certain restrictions (e.g., freed implicitly when the function returns). Actual manipulation of the stack pointer (whatever that even means) is very far down the list of interesting side effects yet has a large cost to enforce so this is definitely the type of thing that ends up in the "not a guaranteed side effect bucket".

The usual way to work around this stuff is to "escape" the relative value. E.g., escape the alloca pointer, so the compiler no longer knows what went on it with and has to assume it gets used in all the supported ways. We have perf::do_not_optimize(x) which "escapes" a value, it works in this case:

https://godbolt.org/z/W7nK4jGsd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants