Skip to content

Commit e646bec

Browse files
authored
Merge pull request #14336 from Vexu/field-reorder
Sema: automatically optimize order of struct fields
2 parents 37424fd + b2c8546 commit e646bec

File tree

6 files changed

+116
-18
lines changed

6 files changed

+116
-18
lines changed

lib/std/os/linux/io_uring.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,7 @@ pub const IO_Uring = struct {
954954
pub fn register_files_update(self: *IO_Uring, offset: u32, fds: []const os.fd_t) !void {
955955
assert(self.fd >= 0);
956956

957-
const FilesUpdate = struct {
957+
const FilesUpdate = extern struct {
958958
offset: u32,
959959
resv: u32,
960960
fds: u64 align(8),

src/Module.zig

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,8 @@ pub const Struct = struct {
941941
owner_decl: Decl.Index,
942942
/// Index of the struct_decl ZIR instruction.
943943
zir_index: Zir.Inst.Index,
944-
944+
/// Indexes into `fields` sorted to be most memory efficient.
945+
optimized_order: ?[*]u32 = null,
945946
layout: std.builtin.Type.ContainerLayout,
946947
/// If the layout is not packed, this is the noreturn type.
947948
/// If the layout is packed, this is the backing integer type of the packed struct.
@@ -1023,6 +1024,10 @@ pub const Struct = struct {
10231024
}
10241025
};
10251026

1027+
/// Used in `optimized_order` to indicate field that is not present in the
1028+
/// runtime version of the struct.
1029+
pub const omitted_field = std.math.maxInt(u32);
1030+
10261031
pub fn getFullyQualifiedName(s: *Struct, mod: *Module) ![:0]u8 {
10271032
return mod.declPtr(s.owner_decl).getFullyQualifiedName(mod);
10281033
}
@@ -1098,6 +1103,39 @@ pub const Struct = struct {
10981103
}
10991104
unreachable; // index out of bounds
11001105
}
1106+
1107+
pub const RuntimeFieldIterator = struct {
1108+
struct_obj: *const Struct,
1109+
index: u32 = 0,
1110+
1111+
pub const FieldAndIndex = struct {
1112+
field: Field,
1113+
index: u32,
1114+
};
1115+
1116+
pub fn next(it: *RuntimeFieldIterator) ?FieldAndIndex {
1117+
while (true) {
1118+
var i = it.index;
1119+
it.index += 1;
1120+
if (it.struct_obj.fields.count() <= i)
1121+
return null;
1122+
1123+
if (it.struct_obj.optimized_order) |some| {
1124+
i = some[i];
1125+
if (i == Module.Struct.omitted_field) return null;
1126+
}
1127+
const field = it.struct_obj.fields.values()[i];
1128+
1129+
if (!field.is_comptime and field.ty.hasRuntimeBits()) {
1130+
return FieldAndIndex{ .index = i, .field = field };
1131+
}
1132+
}
1133+
}
1134+
};
1135+
1136+
pub fn runtimeFieldIterator(s: *const Struct) RuntimeFieldIterator {
1137+
return .{ .struct_obj = s };
1138+
}
11011139
};
11021140

11031141
/// Represents the data that an enum declaration provides, when the fields
@@ -6481,6 +6519,7 @@ pub const Feature = enum {
64816519
error_return_trace,
64826520
is_named_enum_value,
64836521
error_set_has_value,
6522+
field_reordering,
64846523
};
64856524

64866525
pub fn backendSupportsFeature(mod: Module, feature: Feature) bool {
@@ -6493,5 +6532,6 @@ pub fn backendSupportsFeature(mod: Module, feature: Feature) bool {
64936532
.error_return_trace => mod.comp.bin_file.options.use_llvm,
64946533
.is_named_enum_value => mod.comp.bin_file.options.use_llvm,
64956534
.error_set_has_value => mod.comp.bin_file.options.use_llvm,
6535+
.field_reordering => mod.comp.bin_file.options.use_llvm,
64966536
};
64976537
}

src/Sema.zig

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29919,6 +29919,42 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {
2991929919
);
2992029920
return sema.failWithOwnedErrorMsg(msg);
2992129921
}
29922+
29923+
if (struct_obj.layout == .Auto and sema.mod.backendSupportsFeature(.field_reordering)) {
29924+
const optimized_order = blk: {
29925+
const decl = sema.mod.declPtr(struct_obj.owner_decl);
29926+
var decl_arena = decl.value_arena.?.promote(sema.mod.gpa);
29927+
defer decl.value_arena.?.* = decl_arena.state;
29928+
const decl_arena_allocator = decl_arena.allocator();
29929+
29930+
break :blk try decl_arena_allocator.alloc(u32, struct_obj.fields.count());
29931+
};
29932+
29933+
for (struct_obj.fields.values()) |field, i| {
29934+
optimized_order[i] = if (field.ty.hasRuntimeBits())
29935+
@intCast(u32, i)
29936+
else
29937+
Module.Struct.omitted_field;
29938+
}
29939+
29940+
const AlignSortContext = struct {
29941+
struct_obj: *Module.Struct,
29942+
sema: *Sema,
29943+
29944+
fn lessThan(ctx: @This(), a: u32, b: u32) bool {
29945+
if (a == Module.Struct.omitted_field) return false;
29946+
if (b == Module.Struct.omitted_field) return true;
29947+
const target = ctx.sema.mod.getTarget();
29948+
return ctx.struct_obj.fields.values()[a].ty.abiAlignment(target) >
29949+
ctx.struct_obj.fields.values()[b].ty.abiAlignment(target);
29950+
}
29951+
};
29952+
std.sort.sort(u32, optimized_order, AlignSortContext{
29953+
.struct_obj = struct_obj,
29954+
.sema = sema,
29955+
}, AlignSortContext.lessThan);
29956+
struct_obj.optimized_order = optimized_order.ptr;
29957+
}
2992229958
}
2992329959
// otherwise it's a tuple; no need to resolve anything
2992429960
}

src/codegen/llvm.zig

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2083,15 +2083,15 @@ pub const Object = struct {
20832083
comptime assert(struct_layout_version == 2);
20842084
var offset: u64 = 0;
20852085

2086-
for (fields.values()) |field, i| {
2087-
if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
2088-
2086+
var it = ty.castTag(.@"struct").?.data.runtimeFieldIterator();
2087+
while (it.next()) |field_and_index| {
2088+
const field = field_and_index.field;
20892089
const field_size = field.ty.abiSize(target);
20902090
const field_align = field.alignment(target, layout);
20912091
const field_offset = std.mem.alignForwardGeneric(u64, offset, field_align);
20922092
offset = field_offset + field_size;
20932093

2094-
const field_name = try gpa.dupeZ(u8, fields.keys()[i]);
2094+
const field_name = try gpa.dupeZ(u8, fields.keys()[field_and_index.index]);
20952095
defer gpa.free(field_name);
20962096

20972097
try di_fields.append(gpa, dib.createMemberType(
@@ -2985,9 +2985,9 @@ pub const DeclGen = struct {
29852985
var big_align: u32 = 1;
29862986
var any_underaligned_fields = false;
29872987

2988-
for (struct_obj.fields.values()) |field| {
2989-
if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
2990-
2988+
var it = struct_obj.runtimeFieldIterator();
2989+
while (it.next()) |field_and_index| {
2990+
const field = field_and_index.field;
29912991
const field_align = field.alignment(target, struct_obj.layout);
29922992
const field_ty_align = field.ty.abiAlignment(target);
29932993
any_underaligned_fields = any_underaligned_fields or
@@ -3714,9 +3714,9 @@ pub const DeclGen = struct {
37143714
var big_align: u32 = 0;
37153715
var need_unnamed = false;
37163716

3717-
for (struct_obj.fields.values()) |field, i| {
3718-
if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
3719-
3717+
var it = struct_obj.runtimeFieldIterator();
3718+
while (it.next()) |field_and_index| {
3719+
const field = field_and_index.field;
37203720
const field_align = field.alignment(target, struct_obj.layout);
37213721
big_align = @max(big_align, field_align);
37223722
const prev_offset = offset;
@@ -3732,7 +3732,7 @@ pub const DeclGen = struct {
37323732

37333733
const field_llvm_val = try dg.lowerValue(.{
37343734
.ty = field.ty,
3735-
.val = field_vals[i],
3735+
.val = field_vals[field_and_index.index],
37363736
});
37373737

37383738
need_unnamed = need_unnamed or dg.isUnnamedType(field.ty, field_llvm_val);
@@ -10354,9 +10354,9 @@ fn llvmFieldIndex(
1035410354
assert(layout != .Packed);
1035510355

1035610356
var llvm_field_index: c_uint = 0;
10357-
for (ty.structFields().values()) |field, i| {
10358-
if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
10359-
10357+
var it = ty.castTag(.@"struct").?.data.runtimeFieldIterator();
10358+
while (it.next()) |field_and_index| {
10359+
const field = field_and_index.field;
1036010360
const field_align = field.alignment(target, layout);
1036110361
big_align = @max(big_align, field_align);
1036210362
const prev_offset = offset;
@@ -10367,7 +10367,7 @@ fn llvmFieldIndex(
1036710367
llvm_field_index += 1;
1036810368
}
1036910369

10370-
if (field_index <= i) {
10370+
if (field_index == field_and_index.index) {
1037110371
ptr_pl_buf.* = .{
1037210372
.data = .{
1037310373
.pointee_type = field.ty,

src/type.zig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5741,10 +5741,14 @@ pub const Type = extern union {
57415741
target: Target,
57425742

57435743
pub fn next(it: *StructOffsetIterator) ?FieldOffset {
5744-
const i = it.field;
5744+
var i = it.field;
57455745
if (it.struct_obj.fields.count() <= i)
57465746
return null;
57475747

5748+
if (it.struct_obj.optimized_order) |some| {
5749+
i = some[i];
5750+
if (i == Module.Struct.omitted_field) return null;
5751+
}
57485752
const field = it.struct_obj.fields.values()[i];
57495753
it.field += 1;
57505754

test/behavior/struct.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,3 +1555,21 @@ test "optional generic function label struct field" {
15551555
};
15561556
try expect((Options{}).isFoo.?(u8) == 123);
15571557
}
1558+
1559+
test "struct fields get automatically reordered" {
1560+
if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest; // TODO
1561+
1562+
const S1 = struct {
1563+
a: u32,
1564+
b: u32,
1565+
c: bool,
1566+
d: bool,
1567+
};
1568+
const S2 = struct {
1569+
a: u32,
1570+
b: bool,
1571+
c: u32,
1572+
d: bool,
1573+
};
1574+
try expect(@sizeOf(S1) == @sizeOf(S2));
1575+
}

0 commit comments

Comments
 (0)