Skip to content

Commit 9973069

Browse files
committed
Add 32-bit architecture support
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).
1 parent c3475af commit 9973069

File tree

2 files changed

+89
-1
lines changed

2 files changed

+89
-1
lines changed

godot-bindings/src/header_gen.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,22 @@ pub(crate) fn generate_rust_binding(in_h_path: &Path, out_rs_path: &Path) {
2323
// If you have an idea to address this without too invasive changes, please comment on that issue.
2424
let cargo_cfg = bindgen::CargoCallbacks::new().rerun_on_header_files(false);
2525

26+
// Only disable layout tests when cross-compiling between different pointer widths.
27+
// Layout tests are generated based on the host architecture but validated on the target,
28+
// which causes failures when cross-compiling (e.g., from 64-bit host to 32-bit target)
29+
// because struct sizes differ. See: https://github.com/godot-rust/gdext/issues/347.
30+
let host_pointer_width = std::mem::size_of::<usize>() * 8;
31+
let target_pointer_width: usize = env::var("CARGO_CFG_TARGET_POINTER_WIDTH")
32+
.ok()
33+
.and_then(|s| s.parse().ok())
34+
.unwrap_or(host_pointer_width);
35+
let enable_layout_tests = host_pointer_width == target_pointer_width;
36+
2637
let builder = bindgen::Builder::default()
2738
.header(c_header_path)
2839
.parse_callbacks(Box::new(cargo_cfg))
2940
.prepend_enum_name(false)
41+
.layout_tests(enable_layout_tests)
3042
// Bindgen can generate wrong size checks for types defined as `__attribute__((aligned(__alignof__(struct {...}))))`,
3143
// which is how clang defines max_align_t: https://clang.llvm.org/doxygen/____stddef__max__align__t_8h_source.html.
3244
// Size checks seems to be fine on all the targets but `wasm32-unknown-emscripten`, disallowing web builds.

godot-bindings/src/lib.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,87 @@ mod depend_on_prebuilt {
130130
watch.record("write_header_h");
131131

132132
let rs_contents = prebuilt::load_gdextension_header_rs();
133-
std::fs::write(rs_path, rs_contents.as_ref())
133+
134+
// Prebuilt bindings are generated for 64-bit. When targeting 32-bit, we must strip
135+
// the layout assertions since they validate 64-bit struct sizes which don't match.
136+
// For native 64-bit builds, we keep the safety checks intact.
137+
// See: https://github.com/godot-rust/gdext/issues/347
138+
let target_pointer_width = std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH")
139+
.expect("CARGO_CFG_TARGET_POINTER_WIDTH not set");
140+
let rs_contents = if target_pointer_width == "32" {
141+
strip_bindgen_layout_tests(rs_contents.as_ref())
142+
} else {
143+
rs_contents.to_string()
144+
};
145+
146+
std::fs::write(rs_path, rs_contents)
134147
.unwrap_or_else(|e| panic!("failed to write gdextension_interface.rs: {e}"));
135148
watch.record("write_header_rs");
136149
}
137150

151+
/// Removes bindgen-generated layout test assertion blocks from the source code.
152+
///
153+
/// Bindgen generates compile-time assertions in blocks like:
154+
/// ```ignore
155+
/// const _: () = {
156+
/// ["Size of SomeStruct"][::std::mem::size_of::<SomeStruct>() - 48usize];
157+
/// ["Alignment of SomeStruct"][::std::mem::align_of::<SomeStruct>() - 8usize];
158+
/// };
159+
/// ```
160+
///
161+
/// These cause compile-time errors when cross-compiling between 32-bit and 64-bit targets
162+
/// because struct sizes differ based on pointer width.
163+
/// See: https://github.com/godot-rust/gdext/issues/347
164+
fn strip_bindgen_layout_tests(source: &str) -> String {
165+
let mut result = String::with_capacity(source.len());
166+
let mut in_const_block = false;
167+
let mut brace_depth = 0;
168+
169+
for line in source.lines() {
170+
let trimmed = line.trim();
171+
172+
// Detect start of a const assertion block.
173+
// Pattern: `const _: () = {` or with #[allow(...)] attributes before.
174+
if !in_const_block && trimmed.starts_with("const _: () = {") {
175+
in_const_block = true;
176+
brace_depth = 1;
177+
continue;
178+
}
179+
180+
// Skip lines with #[allow(...)] that precede const blocks.
181+
if !in_const_block
182+
&& trimmed.starts_with("#[allow(")
183+
&& trimmed.contains("clippy::unnecessary_operation")
184+
{
185+
continue;
186+
}
187+
188+
if in_const_block {
189+
// Track brace depth to find the end of the block.
190+
for ch in trimmed.chars() {
191+
match ch {
192+
'{' => brace_depth += 1,
193+
'}' => {
194+
brace_depth -= 1;
195+
if brace_depth == 0 {
196+
in_const_block = false;
197+
break;
198+
}
199+
}
200+
_ => {}
201+
}
202+
}
203+
continue; // Skip all lines inside the const block.
204+
}
205+
206+
// Keep all other lines.
207+
result.push_str(line);
208+
result.push('\n');
209+
}
210+
211+
result
212+
}
213+
138214
pub(crate) fn get_godot_version() -> GodotVersion {
139215
let version: Vec<&str> = prebuilt::GODOT_VERSION_STRING
140216
.split('.')

0 commit comments

Comments
 (0)