You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Ensure the WASM output is compatible with wasmtime + WASI #1461
We should ensure that the subset of WASM that we generate in the ASR->WASM is compatible with wasmtime + WASI, so that one can use wasmtime and other tools like (wasm2c) out of the box.
$ lfortran --backend=wasm examples/expr2.f90 -o expr2.wasm
$ wasmtime expr2.wasmError: failed to run main module `expr2.wasm`Caused by: 0: failed to instantiate "expr2.wasm" 1: unknown import: `js::print_i32` has not been defined
We should standardize the WASM output that we generate, and it seems the change is minor, in this example the WAT looks like:
To be compatible with WASI/wasmtime. Details to be figured out.
Then in JavaScript we change the API of our implementation to be also compatible with WASI for the subset that we generate, and so the generated wasm will continue working at https://dev.lfortran.org/.
For printing, we can see how Clang does it using this tutorial:
Would it be fine if we use emscripten in place of clang for compiling to wasm? It seems emscripten uses clang internally (https://stackoverflow.com/a/64691937). Emscripten compiled wasm runs with wasmtime. For clang, I am currently facing the following error:
(base) compile_to_wasm$ clang --target=wasm32 main.cpp -o clang_main.wasm
wasm-ld-14: error: cannot open crt1.o: No such file or directory
wasm-ld-14: error: unable to find library -lc
wasm-ld-14: error: unable to find library -lgcc
clang: error: linker command failed with exit code 1 (use -v to see invocation)
I guess there might be a fix for the above. I am concerned if the fix might break/affect other development environment/tools like lpython/lfortran environment or emscripten tool.
It seems WASI does not have support for printing integers/floats, instead it depends on source language's library (https://bytecodealliance.zulipchat.com/#narrow/stream/217126-wasmtime/topic/import.20memory/near/325736801). Please, could someone possibly share what we should do? Shall we implement a function/class/entity in the frontend language that could print formatted integers/floats? Or Shall we implement something similar in the lines of printing integers/floats in the wasm_x64 backend?
Here is how to experiment with WASI. First install https://github.com/WebAssembly/wasi-sdk, you can go to releases and unpack a tarball. Let's create a simple program:
So the WASM binary is using exactly 5 imports. The WASM that we generate thus should use exactly these imports and things should work.
Regarding how to print floats: we have to implement it one way or the other.
Shall we implement a function/class/entity in the frontend language that could print formatted integers/floats?
We can, although it would not be used by the frontend (which would continue using the current approach with existing ASR nodes), only by the WASM backend.
Or Shall we implement something similar in the lines of printing integers/floats in the wasm_x64 backend?
Yes, the backend has to implement it. Here are some ideas how to do it:
It can implement it by using the frontend language, compile using LPython to WASM and the WASM backend would use it
It can be implemented in C, compiled to WASM using wasi-sdk and used by the WASM backend
It can be implemented directly in the backend by generating the proper WASM instructions like the wasm_x64 backend does.
We write an ASR->ASR transformation that transforms the Print node into an actual runtime implementation: we know the types at compile time, so Print([str, int, str]) would transform into a sequence of only strings Print([str, convert_int_to_str(int), str]), where convert_int_to_str is an ASR implementation of the string conversion.
The wasm_x64 backend will thus not print any integers directly anymore, it just implements fd_write (using the API above) that just prints strings, so things becomes simpler for wasm_x64.
Another way to look at it: currently we implement the int to str conversion in the wasm_x64 backend in assembly. The first step is to "lift" it up into the WASM backend to implement in WASM, and the wasm_x64 only implements printing of strings. The next step is to lift it up from the WASM backend into ASR, so WASM backend only prints strings, and ASR implements the conversion. The frontend typically generates Print(int), so it would probably be an ASR pass that does it. That way backends that don't want to implement can just call the pass.
The mechanism to do that is similar to the IntrinsicFunction mechanism that I am working on in: lfortran/lfortran#1045. Until then, just implement the int to str in Python, compile to WASM using your backend, and then you can "semi-manually" call it from the backend, or just implement in WASM by hand, it's probably not that hard, if you did it already in assembly before. That is, take this:
Here is another related issue: I realized today that wasmtime cannot create a standalone binary executable, it does compile WASM to native code, but it relies on various hooks into the wasmtime runtime: bytecodealliance/wasmtime#4563 (comment). Wasmer can create an executable and it works for the simplest examples, as shown above, but the executable is large (bloated). Also the generated binary doesn't seem to have the capability to read files: wasmerio/wasmer#3574, which is a problem. To read files it seems one must run it via wasmtime or wasmer.
The goal of our wasm_x64 backend should be to create a very small lean x64 standalone binary, and if allowed, the binary should be able to read files.
Unfortunately the wasmer created binary does not work:
$ wasmer create-exe expr2.wasm -o expr2Compiler: craneliftTarget: aarch64-apple-darwinFormat: SymbolsUsing path `~/.wasmer/lib/libwasmer.a` as libwasmer path.✔ Native executable compiled successfully to `expr2`.
$ ll -h expr2-rwxr-xr-x 1 ondrej staff 20M Feb 3 20:59 expr2
$ ./expr2Trap is not NULL: TODO:
$ echo$?255
The 507 bytes of WASM still generates a 20M binary, I think they are linking the wasmer runtime in it:
$ ll -h ~/.wasmer/lib/libwasmer.a
-rw-r--r-- 1 ondrej staff 52M Jan 26 08:44 ~/.wasmer/lib/libwasmer.a
which makes it bloated. Our backend must generate truly native executable, no runtime like this.
I think part of the reason the wasmer generated binary is so large is because they assume the webassembly code cannot be trusted, so they implement WASM in a "safe" way, handling WASM traps correctly, etc. While we assume the code can be trusted and we care more about the size of the binary and speed of compilation, so this should allow us to create small binaries.
@Shaikh-Ubaid given that neither wasmtime nor wasmer can create binaries that robustly work yet (and if they work, they are still about 1,000x larger than they should be), I would not worry about creating binaries using them, but let's use wasmtime and optionally wasmer to test that the WASM that LPython generates runs via their runtime (wasmtime expr2.wasm). And then let's make sure our wasm_x64 can create a small lean binary, that runs and behaves just like wasmtime expr2.wasm.
@Shaikh-Ubaid given that neither wasmtime nor wasmer can create binaries that robustly work yet (and if they work, they are still about 1,000x larger than they should be), I would not worry about creating binaries using them, but let's use wasmtime and optionally wasmer to test that the WASM that LPython generates runs via their runtime (wasmtime expr2.wasm). And then let's make sure our wasm_x64 can create a small lean binary, that runs and behaves just like wasmtime expr2.wasm.
Got it.
Until then, just implement the int to str in Python, compile to WASM using your backend, and then you can "semi-manually" call it from the backend, or just implement in WASM by hand, it's probably not that hard, if you did it already in assembly before. That is, take this:
What do we mean when we say "semi-manually"? Yes, we can implement it similar to wasm_x64 backend.
This #1461 (comment) outputs a small-sized binary because I think it does have any print statements. When a print statement is added, the binary size is 18.6Kb.
Is it possible to include a function in the pure runtime library which could print an integer (digit by digit similar to wasm_x64) to screen/stdout? For example:
defprint_int(x: i32):
#print digits of x similar to wasm_x64 backend's print_i32()
This function could then be parsed/processed upto the ASR (just like the other runtime library functions are processed) and then would be implicitly/auto implemented via the wasm backend. I guess this is similar to lifting the logic to the ASR and without using an ASR pass.
Activity
certik commentedon Jan 31, 2023
The goal here would be to do:
We should standardize the WASM output that we generate, and it seems the change is minor, in this example the WAT looks like:
And we just need to change the imports:
To be compatible with WASI/wasmtime. Details to be figured out.
Then in JavaScript we change the API of our implementation to be also compatible with WASI for the subset that we generate, and so the generated wasm will continue working at https://dev.lfortran.org/.
certik commentedon Jan 31, 2023
For printing, we can see how Clang does it using this tutorial: https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-tutorial.md
certik commentedon Jan 31, 2023
As an example, instead of
sys_exit_code, use theproc_exitimport:https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-proc_exitrval-exitcode
ubaidsk commentedon Feb 2, 2023
Would it be fine if we use
emscriptenin place ofclangfor compiling to wasm? It seems emscripten usesclanginternally (https://stackoverflow.com/a/64691937). Emscripten compiled wasm runs withwasmtime. Forclang, I am currently facing the following error:I guess there might be a fix for the above. I am concerned if the fix might break/affect other development environment/tools like
lpython/lfortranenvironment oremscriptentool.certik commentedon Feb 2, 2023
Yes, I think emscripten is fine. As long as it runs in wasmtime. The goal is to just figure out how they do it, what API they use.
ubaidsk commentedon Feb 2, 2023
Got it.
ubaidsk commentedon Feb 4, 2023
It seems
WASIdoes not have support for printing integers/floats, instead it depends on source language's library (https://bytecodealliance.zulipchat.com/#narrow/stream/217126-wasmtime/topic/import.20memory/near/325736801). Please, could someone possibly share what we should do? Shall we implement a function/class/entity in the frontend language that could print formatted integers/floats? Or Shall we implement something similar in the lines of printing integers/floats in thewasm_x64backend?certik commentedon Feb 4, 2023
Here is how to experiment with WASI. First install https://github.com/WebAssembly/wasi-sdk, you can go to releases and unpack a tarball. Let's create a simple program:
Compile to WASM + WASI:
You can run it with
wasmtimeorwasmeras follows:You can compile to a binary using:
And you can run the binary:
The
expr2.wasmis relatively small (18K) but theexpr2binary is quite big (20M).You can then disassemble using:
So the WASM binary is using exactly 5 imports. The WASM that we generate thus should use exactly these imports and things should work.
Regarding how to print floats: we have to implement it one way or the other.
We can, although it would not be used by the frontend (which would continue using the current approach with existing ASR nodes), only by the WASM backend.
Yes, the backend has to implement it. Here are some ideas how to do it:
wasi-sdkand used by the WASM backendPrint([str, int, str])would transform into a sequence of only stringsPrint([str, convert_int_to_str(int), str]), whereconvert_int_to_stris an ASR implementation of the string conversion.The wasm_x64 backend will thus not print any integers directly anymore, it just implements
fd_write(using the API above) that just prints strings, so things becomes simpler for wasm_x64.Another way to look at it: currently we implement the int to str conversion in the wasm_x64 backend in assembly. The first step is to "lift" it up into the WASM backend to implement in WASM, and the wasm_x64 only implements printing of strings. The next step is to lift it up from the WASM backend into ASR, so WASM backend only prints strings, and ASR implements the conversion. The frontend typically generates Print(int), so it would probably be an ASR pass that does it. That way backends that don't want to implement can just call the pass.
The mechanism to do that is similar to the IntrinsicFunction mechanism that I am working on in: lfortran/lfortran#1045. Until then, just implement the int to str in Python, compile to WASM using your backend, and then you can "semi-manually" call it from the backend, or just implement in WASM by hand, it's probably not that hard, if you did it already in assembly before. That is, take this:
lpython/src/libasr/codegen/x86_assembler.cpp
Line 139 in acd593e
certik commentedon Feb 4, 2023
Here is another related issue: I realized today that wasmtime cannot create a standalone binary executable, it does compile WASM to native code, but it relies on various hooks into the wasmtime runtime: bytecodealliance/wasmtime#4563 (comment). Wasmer can create an executable and it works for the simplest examples, as shown above, but the executable is large (bloated). Also the generated binary doesn't seem to have the capability to read files: wasmerio/wasmer#3574, which is a problem. To read files it seems one must run it via wasmtime or wasmer.
The goal of our wasm_x64 backend should be to create a very small lean x64 standalone binary, and if allowed, the binary should be able to read files.
certik commentedon Feb 4, 2023
Here is an example how to get lean WASM output using Clang: apparently we cannot use the libc library which is bloated (18K):
this gives:
The
expr2.watis just 507 bytes:Unfortunately the wasmer created binary does not work:
The 507 bytes of WASM still generates a 20M binary, I think they are linking the wasmer runtime in it:
which makes it bloated. Our backend must generate truly native executable, no runtime like this.
I think part of the reason the wasmer generated binary is so large is because they assume the webassembly code cannot be trusted, so they implement WASM in a "safe" way, handling WASM traps correctly, etc. While we assume the code can be trusted and we care more about the size of the binary and speed of compilation, so this should allow us to create small binaries.
certik commentedon Feb 4, 2023
@Shaikh-Ubaid given that neither wasmtime nor wasmer can create binaries that robustly work yet (and if they work, they are still about 1,000x larger than they should be), I would not worry about creating binaries using them, but let's use wasmtime and optionally wasmer to test that the WASM that LPython generates runs via their runtime (
wasmtime expr2.wasm). And then let's make sure our wasm_x64 can create a small lean binary, that runs and behaves just likewasmtime expr2.wasm.ubaidsk commentedon Feb 4, 2023
Got it.
What do we mean when we say "semi-manually"? Yes, we can implement it similar to
wasm_x64backend.ubaidsk commentedon Feb 4, 2023
This #1461 (comment) outputs a small-sized binary because I think it does have any print statements. When a print statement is added, the binary size is
18.6Kb.ubaidsk commentedon Feb 4, 2023
#1461 (comment),
clangfromwasi-sdkseems to work on my system. Thank you for sharing.ubaidsk commentedon Feb 4, 2023
Is it possible to include a function in the
pureruntime library which could print an integer (digit by digit similar towasm_x64) to screen/stdout? For example:This function could then be parsed/processed upto the
ASR(just like the other runtime library functions are processed) and then would be implicitly/auto implemented via thewasmbackend. I guess this is similar to lifting the logic to the ASR and without using an ASR pass.certik commentedon Feb 4, 2023
Yes, that's the first bullet point approach in #1461 (comment).
certik commentedon Feb 14, 2023
What's missing:
certik commentedon Feb 21, 2023
This issue is now fixed. The GUI supports WASI in a Draft PR that we will merge in a week: lfortran/lcompilers_frontend#64, so I'll close this issue.