| author | |
| committer | |
| log | 342bae02d86d9bd9f2db6ae9489021bab28595ae |
| tree | 8b50d7cffff029c135a17295c4c324603d2358b1 |
| parent | 31a2b8c3642f1240a70d78203d568051d4dbcd3f |
This is a simple starting version of the optimization described in #168
where the fields are just sorted by order of descending alignment.5 files changed, 115 insertions(+), 17 deletions(-)
src/Module.zig+41-1| ... | ... | @@ -941,7 +941,8 @@ pub const Struct = struct { |
| 941 | 941 | owner_decl: Decl.Index, |
| 942 | 942 | /// Index of the struct_decl ZIR instruction. |
| 943 | 943 | zir_index: Zir.Inst.Index, |
| 944 | ||
| 944 | /// Indexes into `fields` sorted to be most memory efficient. | |
| 945 | optimized_order: ?[*]u32 = null, | |
| 945 | 946 | layout: std.builtin.Type.ContainerLayout, |
| 946 | 947 | /// If the layout is not packed, this is the noreturn type. |
| 947 | 948 | /// If the layout is packed, this is the backing integer type of the packed struct. |
| ... | ... | @@ -1023,6 +1024,10 @@ pub const Struct = struct { |
| 1023 | 1024 | } |
| 1024 | 1025 | }; |
| 1025 | 1026 | |
| 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 | ||
| 1026 | 1031 | pub fn getFullyQualifiedName(s: *Struct, mod: *Module) ![:0]u8 { |
| 1027 | 1032 | return mod.declPtr(s.owner_decl).getFullyQualifiedName(mod); |
| 1028 | 1033 | } |
| ... | ... | @@ -1098,6 +1103,39 @@ pub const Struct = struct { |
| 1098 | 1103 | } |
| 1099 | 1104 | unreachable; // index out of bounds |
| 1100 | 1105 | } |
| 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 | } | |
| 1101 | 1139 | }; |
| 1102 | 1140 | |
| 1103 | 1141 | /// Represents the data that an enum declaration provides, when the fields |
| ... | ... | @@ -6481,6 +6519,7 @@ pub const Feature = enum { |
| 6481 | 6519 | error_return_trace, |
| 6482 | 6520 | is_named_enum_value, |
| 6483 | 6521 | error_set_has_value, |
| 6522 | field_reordering, | |
| 6484 | 6523 | }; |
| 6485 | 6524 | |
| 6486 | 6525 | pub fn backendSupportsFeature(mod: Module, feature: Feature) bool { |
| ... | ... | @@ -6493,5 +6532,6 @@ pub fn backendSupportsFeature(mod: Module, feature: Feature) bool { |
| 6493 | 6532 | .error_return_trace => mod.comp.bin_file.options.use_llvm, |
| 6494 | 6533 | .is_named_enum_value => mod.comp.bin_file.options.use_llvm, |
| 6495 | 6534 | .error_set_has_value => mod.comp.bin_file.options.use_llvm, |
| 6535 | .field_reordering => mod.comp.bin_file.options.use_llvm, | |
| 6496 | 6536 | }; |
| 6497 | 6537 | } |
src/Sema.zig+36| ... | ... | @@ -29919,6 +29919,42 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void { |
| 29919 | 29919 | ); |
| 29920 | 29920 | return sema.failWithOwnedErrorMsg(msg); |
| 29921 | 29921 | } |
| 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 | } | |
| 29922 | 29958 | } |
| 29923 | 29959 | // otherwise it's a tuple; no need to resolve anything |
| 29924 | 29960 | } |
src/codegen/llvm.zig+15-15| ... | ... | @@ -2083,15 +2083,15 @@ pub const Object = struct { |
| 2083 | 2083 | comptime assert(struct_layout_version == 2); |
| 2084 | 2084 | var offset: u64 = 0; |
| 2085 | 2085 | |
| 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; | |
| 2089 | 2089 | const field_size = field.ty.abiSize(target); |
| 2090 | 2090 | const field_align = field.alignment(target, layout); |
| 2091 | 2091 | const field_offset = std.mem.alignForwardGeneric(u64, offset, field_align); |
| 2092 | 2092 | offset = field_offset + field_size; |
| 2093 | 2093 | |
| 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]); | |
| 2095 | 2095 | defer gpa.free(field_name); |
| 2096 | 2096 | |
| 2097 | 2097 | try di_fields.append(gpa, dib.createMemberType( |
| ... | ... | @@ -2985,9 +2985,9 @@ pub const DeclGen = struct { |
| 2985 | 2985 | var big_align: u32 = 1; |
| 2986 | 2986 | var any_underaligned_fields = false; |
| 2987 | 2987 | |
| 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; | |
| 2991 | 2991 | const field_align = field.alignment(target, struct_obj.layout); |
| 2992 | 2992 | const field_ty_align = field.ty.abiAlignment(target); |
| 2993 | 2993 | any_underaligned_fields = any_underaligned_fields or |
| ... | ... | @@ -3714,9 +3714,9 @@ pub const DeclGen = struct { |
| 3714 | 3714 | var big_align: u32 = 0; |
| 3715 | 3715 | var need_unnamed = false; |
| 3716 | 3716 | |
| 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; | |
| 3720 | 3720 | const field_align = field.alignment(target, struct_obj.layout); |
| 3721 | 3721 | big_align = @max(big_align, field_align); |
| 3722 | 3722 | const prev_offset = offset; |
| ... | ... | @@ -3732,7 +3732,7 @@ pub const DeclGen = struct { |
| 3732 | 3732 | |
| 3733 | 3733 | const field_llvm_val = try dg.lowerValue(.{ |
| 3734 | 3734 | .ty = field.ty, |
| 3735 | .val = field_vals[i], | |
| 3735 | .val = field_vals[field_and_index.index], | |
| 3736 | 3736 | }); |
| 3737 | 3737 | |
| 3738 | 3738 | need_unnamed = need_unnamed or dg.isUnnamedType(field.ty, field_llvm_val); |
| ... | ... | @@ -10354,9 +10354,9 @@ fn llvmFieldIndex( |
| 10354 | 10354 | assert(layout != .Packed); |
| 10355 | 10355 | |
| 10356 | 10356 | 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; | |
| 10360 | 10360 | const field_align = field.alignment(target, layout); |
| 10361 | 10361 | big_align = @max(big_align, field_align); |
| 10362 | 10362 | const prev_offset = offset; |
| ... | ... | @@ -10367,7 +10367,7 @@ fn llvmFieldIndex( |
| 10367 | 10367 | llvm_field_index += 1; |
| 10368 | 10368 | } |
| 10369 | 10369 | |
| 10370 | if (field_index <= i) { | |
| 10370 | if (field_index == field_and_index.index) { | |
| 10371 | 10371 | ptr_pl_buf.* = .{ |
| 10372 | 10372 | .data = .{ |
| 10373 | 10373 | .pointee_type = field.ty, |
src/type.zig+5-1| ... | ... | @@ -5741,10 +5741,14 @@ pub const Type = extern union { |
| 5741 | 5741 | target: Target, |
| 5742 | 5742 | |
| 5743 | 5743 | pub fn next(it: *StructOffsetIterator) ?FieldOffset { |
| 5744 | const i = it.field; | |
| 5744 | var i = it.field; | |
| 5745 | 5745 | if (it.struct_obj.fields.count() <= i) |
| 5746 | 5746 | return null; |
| 5747 | 5747 | |
| 5748 | if (it.struct_obj.optimized_order) |some| { | |
| 5749 | i = some[i]; | |
| 5750 | if (i == Module.Struct.omitted_field) return null; | |
| 5751 | } | |
| 5748 | 5752 | const field = it.struct_obj.fields.values()[i]; |
| 5749 | 5753 | it.field += 1; |
| 5750 | 5754 |
test/behavior/struct.zig+18| ... | ... | @@ -1555,3 +1555,21 @@ test "optional generic function label struct field" { |
| 1555 | 1555 | }; |
| 1556 | 1556 | try expect((Options{}).isFoo.?(u8) == 123); |
| 1557 | 1557 | } |
| 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 | } |