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

Support wasm32-unknown-emscripten target #647

Closed
ghost opened this issue Dec 28, 2020 · 48 comments
Closed

Support wasm32-unknown-emscripten target #647

ghost opened this issue Dec 28, 2020 · 48 comments
Labels
c: sys Component: low-level bindings (mod sys) status: postponed
Milestone

Comments

@ghost
Copy link

ghost commented Dec 28, 2020

With WebAssembly support being added to the engine, this should be a priority for the next release.

@ghost ghost added the c: sys Component: low-level bindings (mod sys) label Dec 28, 2020
@ghost ghost added this to the 0.10 milestone Dec 28, 2020
@ghost ghost added the status: postponed label Jan 3, 2021
@ghost ghost removed this from the 0.10 milestone Jan 3, 2021
@ghost
Copy link
Author

ghost commented Jan 3, 2021

Unfortunately, this cannot be done right now since Rust is still using LLVM 11, which is no longer supported by the version of emscripten that Godot requires (2.0.10). Rust only updates LLVM on new releases, so we'll have to wait for a few months until LLVM is updated upstream.

Until then, it might be a good idea to clarify the state of WASM support in documentation.

@deep-gaurav
Copy link

Any updates on this?

@ghost
Copy link
Author

ghost commented Apr 27, 2021

@deep-gaurav Not yet, still blocked by rustc :(

@remissio
Copy link

remissio commented May 8, 2021

With Version 1.52 we got LLVM 12 Support:

https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1520-2021-05-06

image

@orion78fr
Copy link
Contributor

orion78fr commented Jun 3, 2021

Just FYI I successfully compiled for wasm with various hacks : https://github.com/orion78fr/godot_keepass_rust_totp
Example : https://orion78.fr/godot_test/keepassTotp.html (it has a simple script that change the button text according to a function call in rust, but I may update things so it may not work as of now)

@ghost
Copy link
Author

ghost commented Jun 4, 2021

@orion78fr That's wonderful! Last time I checked there was some miscompilation around i128/u128, but glad to see that the ar trick is working now! Could you add a license on the emcc-test files so we can actually put the workaround in our docs for other people?

@orion78fr
Copy link
Contributor

I've put the repository under MIT License, that should be OK.
The whole steps are here : https://github.com/orion78fr/godot_keepass_rust_totp/actions/runs/900998393/workflow#L255

Some things I had to do :

  • Don't forget to set export type to GDNative in the export template (or it won't even try to load the wasm silently) :
    gdnative_wasm_export
  • Pass -all to wasm-opt because it's automatically called by emcc but doesn't seem to pass the correct wasm feature flags and fails
  • Disable stack unwinding because C exceptions is still in spec : https://github.com/WebAssembly/exception-handling (CARGO_PROFILE_RELEASE_PANIC: abort, maybe the other flags for overflow / assertions are necessary but I did so many testing I don't remember).
  • Need to compile with PIC to allow the linker to relocate code : RUSTFLAGS: -C link-args=-fPIC -C relocation-model=pic

Maybe something else, I don't remember x)

@ghost
Copy link
Author

ghost commented Jun 4, 2021

@orion78fr Thanks. The workflow file is very helpful!

@extrawurst
Copy link
Contributor

extrawurst commented Jun 27, 2021

@orion78fr thanks so much for looking into this. did you try to run your Web export in safari? on Firefox, chrome it seems to work fine.

Screenshot 2021-06-27 at 11 07 19

I have the same issue with my demo, so I was wondering

@orion78fr
Copy link
Contributor

Yeah I only tested Chrome / Firefox on Windows and Android (as I mainly focus on APK export atm, can't figure out how I could make Android java calls to open files).

Seems like it's some part of the specs that are not supported on Safari.

CanIuse list shared memory as not supported, but I don't know if it's this in particular. Note that I had emcc complaining about shared memory though so maybe it's this.

image

@extrawurst
Copy link
Contributor

@orion78fr I can export a regular WebGL app via godot and it runs in the browser: https://jontopielski.itch.io/puzzle-sigma (just an example)

so I am still a little hopeful we can figure out how to build a gdnative lib so that safari can load it aswell - after all the above shows that godot itself manages to compile to something safari is able to take and run. it must be some crazy param that i am missing to pass to emcc

@extrawurst
Copy link
Contributor

whatever I do once I run wasm-validate on my gdnative .wasm I get:

wasm-validate _deploy/web/logic.wasm
0000da7: error: invalid section code: 12

which does not happen on any of the Wasm files godot produces.

after a little digging validate works once I pass: --enable-bulk-memory
which tells me that somehow my emcc generates something we do not want

@orion78fr
Copy link
Contributor

(Oh I've seen this one featured in the GMTK jam 2021 video !)

Yeah probably, but I think that I'm missing some flags to pass to rustc too, like the "pic" one or the "panic = abort" to avoid generating things that emcc / wasm don't like.

The dynamic lib ABI is still in a WIP state though : https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md

I've read somewhere that iOS doesn't even support dynamic libs and that GDNative lib should be statically linked, maybe we could do something similar and produce a binary with both godot and out GDNative lib ?

@orion78fr
Copy link
Contributor

Try removing -Wl,--no-check-features -all from the emcc flags, you will see the experimental part used by the binaries produced by rustc.
I really stopped debugging this when it produced a binary that loaded without error. Probably some code generated by the rust compiler isn't correctly supported by all browsers but I don't know how to avoid it, I'm really a noob at rust atm.

@cristodcgomez
Copy link

Hi! I just arrived after googling a few hours... Is there a "one-line" command to do the same as you have in the build? (I'm on archlinux)

I tried several things but I end with the error:

...registry/src/github.com-1ecc6299db9ec823/gdnative-sys-0.9.3/godot_headers/gdnative/string.h:39:10: fatal error: 'wchar.h' file not found

I setted the clang lib with LIBCLANG_PATH=/usr/lib/emsdk/upstream/lib, tried other ones that I installed in the OS... I have no luck with this

I tries to build for wasm32-unknown-unknown and I have the same error with the header...

@extrawurst
Copy link
Contributor

Try removing -Wl,--no-check-features -all from the emcc flags, you will see the experimental part used by the binaries produced by rustc.
I really stopped debugging this when it produced a binary that loaded without error. Probably some code generated by the rust compiler isn't correctly supported by all browsers but I don't know how to avoid it, I'm really a noob at rust atm.

yeah that gives us:

wasm-ld: error: mutable global imported but 'mutable-globals' feature not present in inputs: `__stack_pointer`. Use --no-check-features to suppress.

so the question is: can we make rust not emit mutable-globals (maybe via https://releases.llvm.org/10.0.0/tools/clang/docs/ClangCommandLineReference.html#cmdoption-clang-mmutable-globals)?

@extrawurst
Copy link
Contributor

@orion78fr oooor reading this maybe we can advance here not exporting all functions --export-all since the exported functions are well defined by gdnative anyway

@orion78fr
Copy link
Contributor

@cristomc I think you are missing the include path from emscripten (C_INCLUDE_PATH: ${{env.EMSDK}}/upstream/emscripten/cache/sysroot/include/)

@extrawurst
Copy link
Contributor

A little followup:

I figured out all the emscripten specific rust flags using:

rustc --target wasm32-unknown-emscripten --print target-features
Features supported by rustc for this target:
    simd128             - Enable 128-bit SIMD.
    atomics             - Enable Atomics.
    nontrapping-fptoint - Enable non-trapping float-to-int conversion operators.
    crt-static          - Enables C Run-time Libraries to be statically linked.

Code-generation features supported by LLVM for this target:
    bulk-memory         - Enable bulk memory operations.
    exception-handling  - Enable Wasm exception handling.
    multivalue          - Enable multivalue blocks, instructions, and functions.
    mutable-globals     - Enable mutable globals.
    reference-types     - Enable reference types.
    sign-ext            - Enable sign extension operators.
    tail-call           - Enable tail call instructions.

Use +feature to enable a feature, or -feature to disable it.
For example, rustc -C target-cpu=mycpu -C target-feature=+feature1,-feature2

Code-generation features cannot be used in cfg or #[target_feature],
and may be renamed or removed in a future version of LLVM or rustc.

so by passing -C target-feature=+mutable-globals the emscripten error actually goes away. unfortunately the resulting library still contains bulk-memory operations

@orion78fr
Copy link
Contributor

Can you try adding -C target-feature=-bulk-memory to RUSTFLAGS ? Maybe it will work

@extrawurst
Copy link
Contributor

Can you try adding -C target-feature=-bulk-memory to RUSTFLAGS ? Maybe it will work

I tried all that :D, seems all features are off by default.

but I got it to run. let me cleanup and see which of the thousand changes actually were needed, then I will post that here🥳

@stevedana
Copy link

but I got it to run.

Example code of this would really help

@extrawurst
Copy link
Contributor

but I got it to run.

Example code of this would really help

like I said: I am cleaning it up and posting back here...

@stevedana
Copy link

Sorry wasn't sure if you meant you were just going to share the flags

@extrawurst
Copy link
Contributor

Sorry wasn't sure if you meant you were just going to share the flags

It sure is just about the flags - the rest is unchanged to the repo @orion78fr shared

@extrawurst
Copy link
Contributor

extrawurst commented Jun 30, 2021

Now I can confirm it works on safari aswell, you only need to reduce the optimisation: EMMAKEN_CFLAGS="-O1 -s SIDE_MODULE=1"

But this is not where the story ends, I wanted to port two open source godot projects: godot-vs-rapier and rapier-determinism (you can find the exact build calls as makefiles in there aswell)

Both make use of rapier with different feature flags and uncovered an issue in rapier itself using instant which was not emscripten ready: dimforge/rapier#207
When using serde support the 128bit error @toasteater mentioned happens which boils down to serde deliberately disabling 128bit support in emscripten builds: serde-rs/serde#2049

After figuring all those out I now have web builds of the above projects!! 🥳

see:
https://extrawurst.github.io/godot-vs-rapier/
https://extrawurst.github.io/rapier-determinism/

Thanks again @orion78fr for kicking this off.

A few things that I would like to investigate from here on:

  • update godot 3.3 to 3.3.2
  • updating emscripten (I ran into issue if the emsdk was not exactly 2.0.17
  • the file api in gdnative does not work in wasm builds (I just forgot to make my custom export rules apply to the web export)
  • figuring out why optimisation breaks safari builds (-O0 builds bigger wasm files)

@sjkillen
Copy link

Is this stable enough to be added to the Makefile in godot-rust-template?

@orion78fr
Copy link
Contributor

Honestly, I'm unsure. It seems very brittle.
Changing opt flag level or the emscripten version seems to break the build or make it incompatible with some browser vendors.

@extrawurst
Copy link
Contributor

being a emscripten noob I am wondering: is there a way to make godo-web call our gdnative lib compiled to regular wasm? I mean the wasm64-unknown-unknown seems more stable and we do not have to worry at all about the PITA emscripten toolchain.

@orion78fr
Copy link
Contributor

That would have been cool but it seems like godot-rust needs a libc (wchar.h & co), which is what is provided by emscripten IIRC.

@yue4u
Copy link

yue4u commented Oct 21, 2021

I'm on archlinux (Linux home 5.14.9-arch2-1 #1 SMP PREEMPT Fri, 01 Oct 2021 19:03:20 +0000 x86_64 GNU/Linux) and tried to build locally via RUSTFLAGS="-Clink-args=-fPIC -Crelocation-model=pic --crate-type=lib" EMMAKEN_CFLAGS="-s SIDE_MODULE=1 -shared -Wl,--no-check-features -all" C_INCLUDE_PATH=/home/yue/dev/emsdk/upstream/emscripten/cache/sysroot/include/ cargo build --target wasm32-unknown-emscripten --release but running into the error below:

error: extern location for gdnative does not exist: /home/yue/dev/zemi/game-1/lib/target/wasm32-unknown-emscripten/release/deps/gdnative-b572fd01bb1fca7c.js
 --> src/lib.rs:1:5
  |
1 | use gdnative::prelude::*;
  |     ^^^^^^^^

error: aborting due to previous error

error: could not compile `lib`

not sure about how to fix this.

@Bromeon Bromeon added this to the v0.11 milestone Nov 1, 2021
@derivator
Copy link

derivator commented Jun 23, 2022

I've been trying to get this to work on an up-to-date emscripten. Using rust nightly and emscripten tip-of-tree, it seems to be almost working, here's what I've got:

@hoodmane figured out some flags that are needed here.
In config.toml:

[target.wasm32-unknown-emscripten]
rustflags = [
   	"-Crelocation-model=pic",
	"-Clink-arg=-sSIDE_MODULE=2",
	"-Clink-arg=-sWASM_BIGINT", #not strictly necessary?
	"-Clink-arg=-sDYNCALLS=1", #should fix the issue I'm currently stuck on, but doesn't work
]

In the future, these shouldn't be needed anymore.

To build:

export CARGO_BUILD_TARGET=wasm32-unknown-emscripten
export C_INCLUDE_PATH=$EMSDK/upstream/emscripten/cache/sysroot/include
	
cargo +nightly build --release -Zbuild-std

-Zbuild-std will hopefully also not be needed in the future.

The Godot 3.4.4 HTML5 export template is built with an older, incompatible emscripten version, you have to build it yourself according to this. Should be fixed in 3.6 I think.

Godot loads the wasm module and godot_print! in the init-function works, but trying to register a class throws an error, possibly related to this issue.
This function:

 function dynCallLegacy(sig, ptr, args) {
    var f = Module["dynCall_" + sig];
    return args && args.length ? f.apply(null, [ptr].concat(args)) : f.call(null, ptr)
 }

is called with sig == viiji, and f is undefined.

Emscripten 2.0.13 release notes say:

Add back support for calling the legacy dynCall_sig() API to invoke function
pointers to wasm functions from JavaScript. Pass -s DYNCALLS=1
to include that functionality in the build.

This looks like it should fix the issue, but I haven't had any luck with it. It also seems that the issue occurs regardless of the WASM_BIGINT flag.

@hoodmane
Copy link

-Zbuild-std will hopefully also not be needed rust-lang/rust#98149.

Unfortunately it looks like we were a day late for the 1.62.0 merge window, but it should make it into 1.63.0 which will probably be at the beginning of August. We may also get fixes into 1.63.0 that make it "just work" so that a cdylib compiled with emscripten target will automatically pass -sSIDE_MODULE=2 and other relevant flags.

I wrote a blog post on this:
https://blog.pyodide.org/posts/rust-pyo3-support-in-pyodide/

It also seems that the issue occurs regardless of the WASM_BIGINT flag.

This issue shouldn't happen with the WASM_BIGINT flag. You need to make sure to pass it to both the main module and the side module. But I'm surprised you would get an error like that from passing it only to the side module. I will try building godot-rust and see what the generated code looks like.

@hoodmane
Copy link

If you are using a precompiled main module which was compiled without WASM_BIGINT, then you can build with this patch applied to Emscripten and the error should go away:
https://github.com/emscripten-core/emscripten/pull/16693/files

@hoodmane
Copy link

I confirmed that you will see that error if you compile the side module with -sWASM_BIGINT but not the main module.

@derivator
Copy link

Holy moly, it works. @hoodmane thanks so much for your help! I had to patch the build script for godot's javascript export template by adding these lines:

sys_env.Append(LINKFLAGS=["-s", "WASM_BIGINT=1"])
wasm_env.Append(LINKFLAGS=["-s", "WASM_BIGINT=1"])

@Bromeon
Copy link
Member

Bromeon commented Jun 24, 2022

@hoodmane @derivator (and others in this thread):
Would you be interested in writing a guide to set up godot-rust with WebAssembly in the book?

I have the feeling that this thread contains a lot of very valuable insights, but they're a bit scattered and hard to find for someone new to the topic. On our Discord there have also been a few users tinkering with WASM, maybe they could help as well!

@derivator
Copy link

@Bromeon I could write up a short guide, but with @hoodmane's patches in rust hopefully being merged, much of it will be obsolete pretty soon. I also want to talk to the godot people about building the export templates with WASM_BIGINT by default, so everything would just work(tm) in the future.

@hoodmane
Copy link

Out of curiosity, is there a reason you're dynamically linking this? It seems like it would be reasonable to statically link the rust bindings and the engine together for wasm, in particular you'd get faster load times and smaller code size. If that doesn't work for some reason you could also try building the bindings first and passing the so file to the main module and using -sMAIN_MODULE=2 which would also reduce code size a lot.

@hoodmane
Copy link

Another approach you might try is setting panic=abort. These invoke functions are all related to stack unwinding, so turning it off might get rid of them. Note also that currently -sWASM_BIGINT drops support for Safari 14.x though hopefully the next version of Emscripten will include my patch to support Safari 14.x.

@derivator
Copy link

Out of curiosity, is there a reason you're dynamically linking this?

@hoodmane I think that's just how Godot/GDNative works. You define a GDNative library in the editor and provide a .dll/.so/.wasm for Windows/Linux/HTML5 export. I can only speculate why Godot chose dynamic linking, I suspect it's just for developer convenience, so you don't have to link the engine yourself when you export. I don't know if it's possible to use static linking instead (apparently you have to on iPhone because it doesn't allow dynamic linking?), but the default workflow seems to be dynamic linking, so it makes sense to try to support that.

@hoodmane
Copy link

Emscripten tries to statically link if at all possible -- this is why if you pass -shared it prints a warning and generates a static lib anyways. There are two main reasons for this:

  1. Code size matters much more over a network and much more often applications ship with everything they need
  2. Wasm doesn't have native support for dynamic linking so the runtime support for dynamic linking is complicated and potentially error prone. It also doesn't interact all that well with other "hacked-in" features that wasm MVP doesn't natively support, like 64 bit integers (enable native support with -sWASM_BIGINT), exception handling (has native support but it has a different ABI that Rust doesn't know about), and threading (enable with -pthreads).

In particular, currently you can only pick one of -pthreads and dynamic linking. But game engines might really need to do code splitting, say only loading a level when it knows it is going to be used soon. So maybe you are forced to use dynamic linking.

@derivator
Copy link

Emscripten tries to statically link if at all possible

The thing is that Godot usually uses a prebuilt executable for the engine. I don't know if there's even an established workflow for linking a GDNative module statically, though I guess if it's needed for iPhone, it has to exist somewhere...

@hoodmane
Copy link

hoodmane commented Jul 2, 2022

For the record, I opened a PR to fix Rust side modules when not using -sWASM_BIGINT:
emscripten-core/emscripten#17328

@Roger
Copy link

Roger commented Aug 7, 2022

I'm a bit confuse, I was able to build and run my project, using tot emscripten + custom godot export template + rust nightly 1.64. I was expecting to see a panic, since I'm using Instant::now() and reading hoodmane's blog post it seems that is not supported in wasm32-unknown-emscripten, and looking at ~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/wasm/mod.rs it uses unsupported/time.rs which just panics on now, but instead what I see is that Instant::elapsed() advances as it was an int, instead of a float, it jump from second to second, ie. godot_print!("elapsed: {}ms", my_instant.elapsed().as_millis()); in _physics_process, jumps from 0ms to 1000ms, 2000ms, etc.

Any idea of how it is possible that Instant::now is not panicking and what's going on here?

@Roger
Copy link

Roger commented Aug 7, 2022

ooh, right, in emscripten the environment is unix, not wasm, just checked with:

     cfg_if::cfg_if! {
         if #[cfg(unix)] {
             godot_print!("env: UNIX");
         } else if #[cfg(target_family = "wasm")] {
             godot_print!("env: WASM");
         } else {
             godot_print!("env: OTHER");
         }
     }

and that's using mach_absolute_time, still, don't know why mach_absolute_time is not behaving in the same way in emscripten than unix.

@Bromeon
Copy link
Member

Bromeon commented Aug 7, 2022

@Roger #[cfg] is a proc-macro attribute that will remove blocks where it doesn't apply. Are you sure you didn't mean the declarative macro cfg!, which evaluates to bool?

Edit: nevermind, didn't know about the surrounding cfg_if semantics.

@Bromeon
Copy link
Member

Bromeon commented Oct 12, 2022

There is now a HTML5 guideline in the book. While there's probably still work to do, I would consider this a first step towards WebAssembly support, and thus close this issue.

Thanks a lot to everyone who has helped getting there! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: sys Component: low-level bindings (mod sys) status: postponed
Projects
None yet
Development

No branches or pull requests