struct field reordering (minimizing padding)

This commit is contained in:
2026-08-09 14:12:25 +02:00
parent 729488e702
commit 6688822de2
4 changed files with 111 additions and 15 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ roadmap and milestone history.
- narrow immutable zero-terminated byte pointer/slice conversion to `*c_char` / `?*c_char` without general `u8`/`c_char` interchange - narrow immutable zero-terminated byte pointer/slice conversion to `*c_char` / `?*c_char` without general `u8`/`c_char` interchange
- optionals with `null`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps - optionals with `null`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
- nominal distinct types with explicit scalar backing conversion during construction and explicit scalar backing extraction, native enums with optional explicit integer backing and explicit backing-to-scalar casts, contextual enum literals, and imported C enums as target-backed integer aliases - nominal distinct types with explicit scalar backing conversion during construction and explicit scalar backing extraction, native enums with optional explicit integer backing and explicit backing-to-scalar casts, contextual enum literals, and imported C enums as target-backed integer aliases
- source-order native structs, opaque nominal records with `Name :: opaque`, complete `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)` - compiler-reordered native structs with fields laid out by decreasing alignment (declaration order breaks ties and remains the reflection/diagnostic order), opaque nominal records with `Name :: opaque`, complete source-order `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them - named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction - void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
- native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids - native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids
+38 -5
View File
@@ -1117,6 +1117,15 @@ emit_instruction_stream :: proc(
} else if item.kind == .Range && arg_index == 2 { } else if item.kind == .Range && arg_index == 2 {
element_type = types.BOOL element_type = types.BOOL
} }
aggregate_index := arg_index
if item.kind == .Struct {
aggregate_index = types.physical_field_index(
&emitter.module.types,
instruction.type,
arg_index,
emitter.module.target,
)
}
final := arg_index == total-1 final := arg_index == total-1
if final { if final {
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s ", instruction_index, type_name) fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s ", instruction_index, type_name)
@@ -1134,7 +1143,7 @@ emit_instruction_stream :: proc(
} else { } else {
write_constant(&emitter.builder, i64(item.sentinel), element_type, &emitter.module.types) write_constant(&emitter.builder, i64(item.sentinel), element_type, &emitter.module.types)
} }
fmt.sbprintf(&emitter.builder, ", %d\n", arg_index) fmt.sbprintf(&emitter.builder, ", %d\n", aggregate_index)
} }
case .Null: case .Null:
item, ok := types.node(&emitter.module.types, instruction.type) item, ok := types.node(&emitter.module.types, instruction.type)
@@ -1334,13 +1343,19 @@ emit_instruction_stream :: proc(
emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference") emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference")
continue continue
} }
physical_index := types.physical_field_index(
&emitter.module.types,
base_type,
field_index,
emitter.module.target,
)
if types.is_union(base_type, &emitter.module.types) { if types.is_union(base_type, &emitter.module.types) {
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 %d\n", instruction_index, instruction.a, types.union_payload_offset(base_type, &emitter.module.types, emitter.module.target)) fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 %d\n", instruction_index, instruction.a, types.union_payload_offset(base_type, &emitter.module.types, emitter.module.target))
} else { } else {
fmt.sbprintf( fmt.sbprintf(
&emitter.builder, &emitter.builder,
" %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n", " %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n",
instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index, instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, physical_index,
) )
} }
case .Load: case .Load:
@@ -2593,12 +2608,30 @@ emit_types :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, " }\n") strings.write_string(&emitter.builder, " }\n")
continue continue
} }
fields := types.fields_for(&emitter.module.types, id)
strings.write_string(&emitter.builder, "{ ") strings.write_string(&emitter.builder, "{ ")
for field, field_index in types.fields_for(&emitter.module.types, id) { previous_index := -1
if field_index > 0 { for physical_index in 0..<len(fields) {
logical_index := -1
for _, candidate_index in fields {
if previous_index >= 0 &&
!types.field_layout_precedes(
&emitter.module.types, id, previous_index, candidate_index, emitter.module.target,
) {
continue
}
if logical_index < 0 ||
types.field_layout_precedes(
&emitter.module.types, id, candidate_index, logical_index, emitter.module.target,
) {
logical_index = candidate_index
}
}
if physical_index > 0 {
strings.write_string(&emitter.builder, ", ") strings.write_string(&emitter.builder, ", ")
} }
strings.write_string(&emitter.builder, llvm_type(field.type, &emitter.module.types)) strings.write_string(&emitter.builder, llvm_type(fields[logical_index].type, &emitter.module.types))
previous_index = logical_index
} }
strings.write_string(&emitter.builder, " }\n") strings.write_string(&emitter.builder, " }\n")
} }
+49 -5
View File
@@ -636,6 +636,43 @@ fields_for :: proc(store: ^Store, value: Type) -> []Field {
return store.fields[start:end] return store.fields[start:end]
} }
field_layout_precedes :: proc(
store: ^Store,
value: Type,
left, right: int,
selected := target.DEFAULT,
) -> bool {
fields := fields_for(store, value)
item, ok := node(store, value)
if !ok || item.kind != .Struct || item.c_layout ||
left < 0 || left >= len(fields) || right < 0 || right >= len(fields) {
return left < right
}
left_alignment := alignment_of(fields[left].type, store, selected)
right_alignment := alignment_of(fields[right].type, store, selected)
return left_alignment > right_alignment ||
(left_alignment == right_alignment && left < right)
}
physical_field_index :: proc(
store: ^Store,
value: Type,
logical_index: int,
selected := target.DEFAULT,
) -> int {
fields := fields_for(store, value)
if logical_index < 0 || logical_index >= len(fields) {
return logical_index
}
result := 0
for _, other_index in fields {
if field_layout_precedes(store, value, other_index, logical_index, selected) {
result += 1
}
}
return result
}
params_for :: proc(store: ^Store, value: Type) -> []Field { params_for :: proc(store: ^Store, value: Type) -> []Field {
item, ok := node(store, value) item, ok := node(store, value)
if !ok || item.kind != .Function { if !ok || item.kind != .Function {
@@ -1709,11 +1746,18 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
} }
offset: u64 offset: u64
max_align: u64 = 1 max_align: u64 = 1
for field in fields_for(store, value) { if !item.c_layout {
field_align := u64(alignment_of(field.type, store, selected)) for field in fields_for(store, value) {
offset = (offset+field_align-1)/field_align*field_align offset += size(field.type, store, selected)
offset += size(field.type, store, selected) max_align = max(max_align, u64(alignment_of(field.type, store, selected)))
max_align = max(max_align, field_align) }
} else {
for field in fields_for(store, value) {
field_align := u64(alignment_of(field.type, store, selected))
offset = (offset+field_align-1)/field_align*field_align
offset += size(field.type, store, selected)
max_align = max(max_align, field_align)
}
} }
return (offset+max_align-1)/max_align*max_align return (offset+max_align-1)/max_align*max_align
case .Union: case .Union:
+23 -4
View File
@@ -1887,6 +1887,18 @@ Bool_Last :: struct {
flag bool flag bool
} }
Optimized :: struct {
prefix u8
wide u64
suffix u8
}
Source_Order_C :: c_struct {
prefix c_uchar
wide c_ulong
suffix c_uchar
}
Opaque :: opaque Opaque :: opaque
Color :: enum { Color :: enum {
red red
@@ -1937,12 +1949,19 @@ main func() i32 {
if (sizeof!(bool) != 1) return 15 if (sizeof!(bool) != 1) return 15
if (sizeof!(Bool_First) != 2) return 16 if (sizeof!(Bool_First) != 2) return 16
if (sizeof!(Bool_Last) != 2) return 17 if (sizeof!(Bool_Last) != 2) return 17
optimized Optimized := Optimized{prefix = 40, wide = 72623859790382856, suffix = 41}
if (sizeof!(Optimized) != 16) return 18
if (alignof!(Optimized) != 8) return 19
if (sizeof!(Source_Order_C) != 24) return 20
if (optimized.prefix != 40) return 21
if (optimized.wide != 72623859790382856) return 22
if (optimized.suffix != 41) return 23
first :: make_bool_first() first :: make_bool_first()
if (!first.flag) return 18 if (!first.flag) return 24
if (first.byte != 41) return 19 if (first.byte != 41) return 25
last :: make_bool_last() last :: make_bool_last()
if (!last.flag) return 20 if (!last.flag) return 26
if (last.byte != 42) return 21 if (last.byte != 42) return 27
return 0 return 0
} }
` `