Skip to content

C#: use LayoutKind.Sequential for single-field exposed structs (IL2CPP wasm ABI) - #49

Open
Grantim wants to merge 2 commits into
masterfrom
fix/single-field-exposed-struct-sequential-layout
Open

C#: use LayoutKind.Sequential for single-field exposed structs (IL2CPP wasm ABI)#49
Grantim wants to merge 2 commits into
masterfrom
fix/single-field-exposed-struct-sequential-layout

Conversation

@Grantim

@Grantim Grantim commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Problem

Unity WebGL (IL2CPP) builds of MeshLib's wasm bindings link with 622 wasm-ld: warning: function signature mismatch, one per C API function returning an MR::*Id; those calls trap at runtime. The ~1000 functions that take an Id by value get no warning (same i32 on both sides) but silently receive a pointer to a stack copy instead of the value.

Root cause: exposed structs are emitted with LayoutKind.Explicit + FieldOffset. IL2CPP compiles explicit-layout structs to a union with padding (union { struct { ... }; uint8_t padding[N]; }), and Clang's wasm32 ABI only flattens single-element structs to scalars. So a one-field struct becomes an aggregate on the IL2CPP side (returned through a hidden pointer, passed by pointer) while the C side uses a plain int. IL2CPP compiles sequential structs to plain C structs (e.g. struct Int32_t { int32_t ___m_value; };), which Clang flattens exactly like the C side. Multi-field structs are passed indirectly on both sides and are unaffected.

Fix

Exposed structs with exactly one non-static field are emitted with LayoutKind.Sequential and without FieldOffset attributes. For one field both layouts are trivially identical (offset 0, size and alignment of the field), so nothing else changes: no DllImport signatures, no C API. All other exposed structs keep LayoutKind.Explicit.

Verification

  • Regenerated all test outputs (parser built with gcc 14 + the llvm-22.1.8 libs, like CI). The diff is confined to the attribute lines of the single-field structs (ConvCtorExposed, ExposedLayoutB, ExposedLayoutC, ConstNonconstConflicts, NameConflictsExposed::A, DeclOrder::*, and the std::array wrappers) plus the new test function.
  • dotnet build of the three generated C# test projects: 0 warnings.
  • The ABI difference was reproduced with Unity's own emcc: IL2CPP's union shape gives (i32 sret) -> void and pass-by-pointer; the plain one-field struct gives () -> i32 and pass-by-value, matching MeshLib's C API.
  • Added test_exposed_c() covering a by-value parameter and return value of a single-field exposed struct.

Supersedes #48, which fixed the same bug at the DllImport level with more code.

Follow-up in MeshLib: bump mrbind and regenerate the C# bindings; the Unity wasm link should then be free of function signature mismatch warnings.

🤖 Generated with Claude Code

Grantim and others added 2 commits September 2, 2026 17:04
Unity's IL2CPP compiles explicit-layout structs to a union with padding, and Clang's wasm32 ABI flattens only single-element structs, so a one-field exposed struct was returned through a hidden pointer and passed by pointer at the IL2CPP call site while the C side used a plain `int` (`wasm-ld: function signature mismatch`, traps or garbage at runtime). A sequential one-field struct compiles to a plain C struct with the same ABI as the C side, and for one field both layouts are identical anyway.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds `test_exposed_c()` taking and returning a single-field exposed struct by value, and regenerates the C and C# test outputs. In the C# outputs only the struct layout attributes of the single-field structs change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant