WebAssembly / binaryen

Optimizer and compiler/toolchain library for WebAssembly
Apache License 2.0
7.48k stars 740 forks source link

wasm-opt: Optimize for label count #5530

Closed iBicha closed 1 year ago

iBicha commented 1 year ago

Looking for advice on a long shot.

The goal here is to run arbitrary wasm code on a Roku TV using brightscript

There is a tool that translates wasm modules to brightscript (wasm2brs). Brilliant!

The problem is that the brightscript runtime has limitations that wasm don't have. One of these limitations is label count within a funciton (goto instruction labels). Code fails to run as the label count gets closer to 256 (128 is a soft limit, and 256 is a hard limit, based on testing, see here)

For bigger projects, it's normal to a wasm function with hundreds of labels. wasm-opt is being used to help with that (using -O4), but a lot of the times it's not good enough to keep the count level below the limit.

My question is, is there a way to transform a wasm module, in a way that functions do not exceed a certain number of labels?


Another approach we tried is to translate a wasm runtime (e.g. wasm3) from wasm format to brightscript, and use that to load another wasm module. This approach does not hit the label count issue, but it faces another limitation of the BrightScript runtime, which is stack depth (even loading a hello world wasm leads to a stack overflow).

kripken commented 1 year ago

To limit the number of labels you'd need to do two things:

  1. Break up functions with too many labels. A function with 300 labels after each other would need to be two functions with less than 256, and the first calls the second.
  2. Break up deeply-nested labels. It's common to have a single br_table that targets much more than 256 labels, so simple function splitting wouldn't help. You'd need to do something like have a chain of ifs, and to split out code to other functions as needed.

Both are doable but would take some serious work.

Getting a wasm runtime to work seems easier to me. Do you know why it wasm3 hits a stack depth issue? Note that even on hello world there may well be one of those big br_table instructions, inside printf or such; if that's the issue then perhaps wasm3 could be written to use a stack instead of recursion or something like that. Or you can try another interpreter. The Binaryen interpreter has special code to avoid recursion in those places:

https://github.com/WebAssembly/binaryen/blob/059622228659fbf59dd82363fd16323725288de1/src/wasm-interpreter.h#L219-L226

You can also try the wabt interpreter.

iBicha commented 1 year ago

Thanks for the reply @kripken !

Break up functions with too many labels

This is not as easy as it sounds. Some functions are simply too big and complex, and there's a good chance to break it when attempting to do so. Besides, this does not scale for some projects where there's plenty of functions like this unfortunately. This is why my thoughts where "Oh wasm-opt might perhaps be able to do that by splitting functions etc"

Do you know why it wasm3 hits a stack depth issue?

This might be due to how it works https://github.com/wasm3/wasm3/blob/main/docs/Interpreter.md

because M3 execution leans heavily on the native stack, this does create one runtime usage issue

Or you can try another interpreter.

Yeah I would need to find an interpreter that does not use the stack natively, and that can build to wasm, and hopefully 🤞 works.

MaxGraey commented 1 year ago

wasm3 has a quite small stack limits by default due to it applications (IoT) but it has ability to config this: https://github.com/wasm3/wasm3/blob/main/source/m3_config.h#L20

iBicha commented 1 year ago

wasm3 has a quite small stack limits by default due to it applications (IoT) but it has ability to config this: https://github.com/wasm3/wasm3/blob/main/source/m3_config.h#L20

That's cool! unfortunately it still hits an overflow for some reason

Edit: If I'm not mistaken, d_m3MaxFunctionStackHeight is to ensure the stack at the wasm runtime level, not at the native stack level. What I need is for the wasm runtime to not use the native stack at all, because it is very limited.

The alternative might be to try another interpreter

iBicha commented 1 year ago

@kripken is there a simple way to build the Binaryen interpreter into wasm?

kripken commented 1 year ago

@iBicha There is already an emscripten port of binaryen, see the CMakeFiles. That emits wasm+JS. Porting to pure wasm with wasi-libc should be easy except that we use C++ exceptions, which they don't support yet, but they will eventually I hope.

iBicha commented 1 year ago

Thank you everyone for the help, I'll close this for now