Rust and WGSL can describe structures that look identical while assigning them different binary layouts. Matthew Keeter turns that dangerous CPU/GPU boundary into a unit-tested contract, using Naga for the shader side and Facet reflection for the Rust side.
The motivating mismatch is small but destructive:
- WGSL pads each row of a
mat3x3fto 16 bytes, making the matrix 48 bytes - Rust packs
[[f32; 3]; 3]into 36 bytes - Every field after the matrix is consequently read at the wrong offset
- In compute-shader VMs, malformed inputs can leave GPU threads spinning until reboot
The generic checker compares:
- Overall structure size
- Every field name, offset, and size
- Fields in both directions, catching additions on either side
- The fixed prefix before a trailing WGSL runtime-sized array
Facet is derived only under cfg(test), so the production binary pays no reflection cost. More importantly, checking field sizes identifies the field that caused the drift instead of merely reporting the next displaced field.
The broader lesson is to test binary interfaces as schemas. When two languages independently describe the same bytes, successful serialization on one side is not proof that the consumer agrees about their meaning.