Skip to content

Conversation

@ufoot
Copy link

@ufoot ufoot commented Dec 15, 2025

Closes #347

This PR enables cross-compilation to 32-bit targets such as wasm32-unknown-emscripten.

Problem

Bindgen generates compile-time struct size/alignment assertions based on the host architecture. When cross-compiling from a 64-bit host to a 32-bit target, these assertions fail because pointer widths differ (8 bytes vs 4 bytes).

Solution

The fix handles both binding modes:

  • api-custom mode: Conditionally disable layout_tests in bindgen when host and target pointer widths differ. Native builds retain full safety checks.
  • Prebuilt mode: Strip the assertion blocks from pre-generated bindings only when CARGO_CFG_TARGET_POINTER_WIDTH == "32". The prebuilt bindings are generated on 64-bit, so 64-bit targets keep all checks.

CI

Added a wasm-check job that verifies compilation for wasm32-unknown-emscripten. This should catch pointer width regressions. (not sure how I can check that, how CI works exactly etc. but I get a feeling adding a non-regression test won't harm, I suspect the real is more complex than this naive "add a target on matrix", cf #1275)

Testing

I have a working local prototype with my game https://gitlab.com/liberecofr/liquidwar7 running on wasm32.

image

Required Cargo.toml configuration:

[dependencies]
godot = { version = "...", features = ["experimental-wasm", "experimental-wasm-nothreads"] }

[workspace.dependencies]
# Required for wasm - provides random source in browser
getrandom = { version = "0.3", features = ["wasm_js"] }

The getrandom bit is because on that project, this crate is used, it's not strictly related to this PR but I thought it was interesting to mention it as a) if you want to reproduce, you'd need it and b) random number generation is not so rare in games, so the workaround may be interesting to know.

Required .cargo/config.toml:

[target.wasm32-unknown-emscripten]
rustflags = [
      '-C', 'link-arg=-sSIDE_MODULE=2',
      '-C', 'link-arg=-sDISABLE_EXCEPTION_CATCHING=1',
      '-C', 'link-arg=-sERROR_ON_UNDEFINED_SYMBOLS=0',
      '-C', 'panic=abort',
]

The ERROR_ON_UNDEFINED_SYMBOLS=0 is key otherwise my program would die with a cryptic error saying invoke_viii is not defined...

Commits

  1. [lint] Add clippy override to tolerate suspicious formatting
  2. Add 32-bit architecture support - Core fix for bindgen layout test issues
  3. Add wasm32 compilation check to CI - Prevents future regressions

Enable cross-compilation to 32-bit targets (e.g., wasm) by addressing
bindgen layout test incompatibilities.

Problem:
Bindgen generates compile-time struct size/alignment assertions based on
the host architecture (64-bit). When cross-compiling to 32-bit, these
assertions fail because pointer sizes differ (8 bytes vs 4 bytes).

Solution:
- For `api-custom` mode: disable layout_tests in bindgen configuration
- For prebuilt mode: strip layout assertion blocks only when targeting
  32-bit (CARGO_CFG_TARGET_POINTER_WIDTH == "32")

Native 64-bit builds retain all safety checks intact. The assertions are
only removed for 32-bit targets where they would be incorrect anyway
(validating 64-bit sizes against 32-bit structs).
Add a `wasm-check` job that verifies compilation for wasm32-unknown-emscripten.
Since wasm32 is a 32-bit target, this catches pointer width regressions
before merge.

Related: godot-rust#347
@ufoot ufoot marked this pull request as ready for review December 15, 2025 09:37
@Bromeon
Copy link
Member

Bromeon commented Dec 15, 2025

Hi, and thank you for the contribution!

I do not think that manually modifying the bindgen result is the right approach. We should generate Rust files for the correct target upstream in the prebuilt artifact. Unfortunately this is not as simple as opening a PR, I have to modify the generator itself and adjust some scripts.

So I wanted to do this for a while; it's also what's blocking #1275 -- which would also add a first integrated CI support for Wasm. I'll try to find some time soon... Could I notify you once that's taking shape? Since you have a game and an established workflow, it would be cool to test it 🙂

@ufoot
Copy link
Author

ufoot commented Dec 15, 2025

So I wanted to do this for a while; it's also what's blocking #1275 -- which would also add a first integrated CI support for Wasm. I'll try to find some time soon... Could I notify you once that's taking shape? Since you have a game and an established workflow, it would be cool to test it 🙂
For sure, notify me whenever you have a final/better solution for this. To be fair, I was mostly concerned about de-risking the game export to web, to make sure it was "realistic" to expect it to work.

The short version is -> it works surprisingly well, in terms of performance, the web "release" version is approaching the desktop debug version in terms of performance, which is not that bad. All inputs etc. -> worked like a charm.

I understand hacking one's way through the generated bindgen result and ignoring the 32 vs 64 difference may not be how you want to build a solid, stable library. Makes sense. But I am indeed curious about what's next.

I'll continue experimenting with web support, regardless of whether it's the right way to do it, it allows me to prototype and early-debunk possible blockers. Thanks for taking the time to consider the PR!

@ufoot
Copy link
Author

ufoot commented Dec 16, 2025

Okay, add a patch to match the latest updates in godot-rust/godot4-prebuilt#2

With that, I have been able to do locally:

christian@a6:~/Home/_/gdext$ cargo check --target wasm32-unknown-emscripten -p godot --features experimental-wasm,experimental-wasm-nothreads
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.26s

^ which, looks good.

Thanks for your feedback, I think it looks a little better now indeed, preparing both bindings 32+64, upstream in godot4-prebuilt and using the right ones here.

Will NOT work out-of-the-box as I had (as expected) to change my Cargo.toml files all over the place to point to local versions, but so far I think it looks okayish.

[EDIT] all this in multiple commits etc. -> happy to squash/rebase that, but keeping the complete history now as it may be easier to revert/hack while this is not fully baked.

Prebuilt bindings previously used a runtime workaround that stripped layout
tests when cross-compiling to 32-bit. This was fragile and didn't validate
struct layouts on 32-bit targets.

Changes:
- Add TargetPointerWidth enum and generate_rust_binding_for_target() for
  generating bindings for a specific architecture via clang's --target flag
- Export write_gdextension_headers_for_target() for godot4-prebuilt to
  generate both 32-bit and 64-bit bindings from a single host
- Update prebuilt mode to select bindings using CARGO_CFG_TARGET_POINTER_WIDTH
  (the actual cross-compile target, not the host)
- Remove strip_bindgen_layout_tests() workaround - no longer needed since
  prebuilt artifacts now include proper architecture-specific bindings.
  This should be squashed with previous commits, but until this is validated
  let's keep full history of what's there.

Requirements:
- this requires godot-rust/godot4-prebuilt#2
  or similar, will not work with current tip-of-master.
@Yarwin
Copy link
Contributor

Yarwin commented Dec 16, 2025

Hello!

First of all, I appreciate your enthusiasm. Unfortunately this PR is not up to our standards.

api-custom mode: Conditionally disable layout_tests in bindgen when host and target pointer widths differ.

Why?

    // Only disable layout tests when cross-compiling between different pointer widths.
    // Layout tests are generated based on the host architecture but validated on the target,
    // which causes failures when cross-compiling (e.g., from 64-bit host to 32-bit target)
    // because struct sizes differ. See: https://github.com/godot-rust/gdext/issues/347.

If bindgen wouldn't be able to generate proper layout tests for target platform we compile to, then cross compiling would be pretty much impossible. The bindgen FAQ explicitly shows example with 32bit platform – armv7a.

You can verify that given checks are alright on your own by cross-compiling simple file... or by comparing gdextension_interface.rs compiled with api_custom and wasm32 target against the prebuilt one.

If there is any problem with layout tests in a bindgen then it needs to be properly documented. In other words, we need to know:

  • what layout tests are wrong
  • why they are wrong
  • are only layout tests wrong or is the type wrongly generated? How llvm define given type for given platform (afaik bindgen treats llvm as a source of truth)?
  • if layout tests are wrong, then why are they wrong?

Example: #1401. Avoid turning off all the safety checks – while flawed, they have a precise meaning and are there to shield us from very nasty bugs. Ignoring layout tests might result in catastrophic UB in the future.

Don't "make problem go away", if there is any problem then it needs to be resolved.

Nonetheless, thank you for your contribution! Once @Bromeon found the time to update the prebuilt artifacts, we'd be happy to hear if the new setup works for your case 🙏.

Best,
Y

@ufoot
Copy link
Author

ufoot commented Dec 16, 2025

If bindgen wouldn't be able to generate proper layout tests for target platform we compile to, then cross compiling would be pretty much impossible. The bindgen FAQ explicitly shows example with 32bit platform – armv7a.

Thanks for the extended explanation & links. If anything, just know I am around and ready to experiment whatever acceptable solution there could be to have this 32-bit support. I'll close the PR to avoid maintainers spending time on it, as my understanding is that whatever is going to end up in master branch is pretty far from this hack, so let's reduce noise, lingering PRs are IMHO rarely a good thing to have. Have a nice day!

@ufoot ufoot closed this Dec 16, 2025
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.

Add support for 32-bit

3 participants