Commit c970dbdfec

Andrew Kelley <andrew@ziglang.org>
2023-09-18 03:12:17
LLVM: cache LLVM struct field indexes
This is an optimization to avoid O(N) field index lookups. It's also nicer in terms of DRY; the only tradeoff is memory usage.
1 parent 48e2ba3
Changed files (1)
src
codegen
src/codegen/llvm.zig
@@ -825,6 +825,18 @@ pub const Object = struct {
     /// Memoizes a null `?usize` value.
     null_opt_usize: Builder.Constant,
 
+    /// When an LLVM struct type is created, an entry is inserted into this
+    /// table for every zig source field of the struct that has a corresponding
+    /// LLVM struct field. comptime fields and 0 bit fields are not included.
+    /// The value is the LLVM struct field index.
+    /// This is denormalized data.
+    struct_field_map: std.AutoHashMapUnmanaged(ZigStructField, c_uint),
+
+    const ZigStructField = struct {
+        struct_ty: InternPool.Index,
+        field_index: u32,
+    };
+
     pub const TypeMap = std.AutoHashMapUnmanaged(InternPool.Index, Builder.Type);
 
     /// This is an ArrayHashMap as opposed to a HashMap because in `flushModule` we
@@ -979,6 +991,7 @@ pub const Object = struct {
             .error_name_table = .none,
             .extern_collisions = .{},
             .null_opt_usize = .no_init,
+            .struct_field_map = .{},
         };
     }
 
@@ -994,6 +1007,7 @@ pub const Object = struct {
         self.type_map.deinit(gpa);
         self.extern_collisions.deinit(gpa);
         self.builder.deinit();
+        self.struct_field_map.deinit(gpa);
         self.* = undefined;
     }
 
@@ -3274,7 +3288,10 @@ pub const Object = struct {
 
                     var llvm_field_types = std.ArrayListUnmanaged(Builder.Type){};
                     defer llvm_field_types.deinit(o.gpa);
+                    // Although we can estimate how much capacity to add, these cannot be
+                    // relied upon because of the recursive calls to lowerType below.
                     try llvm_field_types.ensureUnusedCapacity(o.gpa, struct_obj.fields.count());
+                    try o.struct_field_map.ensureUnusedCapacity(o.gpa, @intCast(struct_obj.fields.count()));
 
                     comptime assert(struct_layout_version == 2);
                     var offset: u64 = 0;
@@ -3296,6 +3313,10 @@ pub const Object = struct {
                             o.gpa,
                             try o.builder.arrayType(padding_len, .i8),
                         );
+                        try o.struct_field_map.put(o.gpa, .{
+                            .struct_ty = t.toIntern(),
+                            .field_index = field_and_index.index,
+                        }, @intCast(llvm_field_types.items.len));
                         try llvm_field_types.append(o.gpa, try o.lowerType(field.ty));
 
                         offset += field.ty.abiSize(mod);
@@ -3319,7 +3340,10 @@ pub const Object = struct {
                 .anon_struct_type => |anon_struct_type| {
                     var llvm_field_types: std.ArrayListUnmanaged(Builder.Type) = .{};
                     defer llvm_field_types.deinit(o.gpa);
+                    // Although we can estimate how much capacity to add, these cannot be
+                    // relied upon because of the recursive calls to lowerType below.
                     try llvm_field_types.ensureUnusedCapacity(o.gpa, anon_struct_type.types.len);
+                    try o.struct_field_map.ensureUnusedCapacity(o.gpa, anon_struct_type.types.len);
 
                     comptime assert(struct_layout_version == 2);
                     var offset: u64 = 0;
@@ -3328,7 +3352,8 @@ pub const Object = struct {
                     for (
                         anon_struct_type.types.get(ip),
                         anon_struct_type.values.get(ip),
-                    ) |field_ty, field_val| {
+                        0..,
+                    ) |field_ty, field_val, field_index| {
                         if (field_val != .none or !field_ty.toType().hasRuntimeBits(mod)) continue;
 
                         const field_align = field_ty.toType().abiAlignment(mod);
@@ -3341,6 +3366,10 @@ pub const Object = struct {
                             o.gpa,
                             try o.builder.arrayType(padding_len, .i8),
                         );
+                        try o.struct_field_map.put(o.gpa, .{
+                            .struct_ty = t.toIntern(),
+                            .field_index = @intCast(field_index),
+                        }, @intCast(llvm_field_types.items.len));
                         try llvm_field_types.append(o.gpa, try o.lowerType(field_ty.toType()));
 
                         offset += field_ty.toType().abiSize(mod);
@@ -4237,9 +4266,9 @@ pub const Object = struct {
                             try o.lowerType(parent_ty),
                             parent_ptr,
                             null,
-                            if (llvmField(parent_ty, field_index, mod)) |llvm_field| &.{
+                            if (o.llvmFieldIndex(parent_ty, field_index)) |llvm_field_index| &.{
                                 try o.builder.intConst(.i32, 0),
-                                try o.builder.intConst(.i32, llvm_field.index),
+                                try o.builder.intConst(.i32, llvm_field_index),
                             } else &.{
                                 try o.builder.intConst(.i32, @intFromBool(
                                     parent_ty.hasRuntimeBitsIgnoreComptime(mod),
@@ -4396,6 +4425,13 @@ pub const Object = struct {
         try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = alignment }, &o.builder);
         if (byval) try attributes.addParamAttr(llvm_arg_i, .{ .byval = param_llvm_ty }, &o.builder);
     }
+
+    fn llvmFieldIndex(o: *Object, struct_ty: Type, field_index: usize) ?c_uint {
+        return o.struct_field_map.get(.{
+            .struct_ty = struct_ty.toIntern(),
+            .field_index = @intCast(field_index),
+        });
+    }
 };
 
 pub const DeclGen = struct {
@@ -6142,7 +6178,7 @@ pub const FuncGen = struct {
                         return self.wip.cast(.trunc, shifted_value, elem_llvm_ty, "");
                     },
                     else => {
-                        const llvm_field_index = llvmField(struct_ty, field_index, mod).?.index;
+                        const llvm_field_index = o.llvmFieldIndex(struct_ty, field_index).?;
                         return self.wip.extractValue(struct_llvm_val, &.{llvm_field_index}, "");
                     },
                 },
@@ -6169,23 +6205,25 @@ pub const FuncGen = struct {
 
         switch (struct_ty.zigTypeTag(mod)) {
             .Struct => {
-                assert(struct_ty.containerLayout(mod) != .Packed);
-                const llvm_field = llvmField(struct_ty, field_index, mod).?;
+                const layout = struct_ty.containerLayout(mod);
+                assert(layout != .Packed);
                 const struct_llvm_ty = try o.lowerType(struct_ty);
+                const llvm_field_index = o.llvmFieldIndex(struct_ty, field_index).?;
                 const field_ptr =
-                    try self.wip.gepStruct(struct_llvm_ty, struct_llvm_val, llvm_field.index, "");
+                    try self.wip.gepStruct(struct_llvm_ty, struct_llvm_val, llvm_field_index, "");
+                const alignment = struct_ty.structFieldAlign(field_index, mod);
                 const field_ptr_ty = try mod.ptrType(.{
-                    .child = llvm_field.ty.toIntern(),
+                    .child = field_ty.toIntern(),
                     .flags = .{
-                        .alignment = InternPool.Alignment.fromNonzeroByteUnits(llvm_field.alignment),
+                        .alignment = InternPool.Alignment.fromNonzeroByteUnits(alignment),
                     },
                 });
                 if (isByRef(field_ty, mod)) {
                     if (canElideLoad(self, body_tail))
                         return field_ptr;
 
-                    assert(llvm_field.alignment != 0);
-                    const field_alignment = Builder.Alignment.fromByteUnits(llvm_field.alignment);
+                    assert(alignment != 0);
+                    const field_alignment = Builder.Alignment.fromByteUnits(alignment);
                     return self.loadByRef(field_ptr, field_ty, field_alignment, .normal);
                 } else {
                     return self.load(field_ptr, field_ptr_ty);
@@ -7080,15 +7118,17 @@ pub const FuncGen = struct {
         const field_index = ty_pl.payload;
 
         const mod = o.module;
-        const llvm_field = llvmField(struct_ty, field_index, mod).?;
         const struct_llvm_ty = try o.lowerType(struct_ty);
+        const llvm_field_index = o.llvmFieldIndex(struct_ty, field_index).?;
         assert(self.err_ret_trace != .none);
         const field_ptr =
-            try self.wip.gepStruct(struct_llvm_ty, self.err_ret_trace, llvm_field.index, "");
+            try self.wip.gepStruct(struct_llvm_ty, self.err_ret_trace, llvm_field_index, "");
+        const field_alignment = struct_ty.structFieldAlign(field_index, mod);
+        const field_ty = struct_ty.structFieldType(field_index, mod);
         const field_ptr_ty = try mod.ptrType(.{
-            .child = llvm_field.ty.toIntern(),
+            .child = field_ty.toIntern(),
             .flags = .{
-                .alignment = InternPool.Alignment.fromNonzeroByteUnits(llvm_field.alignment),
+                .alignment = InternPool.Alignment.fromNonzeroByteUnits(field_alignment),
             },
         });
         return self.load(field_ptr, field_ptr_ty);
@@ -7640,8 +7680,8 @@ pub const FuncGen = struct {
         const result_val = try self.wip.extractValue(results, &.{0}, "");
         const overflow_bit = try self.wip.extractValue(results, &.{1}, "");
 
-        const result_index = llvmField(inst_ty, 0, mod).?.index;
-        const overflow_index = llvmField(inst_ty, 1, mod).?.index;
+        const result_index = o.llvmFieldIndex(inst_ty, 0).?;
+        const overflow_index = o.llvmFieldIndex(inst_ty, 1).?;
 
         if (isByRef(inst_ty, mod)) {
             const result_alignment = Builder.Alignment.fromByteUnits(inst_ty.abiAlignment(mod));
@@ -7998,8 +8038,8 @@ pub const FuncGen = struct {
 
         const overflow_bit = try self.wip.icmp(.ne, lhs, reconstructed, "");
 
-        const result_index = llvmField(dest_ty, 0, mod).?.index;
-        const overflow_index = llvmField(dest_ty, 1, mod).?.index;
+        const result_index = o.llvmFieldIndex(dest_ty, 0).?;
+        const overflow_index = o.llvmFieldIndex(dest_ty, 1).?;
 
         if (isByRef(dest_ty, mod)) {
             const result_alignment = Builder.Alignment.fromByteUnits(dest_ty.abiAlignment(mod));
@@ -9616,7 +9656,7 @@ pub const FuncGen = struct {
                         if ((try result_ty.structFieldValueComptime(mod, i)) != null) continue;
 
                         const llvm_elem = try self.resolveInst(elem);
-                        const llvm_i = llvmField(result_ty, i, mod).?.index;
+                        const llvm_i = o.llvmFieldIndex(result_ty, i).?;
                         const field_ptr =
                             try self.wip.gepStruct(llvm_result_ty, alloca_inst, llvm_i, "");
                         const field_ptr_ty = try mod.ptrType(.{
@@ -9637,7 +9677,7 @@ pub const FuncGen = struct {
                         if ((try result_ty.structFieldValueComptime(mod, i)) != null) continue;
 
                         const llvm_elem = try self.resolveInst(elem);
-                        const llvm_i = llvmField(result_ty, i, mod).?.index;
+                        const llvm_i = o.llvmFieldIndex(result_ty, i).?;
                         result = try self.wip.insertValue(result, llvm_elem, &.{llvm_i}, "");
                     }
                     return result;
@@ -10059,8 +10099,8 @@ pub const FuncGen = struct {
                 else => {
                     const struct_llvm_ty = try o.lowerPtrElemTy(struct_ty);
 
-                    if (llvmField(struct_ty, field_index, mod)) |llvm_field| {
-                        return self.wip.gepStruct(struct_llvm_ty, struct_ptr, llvm_field.index, "");
+                    if (o.llvmFieldIndex(struct_ty, field_index)) |llvm_field_index| {
+                        return self.wip.gepStruct(struct_llvm_ty, struct_ptr, llvm_field_index, "");
                     } else {
                         // If we found no index then this means this is a zero sized field at the
                         // end of the struct. Treat our struct pointer as an array of two and get
@@ -10527,90 +10567,6 @@ fn toLlvmGlobalAddressSpace(wanted_address_space: std.builtin.AddressSpace, targ
     };
 }
 
-const LlvmField = struct {
-    index: c_uint,
-    ty: Type,
-    alignment: u32,
-};
-
-/// Take into account 0 bit fields and padding. Returns null if an llvm
-/// field could not be found.
-/// This only happens if you want the field index of a zero sized field at
-/// the end of the struct.
-fn llvmField(ty: Type, field_index: usize, mod: *Module) ?LlvmField {
-    // Detects where we inserted extra padding fields so that we can skip
-    // over them in this function.
-    comptime assert(struct_layout_version == 2);
-    var offset: u64 = 0;
-    var big_align: u32 = 0;
-
-    const ip = &mod.intern_pool;
-    const struct_type = switch (ip.indexToKey(ty.toIntern())) {
-        .anon_struct_type => |tuple| {
-            var llvm_field_index: c_uint = 0;
-            for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty, field_val, i| {
-                if (field_val != .none or !field_ty.toType().hasRuntimeBits(mod)) continue;
-
-                const field_align = field_ty.toType().abiAlignment(mod);
-                big_align = @max(big_align, field_align);
-                const prev_offset = offset;
-                offset = std.mem.alignForward(u64, offset, field_align);
-
-                const padding_len = offset - prev_offset;
-                if (padding_len > 0) {
-                    llvm_field_index += 1;
-                }
-
-                if (field_index <= i) {
-                    return .{
-                        .index = llvm_field_index,
-                        .ty = field_ty.toType(),
-                        .alignment = field_align,
-                    };
-                }
-
-                llvm_field_index += 1;
-                offset += field_ty.toType().abiSize(mod);
-            }
-            return null;
-        },
-        .struct_type => |s| s,
-        else => unreachable,
-    };
-    const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
-    const layout = struct_obj.layout;
-    assert(layout != .Packed);
-
-    var llvm_field_index: c_uint = 0;
-    var it = struct_obj.runtimeFieldIterator(mod);
-    while (it.next()) |field_and_index| {
-        const field = field_and_index.field;
-        const field_align = field.alignment(mod, layout);
-        big_align = @max(big_align, field_align);
-        const prev_offset = offset;
-        offset = std.mem.alignForward(u64, offset, field_align);
-
-        const padding_len = offset - prev_offset;
-        if (padding_len > 0) {
-            llvm_field_index += 1;
-        }
-
-        if (field_index == field_and_index.index) {
-            return .{
-                .index = llvm_field_index,
-                .ty = field.ty,
-                .alignment = field_align,
-            };
-        }
-
-        llvm_field_index += 1;
-        offset += field.ty.abiSize(mod);
-    } else {
-        // We did not find an llvm field that corresponds to this zig field.
-        return null;
-    }
-}
-
 fn firstParamSRet(fn_info: InternPool.Key.FuncType, mod: *Module) bool {
     const return_type = fn_info.return_type.toType();
     if (!return_type.hasRuntimeBitsIgnoreComptime(mod)) return false;