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.
This commit is contained in:
Andrew Kelley 2023-09-17 18:12:17 -07:00
parent 48e2ba3b3c
commit c970dbdfec

View File

@ -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;