Section 06: Struct & Tuple Layout Optimization
Context: The spec (Annex E — System Considerations) explicitly permits struct field reordering: “Struct field order in memory may differ from declaration order.” This is a non-guarantee. Rust’s repr(Rust) does exactly this — it reorders fields by alignment to minimize padding. Ori should do the same.
Reference implementations:
- Rust
compiler/rustc_abi/src/layout.rs: Fields sorted by descending alignment, then by descending size - Zig
src/Type.zig: ABI-optimal layout with explicit alignment control - C/C++: No reordering (declaration order = memory order) — this is why
#pragma packexists
Depends on: §04, §05 (need to know narrowed field sizes before computing layout).
06.1 Field Reordering Algorithm
File(s): compiler/ori_repr/src/layout/struct_layout.rs
-
Implement the field reordering algorithm:
pub fn optimize_struct_layout( fields: &[FieldInfo], repr_plan: &ReprPlan, ) -> StructRepr { // Step 1: Compute size and alignment for each field let mut field_sizes: Vec<(usize, u32, u32)> = fields.iter() .enumerate() .map(|(i, f)| { let repr = repr_plan.get_repr(f.type_idx); let size = repr.size(); let align = repr.alignment(); (i, size, align) }) .collect(); // Step 2: Sort by descending alignment, then descending size // (This minimizes padding — largest alignment first fills // naturally, smaller fields pack into trailing space) field_sizes.sort_by(|a, b| { b.2.cmp(&a.2) // alignment descending .then(b.1.cmp(&a.1)) // size descending }); // Step 3: Compute offsets let mut offset = 0u32; let mut max_align = 1u32; let mut layout_fields = Vec::with_capacity(fields.len()); for &(original_index, size, align) in &field_sizes { // Align offset offset = (offset + align - 1) & !(align - 1); layout_fields.push(FieldRepr { original_index: original_index as u32, offset, repr: repr_plan.get_repr(fields[original_index].type_idx).clone(), }); offset += size; max_align = max_align.max(align); } // Step 4: Add trailing padding for array alignment let total_size = (offset + max_align - 1) & !(max_align - 1); StructRepr { fields: layout_fields, size: total_size, align: max_align, trivial: fields.iter().all(|f| repr_plan.is_trivial(f.type_idx)), } } -
Handle zero-sized fields (unit, never):
- Zero-sized fields contribute 0 bytes and 1-byte alignment
- They still get an offset (for field access codegen) but no storage
06.2 Padding Minimization
File(s): compiler/ori_repr/src/layout/struct_layout.rs
-
Track padding bytes per struct and emit a tracing warning when padding exceeds 25% of total size:
let padding = total_size - fields.iter().map(|f| f.repr.size()).sum::<u32>(); if padding > total_size / 4 { tracing::warn!( struct_name = %name, total_size, padding, "Struct has >25% padding despite field reordering" ); } -
Consider field packing for small fields:
- Multiple
boolfields → pack into a single byte (bitfield) - Multiple
bytefields → pack contiguously (no alignment waste) Ordering(3 values) +bool(2 values) → pack into single byte
- Multiple
06.3 ABI-Stable Opt-Out
File(s): compiler/ori_repr/src/layout/struct_layout.rs
For FFI interop, users need control over memory layout.
-
Support
#repr("c")attribute:- Fields in declaration order (C struct layout rules)
- Platform-specific alignment (matches target C ABI)
- No field reordering, no narrowing of field types
-
Support
#repr("packed")attribute:- No padding between fields
- Alignment = 1
- May require unaligned loads (performance cost)
-
Support
#repr("transparent")attribute:- Struct must have exactly one non-ZST field
- Struct has same layout as that single field (no tag, no padding)
- Used for newtypes with guaranteed ABI compatibility
- Validate: error if struct has 0 or 2+ non-ZST fields
-
Support
#repr("aligned", N)attribute:- N must be a power of two (validate at parse/check time)
- Struct alignment = max(computed_alignment, N)
- May combine with
#repr("c")→CAligned(N)inReprAttribute - Must NOT combine with
#repr("packed")or#repr("transparent")(spec restriction) - Trailing padding adjusted for new alignment
-
Default behavior (no attribute):
- Reorder fields for optimal alignment
- Narrow field types based on range analysis
- Pad for alignment
06.4 Tuple Layout
File(s): compiler/ori_repr/src/layout/tuple_layout.rs
Tuples are anonymous structs. Apply the same optimization.
-
Implement
optimize_tuple_layout():- Same algorithm as struct layout
- Original index is the tuple position (0, 1, 2, …)
- Codegen must remap
.0,.1,.2to the reordered offsets
-
Ensure tuple destructuring works with reordered layout:
let (a, b, c) = tuple→ uses original indices, not memory order- Codegen translates:
a = GEP(tuple, layout.field_at_original(0).offset)
06.5 Completion Checklist
-
struct { a: bool, b: int, c: bool }uses 16 bytes not 24 (field storage = 10, rounded to max_align 8 = 16) -
struct { x: int, y: int }uses 16 bytes (no change — already optimal) -
(bool, int, bool)uses same layout as the equivalent struct -
#repr("c")structs use C layout (no reordering) -
#repr("transparent")newtype struct has same size/align as inner field -
#repr("aligned", 16)struct has alignment ≥ 16 even if fields don’t require it -
#repr("aligned", N)combined with#repr("c")works correctly -
#repr("transparent")with >1 non-ZST field produces compile error -
#repr("packed")combined with#repr("aligned")produces compile error - Field access codegen uses correct offsets from
StructRepr - Pattern matching on structs works correctly with reordered fields
-
./test-all.shgreen -
./clippy-all.shgreen -
./diagnostics/valgrind-aot.shclean
Exit Criteria: sizeof for struct { a: bool, b: int, c: bool, d: byte } is 16 bytes (i64 + i8 + i8 + i8 + 5 trailing padding to align 8) instead of 32 bytes, verified in LLVM IR. All struct-related spec tests pass.