authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-16 21:19:48-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-01-16 21:19:48-05:00
loge646becd04c37fbaaa654f6419dd9f5e85d9f210
tree01e5a1535ebec3ab5d2476396efb9469d75f29ce
parent37424fd11a79801e18a121ee7e86730012b0c55e
parentb2c85464bef931c8263b50b40697a584600ee69e
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14336 from Vexu/field-reorder

Sema: automatically optimize order of struct fields

6 files changed, 116 insertions(+), 18 deletions(-)

lib/std/os/linux/io_uring.zig+1-1
......@@ -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+41-1
......@@ -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
64866525pub 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+36
......@@ -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+15-15
......@@ -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+5-1
......@@ -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+18
......@@ -1555,3 +1555,21 @@ test "optional generic function label struct field" {
15551555 };
15561556 try expect((Options{}).isFoo.?(u8) == 123);
15571557}
1558
1559test "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}