LLVM: implement debug info for structs

This involved some significant reworking in order to introduce the
concept of "forward declarations" to the system to break dependency
loops.

The `lowerDebugType` function now takes an `enum { full, fwd }` and is moved
from `DeclGen` to `Object` so that it can be called from `flushModule`.

`DITypeMap` is now an `ArrayHashMap` instead of a `HashMap` so that we can
iterate over the entries in `flushModule` and finalize the forward decl
DITypes into full DITypes.

`DITypeMap` now stores `AnnotatedDITypePtr` values instead of
`*DIType` values. This is an abstraction around a `usize` which assumes
the pointers will be at least 2 bytes aligned and uses the least
significant bit to store whether it is forward decl or a fully resolved
debug info type.

`lowerDebugTypeImpl` is extracted out from `lowerDebugType` and it has a
mechanism for completing a forward decl DIType to a fully resolved one.
The function now contains lowering for struct types. Closes #11095.

There is a workaround for struct types which have not had
`resolveFieldTypes` called in Sema, even by the time `flushModule` is
called. This is a deficiency of Sema that should be addressed, and the
workaround removed. I think Sema needs a new mechanism to queue up type
resolution work instead of doing it in-line, so that it does not cause
false dependency loops. We already have one failing behavior test
because of a false dependency loop.
This commit is contained in:
Andrew Kelley
2022-03-15 23:32:02 -07:00
parent cfc31b5bbd
commit c3663f2617
+915 -780
View File
@@ -160,6 +160,7 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 {
}
pub const Object = struct {
gpa: Allocator,
llvm_module: *const llvm.Module,
di_builder: ?*llvm.DIBuilder,
/// One of these mappings:
@@ -171,6 +172,7 @@ pub const Object = struct {
context: *const llvm.Context,
target_machine: *const llvm.TargetMachine,
target_data: *const llvm.TargetData,
target: std.Target,
/// Ideally we would use `llvm_module.getNamedFunction` to go from *Decl to LLVM function,
/// but that has some downsides:
/// * we have to compute the fully qualified name every time we want to do the lookup
@@ -202,11 +204,13 @@ pub const Object = struct {
std.hash_map.default_max_load_percentage,
);
pub const DITypeMap = std.HashMapUnmanaged(
/// This is an ArrayHashMap as opposed to a HashMap because in `flushModule` we
/// want to iterate over it while adding entries to it.
pub const DITypeMap = std.ArrayHashMapUnmanaged(
Type,
*llvm.DIType,
Type.HashContext64,
std.hash_map.default_max_load_percentage,
AnnotatedDITypePtr,
Type.HashContext32,
true,
);
pub fn create(gpa: Allocator, options: link.Options) !*Object {
@@ -335,6 +339,7 @@ pub const Object = struct {
llvm_module.setModuleDataLayout(target_data);
return Object{
.gpa = gpa,
.llvm_module = llvm_module,
.di_map = .{},
.di_builder = opt_di_builder,
@@ -342,6 +347,7 @@ pub const Object = struct {
.context = context,
.target_machine = target_machine,
.target_data = target_data,
.target = options.target,
.decl_map = .{},
.type_map = .{},
.type_map_arena = std.heap.ArenaAllocator.init(gpa),
@@ -437,7 +443,25 @@ pub const Object = struct {
pub fn flushModule(self: *Object, comp: *Compilation) !void {
try self.genErrorNameTable(comp);
if (self.di_builder) |dib| dib.finalize();
if (self.di_builder) |dib| {
// When lowering debug info for pointers, we emitted the element types as
// forward decls. Now we must go flesh those out.
// Here we iterate over a hash map while modifying it but it is OK because
// we never add or remove entries during this loop.
var i: usize = 0;
while (i < self.di_type_map.count()) : (i += 1) {
const value_ptr = &self.di_type_map.values()[i];
const annotated = value_ptr.*;
if (!annotated.isFwdOnly()) continue;
const entry: Object.DITypeMap.Entry = .{
.key_ptr = &self.di_type_map.keys()[i],
.value_ptr = value_ptr,
};
_ = try self.lowerDebugTypeImpl(entry, .full, annotated.toDIType());
}
dib.finalize();
}
if (comp.verbose_llvm_ir) {
self.llvm_module.dump();
@@ -503,7 +527,7 @@ pub const Object = struct {
}
pub fn updateFunc(
self: *Object,
o: *Object,
module: *Module,
func: *Module.Fn,
air: Air,
@@ -512,8 +536,8 @@ pub const Object = struct {
const decl = func.owner_decl;
var dg: DeclGen = .{
.context = self.context,
.object = self,
.context = o.context,
.object = o,
.module = module,
.decl = decl,
.err_msg = null,
@@ -584,7 +608,7 @@ pub const Object = struct {
llvm_func.getValueName(),
di_file.?,
line_number,
try dg.lowerDebugType(decl.ty),
try o.lowerDebugType(decl.ty, .full),
is_internal_linkage,
true, // is definition
line_number + func.lbrace_line, // scope line
@@ -631,7 +655,7 @@ pub const Object = struct {
};
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
try self.updateDeclExports(module, decl, decl_exports);
try o.updateDeclExports(module, decl, decl_exports);
}
pub fn updateDecl(self: *Object, module: *Module, decl: *Module.Decl) !void {
@@ -773,6 +797,856 @@ pub const Object = struct {
gop.value_ptr.* = di_file.toNode();
return di_file;
}
const DebugResolveStatus = enum { fwd, full };
/// In the implementation of this function, it is required to store a forward decl
/// into `gop` before making any recursive calls (even directly).
fn lowerDebugType(
o: *Object,
ty: Type,
resolve: DebugResolveStatus,
) Allocator.Error!*llvm.DIType {
const gpa = o.gpa;
// Be careful not to reference this `gop` variable after any recursive calls
// to `lowerDebugType`.
const gop = try o.di_type_map.getOrPut(gpa, ty);
if (gop.found_existing) {
const annotated = gop.value_ptr.*;
const di_type = annotated.toDIType();
if (!annotated.isFwdOnly() or resolve == .fwd) {
return di_type;
}
const entry: Object.DITypeMap.Entry = .{
.key_ptr = gop.key_ptr,
.value_ptr = gop.value_ptr,
};
return o.lowerDebugTypeImpl(entry, resolve, di_type);
}
errdefer assert(o.di_type_map.orderedRemove(ty));
// The Type memory is ephemeral; since we want to store a longer-lived
// reference, we need to copy it here.
gop.key_ptr.* = try ty.copy(o.type_map_arena.allocator());
const entry: Object.DITypeMap.Entry = .{
.key_ptr = gop.key_ptr,
.value_ptr = gop.value_ptr,
};
return o.lowerDebugTypeImpl(entry, resolve, null);
}
/// This is a helper function used by `lowerDebugType`.
fn lowerDebugTypeImpl(
o: *Object,
gop: Object.DITypeMap.Entry,
resolve: DebugResolveStatus,
opt_fwd_decl: ?*llvm.DIType,
) Allocator.Error!*llvm.DIType {
const ty = gop.key_ptr.*;
const gpa = o.gpa;
const target = o.target;
const dib = o.di_builder.?;
switch (ty.zigTypeTag()) {
.Void, .NoReturn => {
const di_type = dib.createBasicType("void", 0, DW.ATE.signed);
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_type);
return di_type;
},
.Int => {
const info = ty.intInfo(target);
assert(info.bits != 0);
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const dwarf_encoding: c_uint = switch (info.signedness) {
.signed => DW.ATE.signed,
.unsigned => DW.ATE.unsigned,
};
const di_type = dib.createBasicType(name, info.bits, dwarf_encoding);
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_type);
return di_type;
},
.Enum => {
const owner_decl = ty.getOwnerDecl();
if (!ty.hasRuntimeBitsIgnoreComptime()) {
const enum_di_ty = try o.makeEmptyNamespaceDIType(owner_decl);
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
// means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(enum_di_ty));
return enum_di_ty;
}
const field_names = ty.enumFields().keys();
const enumerators = try gpa.alloc(*llvm.DIEnumerator, field_names.len);
defer gpa.free(enumerators);
var buf_field_index: Value.Payload.U32 = .{
.base = .{ .tag = .enum_field_index },
.data = undefined,
};
const field_index_val = Value.initPayload(&buf_field_index.base);
for (field_names) |field_name, i| {
const field_name_z = try gpa.dupeZ(u8, field_name);
defer gpa.free(field_name_z);
buf_field_index.data = @intCast(u32, i);
var buf_u64: Value.Payload.U64 = undefined;
const field_int_val = field_index_val.enumToInt(ty, &buf_u64);
// See https://github.com/ziglang/zig/issues/645
const field_int = field_int_val.toSignedInt();
enumerators[i] = dib.createEnumerator(field_name_z, field_int);
}
const di_file = try o.getDIFile(gpa, owner_decl.src_namespace.file_scope);
const di_scope = try o.namespaceToDebugScope(owner_decl.src_namespace);
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
var buffer: Type.Payload.Bits = undefined;
const int_ty = ty.intTagType(&buffer);
const enum_di_ty = dib.createEnumerationType(
di_scope,
name,
di_file,
owner_decl.src_node + 1,
ty.abiSize(target) * 8,
ty.abiAlignment(target) * 8,
enumerators.ptr,
@intCast(c_int, enumerators.len),
try o.lowerDebugType(int_ty, .full),
"",
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(enum_di_ty));
return enum_di_ty;
},
.Float => {
const bits = ty.floatBits(target);
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const di_type = dib.createBasicType(name, bits, DW.ATE.float);
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_type);
return di_type;
},
.Bool => {
const di_type = dib.createBasicType("bool", 1, DW.ATE.boolean);
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_type);
return di_type;
},
.Pointer => {
// Normalize everything that the debug info does not represent.
const ptr_info = ty.ptrInfo().data;
if (ptr_info.sentinel != null or
ptr_info.@"addrspace" != .generic or
ptr_info.bit_offset != 0 or
ptr_info.host_size != 0 or
ptr_info.@"allowzero" or
!ptr_info.mutable or
ptr_info.@"volatile" or
ptr_info.size == .Many or ptr_info.size == .C or
!ptr_info.pointee_type.hasRuntimeBitsIgnoreComptime())
{
var payload: Type.Payload.Pointer = .{
.data = .{
.pointee_type = ptr_info.pointee_type,
.sentinel = null,
.@"align" = ptr_info.@"align",
.@"addrspace" = .generic,
.bit_offset = 0,
.host_size = 0,
.@"allowzero" = false,
.mutable = true,
.@"volatile" = false,
.size = switch (ptr_info.size) {
.Many, .C, .One => .One,
.Slice => .Slice,
},
},
};
if (!ptr_info.pointee_type.hasRuntimeBitsIgnoreComptime()) {
payload.data.pointee_type = Type.anyopaque;
}
const bland_ptr_ty = Type.initPayload(&payload.base);
const ptr_di_ty = try o.lowerDebugType(bland_ptr_ty, resolve);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(ptr_di_ty));
return ptr_di_ty;
}
if (ty.isSlice()) {
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const ptr_ty = ty.slicePtrFieldType(&buf);
const len_ty = Type.usize;
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const di_file: ?*llvm.DIFile = null;
const line = 0;
const compile_unit_scope = o.di_compile_unit.?.toScope();
const fwd_decl = opt_fwd_decl orelse blk: {
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
compile_unit_scope,
di_file,
line,
);
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
if (resolve == .fwd) return fwd_decl;
break :blk fwd_decl;
};
const ptr_size = ptr_ty.abiSize(target);
const ptr_align = ptr_ty.abiAlignment(target);
const len_size = len_ty.abiSize(target);
const len_align = len_ty.abiAlignment(target);
var offset: u64 = 0;
offset += ptr_size;
offset = std.mem.alignForwardGeneric(u64, offset, len_align);
const len_offset = offset;
const fields: [2]*llvm.DIType = .{
dib.createMemberType(
fwd_decl.toScope(),
"ptr",
di_file,
line,
ptr_size * 8, // size in bits
ptr_align * 8, // align in bits
0, // offset in bits
0, // flags
try o.lowerDebugType(ptr_ty, .full),
),
dib.createMemberType(
fwd_decl.toScope(),
"len",
di_file,
line,
len_size * 8, // size in bits
len_align * 8, // align in bits
len_offset * 8, // offset in bits
0, // flags
try o.lowerDebugType(len_ty, .full),
),
};
const full_di_ty = dib.createStructType(
compile_unit_scope,
name.ptr,
di_file,
line,
ty.abiSize(target) * 8, // size in bits
ty.abiAlignment(target) * 8, // align in bits
0, // flags
null, // derived from
&fields,
fields.len,
0, // run time lang
null, // vtable holder
"", // unique id
);
dib.replaceTemporary(fwd_decl, full_di_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(full_di_ty));
return full_di_ty;
}
const elem_di_ty = try o.lowerDebugType(ptr_info.pointee_type, .fwd);
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const ptr_di_ty = dib.createPointerType(
elem_di_ty,
target.cpu.arch.ptrBitWidth(),
ty.ptrAlignment(target) * 8,
name,
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(ptr_di_ty));
return ptr_di_ty;
},
.Opaque => {
if (ty.tag() == .anyopaque) {
const di_ty = dib.createBasicType("anyopaque", 0, DW.ATE.signed);
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_ty);
return di_ty;
}
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const owner_decl = ty.getOwnerDecl();
const opaque_di_ty = dib.createForwardDeclType(
DW.TAG.structure_type,
name,
try o.namespaceToDebugScope(owner_decl.src_namespace),
try o.getDIFile(gpa, owner_decl.src_namespace.file_scope),
owner_decl.src_node + 1,
);
// The recursive call to `lowerDebugType` va `namespaceToDebugScope`
// means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(opaque_di_ty));
return opaque_di_ty;
},
.Array => {
const array_di_ty = dib.createArrayType(
ty.abiSize(target) * 8,
ty.abiAlignment(target) * 8,
try o.lowerDebugType(ty.childType(), .full),
@intCast(c_int, ty.arrayLen()),
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(array_di_ty));
return array_di_ty;
},
.Vector => {
const vector_di_ty = dib.createVectorType(
ty.abiSize(target) * 8,
ty.abiAlignment(target) * 8,
try o.lowerDebugType(ty.childType(), .full),
ty.vectorLen(),
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(vector_di_ty));
return vector_di_ty;
},
.Optional => {
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
var buf: Type.Payload.ElemType = undefined;
const child_ty = ty.optionalChild(&buf);
if (!child_ty.hasRuntimeBitsIgnoreComptime()) {
const di_ty = dib.createBasicType(name, 1, DW.ATE.boolean);
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_ty);
return di_ty;
}
if (ty.isPtrLikeOptional()) {
const ptr_di_ty = try o.lowerDebugType(child_ty, resolve);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(ptr_di_ty));
return ptr_di_ty;
}
const di_file: ?*llvm.DIFile = null;
const line = 0;
const compile_unit_scope = o.di_compile_unit.?.toScope();
const fwd_decl = opt_fwd_decl orelse blk: {
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
compile_unit_scope,
di_file,
line,
);
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
if (resolve == .fwd) return fwd_decl;
break :blk fwd_decl;
};
const non_null_ty = Type.bool;
const payload_size = child_ty.abiSize(target);
const payload_align = child_ty.abiAlignment(target);
const non_null_size = non_null_ty.abiSize(target);
const non_null_align = non_null_ty.abiAlignment(target);
var offset: u64 = 0;
offset += payload_size;
offset = std.mem.alignForwardGeneric(u64, offset, non_null_align);
const non_null_offset = offset;
const fields: [2]*llvm.DIType = .{
dib.createMemberType(
fwd_decl.toScope(),
"data",
di_file,
line,
payload_size * 8, // size in bits
payload_align * 8, // align in bits
0, // offset in bits
0, // flags
try o.lowerDebugType(child_ty, .full),
),
dib.createMemberType(
fwd_decl.toScope(),
"some",
di_file,
line,
non_null_size * 8, // size in bits
non_null_align * 8, // align in bits
non_null_offset * 8, // offset in bits
0, // flags
try o.lowerDebugType(non_null_ty, .full),
),
};
const full_di_ty = dib.createStructType(
compile_unit_scope,
name.ptr,
di_file,
line,
ty.abiSize(target) * 8, // size in bits
ty.abiAlignment(target) * 8, // align in bits
0, // flags
null, // derived from
&fields,
fields.len,
0, // run time lang
null, // vtable holder
"", // unique id
);
dib.replaceTemporary(fwd_decl, full_di_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(full_di_ty));
return full_di_ty;
},
.ErrorUnion => {
const err_set_ty = ty.errorUnionSet();
const payload_ty = ty.errorUnionPayload();
if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
const err_set_di_ty = try o.lowerDebugType(err_set_ty, .full);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(err_set_di_ty));
return err_set_di_ty;
}
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const di_file: ?*llvm.DIFile = null;
const line = 0;
const compile_unit_scope = o.di_compile_unit.?.toScope();
const fwd_decl = opt_fwd_decl orelse blk: {
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
compile_unit_scope,
di_file,
line,
);
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
if (resolve == .fwd) return fwd_decl;
break :blk fwd_decl;
};
const err_set_size = err_set_ty.abiSize(target);
const err_set_align = err_set_ty.abiAlignment(target);
const payload_size = payload_ty.abiSize(target);
const payload_align = payload_ty.abiAlignment(target);
var offset: u64 = 0;
offset += err_set_size;
offset = std.mem.alignForwardGeneric(u64, offset, payload_align);
const payload_offset = offset;
const fields: [2]*llvm.DIType = .{
dib.createMemberType(
fwd_decl.toScope(),
"tag",
di_file,
line,
err_set_size * 8, // size in bits
err_set_align * 8, // align in bits
0, // offset in bits
0, // flags
try o.lowerDebugType(err_set_ty, .full),
),
dib.createMemberType(
fwd_decl.toScope(),
"value",
di_file,
line,
payload_size * 8, // size in bits
payload_align * 8, // align in bits
payload_offset * 8, // offset in bits
0, // flags
try o.lowerDebugType(payload_ty, .full),
),
};
const full_di_ty = dib.createStructType(
compile_unit_scope,
name.ptr,
di_file,
line,
ty.abiSize(target) * 8, // size in bits
ty.abiAlignment(target) * 8, // align in bits
0, // flags
null, // derived from
&fields,
fields.len,
0, // run time lang
null, // vtable holder
"", // unique id
);
dib.replaceTemporary(fwd_decl, full_di_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(full_di_ty));
return full_di_ty;
},
.ErrorSet => {
// TODO make this a proper enum with all the error codes in it.
// will need to consider how to take incremental compilation into account.
const di_ty = dib.createBasicType("anyerror", 16, DW.ATE.unsigned);
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_ty);
return di_ty;
},
.Struct => {
const compile_unit_scope = o.di_compile_unit.?.toScope();
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
if (ty.castTag(.@"struct")) |payload| {
const struct_obj = payload.data;
if (struct_obj.layout == .Packed) {
var buf: Type.Payload.Bits = undefined;
const info = struct_obj.packedIntegerType(target, &buf).intInfo(target);
const dwarf_encoding: c_uint = switch (info.signedness) {
.signed => DW.ATE.signed,
.unsigned => DW.ATE.unsigned,
};
const di_ty = dib.createBasicType(name, info.bits, dwarf_encoding);
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_ty);
return di_ty;
}
}
const fwd_decl = opt_fwd_decl orelse blk: {
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
compile_unit_scope,
null, // file
0, // line
);
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
if (resolve == .fwd) return fwd_decl;
break :blk fwd_decl;
};
if (ty.isTupleOrAnonStruct()) {
const tuple = ty.tupleFields();
var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{};
defer di_fields.deinit(gpa);
try di_fields.ensureUnusedCapacity(gpa, tuple.types.len);
comptime assert(struct_layout_version == 2);
var offset: u64 = 0;
for (tuple.types) |field_ty, i| {
const field_val = tuple.values[i];
if (field_val.tag() != .unreachable_value) continue;
const field_size = field_ty.abiSize(target);
const field_align = field_ty.abiAlignment(target);
const field_offset = std.mem.alignForwardGeneric(u64, offset, field_align);
offset = field_offset + field_size;
const field_name = if (ty.castTag(.anon_struct)) |payload|
try gpa.dupeZ(u8, payload.data.names[i])
else
try std.fmt.allocPrintZ(gpa, "{d}", .{i});
defer gpa.free(field_name);
try di_fields.append(gpa, dib.createMemberType(
fwd_decl.toScope(),
field_name,
null, // file
0, // line
field_size * 8, // size in bits
field_align * 8, // align in bits
field_offset * 8, // offset in bits
0, // flags
try o.lowerDebugType(field_ty, .full),
));
}
const full_di_ty = dib.createStructType(
compile_unit_scope,
name.ptr,
null, // file
0, // line
ty.abiSize(target) * 8, // size in bits
ty.abiAlignment(target) * 8, // align in bits
0, // flags
null, // derived from
di_fields.items.ptr,
@intCast(c_int, di_fields.items.len),
0, // run time lang
null, // vtable holder
"", // unique id
);
dib.replaceTemporary(fwd_decl, full_di_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(full_di_ty));
return full_di_ty;
}
if (ty.castTag(.@"struct")) |payload| {
const struct_obj = payload.data;
if (!struct_obj.haveFieldTypes()) {
// TODO: improve the frontend to populate this struct.
// For now we treat it as a zero bit type.
const owner_decl = ty.getOwnerDecl();
const struct_di_ty = try o.makeEmptyNamespaceDIType(owner_decl);
dib.replaceTemporary(fwd_decl, struct_di_ty);
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
// means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(struct_di_ty));
return struct_di_ty;
}
}
if (!ty.hasRuntimeBitsIgnoreComptime()) {
const owner_decl = ty.getOwnerDecl();
const struct_di_ty = try o.makeEmptyNamespaceDIType(owner_decl);
dib.replaceTemporary(fwd_decl, struct_di_ty);
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
// means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(struct_di_ty));
return struct_di_ty;
}
const fields = ty.structFields();
var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{};
defer di_fields.deinit(gpa);
try di_fields.ensureUnusedCapacity(gpa, fields.count());
comptime assert(struct_layout_version == 2);
var offset: u64 = 0;
for (fields.values()) |field, i| {
if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue;
const field_size = field.ty.abiSize(target);
const field_align = field.normalAlignment(target);
const field_offset = std.mem.alignForwardGeneric(u64, offset, field_align);
offset = field_offset + field_size;
const field_name = try gpa.dupeZ(u8, fields.keys()[i]);
defer gpa.free(field_name);
try di_fields.append(gpa, dib.createMemberType(
fwd_decl.toScope(),
field_name,
null, // file
0, // line
field_size * 8, // size in bits
field_align * 8, // align in bits
field_offset * 8, // offset in bits
0, // flags
try o.lowerDebugType(field.ty, .full),
));
}
const full_di_ty = dib.createStructType(
compile_unit_scope,
name.ptr,
null, // file
0, // line
ty.abiSize(target) * 8, // size in bits
ty.abiAlignment(target) * 8, // align in bits
0, // flags
null, // derived from
di_fields.items.ptr,
@intCast(c_int, di_fields.items.len),
0, // run time lang
null, // vtable holder
"", // unique id
);
dib.replaceTemporary(fwd_decl, full_di_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(full_di_ty));
return full_di_ty;
},
.Union => {
const owner_decl = ty.getOwnerDecl();
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const fwd_decl = opt_fwd_decl orelse blk: {
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
o.di_compile_unit.?.toScope(),
null, // file
0, // line
);
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
if (resolve == .fwd) return fwd_decl;
break :blk fwd_decl;
};
const TODO_implement_this = true; // TODO
if (TODO_implement_this or !ty.hasRuntimeBitsIgnoreComptime()) {
const union_di_ty = try o.makeEmptyNamespaceDIType(owner_decl);
dib.replaceTemporary(fwd_decl, union_di_ty);
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
// means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(union_di_ty));
return union_di_ty;
}
@panic("TODO debug info type for union");
//const gop = try o.type_map.getOrPut(gpa, ty);
//if (gop.found_existing) return gop.value_ptr.*;
//// The Type memory is ephemeral; since we want to store a longer-lived
//// reference, we need to copy it here.
//gop.key_ptr.* = try ty.copy(o.type_map_arena.allocator());
//const layout = ty.unionGetLayout(target);
//const union_obj = ty.cast(Type.Payload.Union).?.data;
//if (layout.payload_size == 0) {
// const enum_tag_llvm_ty = try dg.llvmType(union_obj.tag_ty);
// gop.value_ptr.* = enum_tag_llvm_ty;
// return enum_tag_llvm_ty;
//}
//const name = try union_obj.getFullyQualifiedName(gpa);
//defer gpa.free(name);
//const llvm_union_ty = dg.context.structCreateNamed(name);
//gop.value_ptr.* = llvm_union_ty; // must be done before any recursive calls
//const aligned_field = union_obj.fields.values()[layout.most_aligned_field];
//const llvm_aligned_field_ty = try dg.llvmType(aligned_field.ty);
//const llvm_payload_ty = ty: {
// if (layout.most_aligned_field_size == layout.payload_size) {
// break :ty llvm_aligned_field_ty;
// }
// const padding_len = @intCast(c_uint, layout.payload_size - layout.most_aligned_field_size);
// const fields: [2]*const llvm.Type = .{
// llvm_aligned_field_ty,
// dg.context.intType(8).arrayType(padding_len),
// };
// break :ty dg.context.structType(&fields, fields.len, .True);
//};
//if (layout.tag_size == 0) {
// var llvm_fields: [1]*const llvm.Type = .{llvm_payload_ty};
// llvm_union_ty.structSetBody(&llvm_fields, llvm_fields.len, .False);
// return llvm_union_ty;
//}
//const enum_tag_llvm_ty = try dg.llvmType(union_obj.tag_ty);
//// Put the tag before or after the payload depending on which one's
//// alignment is greater.
//var llvm_fields: [3]*const llvm.Type = undefined;
//var llvm_fields_len: c_uint = 2;
//if (layout.tag_align >= layout.payload_align) {
// llvm_fields = .{ enum_tag_llvm_ty, llvm_payload_ty, undefined };
//} else {
// llvm_fields = .{ llvm_payload_ty, enum_tag_llvm_ty, undefined };
//}
//// Insert padding to make the LLVM struct ABI size match the Zig union ABI size.
//if (layout.padding != 0) {
// llvm_fields[2] = dg.context.intType(8).arrayType(layout.padding);
// llvm_fields_len = 3;
//}
//llvm_union_ty.structSetBody(&llvm_fields, llvm_fields_len, .False);
//return llvm_union_ty;
},
.Fn => {
const fn_info = ty.fnInfo();
const sret = firstParamSRet(fn_info, target);
var param_di_types = std.ArrayList(*llvm.DIType).init(gpa);
defer param_di_types.deinit();
// Return type goes first.
const di_ret_ty = if (sret or !fn_info.return_type.hasRuntimeBitsIgnoreComptime())
Type.void
else
fn_info.return_type;
try param_di_types.append(try o.lowerDebugType(di_ret_ty, .full));
if (sret) {
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = fn_info.return_type,
};
const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
try param_di_types.append(try o.lowerDebugType(ptr_ty, .full));
}
for (fn_info.param_types) |param_ty| {
if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue;
if (isByRef(param_ty)) {
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = param_ty,
};
const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
try param_di_types.append(try o.lowerDebugType(ptr_ty, .full));
} else {
try param_di_types.append(try o.lowerDebugType(param_ty, .full));
}
}
const fn_di_ty = dib.createSubroutineType(
param_di_types.items.ptr,
@intCast(c_int, param_di_types.items.len),
0,
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try o.di_type_map.put(gpa, ty, AnnotatedDITypePtr.initFull(fn_di_ty));
return fn_di_ty;
},
.ComptimeInt => unreachable,
.ComptimeFloat => unreachable,
.Type => unreachable,
.Undefined => unreachable,
.Null => unreachable,
.EnumLiteral => unreachable,
.BoundFn => @panic("TODO remove BoundFn from the language"),
.Frame => @panic("TODO implement lowerDebugType for Frame types"),
.AnyFrame => @panic("TODO implement lowerDebugType for AnyFrame types"),
}
}
fn namespaceToDebugScope(o: *Object, namespace: *const Module.Namespace) !*llvm.DIScope {
if (namespace.parent == null) {
const di_file = try o.getDIFile(o.gpa, namespace.file_scope);
return di_file.toScope();
}
const di_type = try o.lowerDebugType(namespace.ty, .fwd);
return di_type.toScope();
}
/// This is to be used instead of void for debug info types, to avoid tripping
/// Assertion `!isa<DIType>(Scope) && "shouldn't make a namespace scope for a type"'
/// when targeting CodeView (Windows).
fn makeEmptyNamespaceDIType(o: *Object, decl: *const Module.Decl) !*llvm.DIType {
const fields: [0]*llvm.DIType = .{};
return o.di_builder.?.createStructType(
try o.namespaceToDebugScope(decl.src_namespace),
decl.name, // TODO use fully qualified name
try o.getDIFile(o.gpa, decl.src_namespace.file_scope),
decl.src_line + 1,
0, // size in bits
0, // align in bits
0, // flags
null, // derived from
undefined, // TODO should be able to pass &fields,
fields.len,
0, // run time lang
null, // vtable holder
"", // unique id
);
}
};
pub const DeclGen = struct {
@@ -867,7 +1741,7 @@ pub const DeclGen = struct {
global.getValueName(),
di_file,
line_number,
try dg.lowerDebugType(decl.ty),
try dg.object.lowerDebugType(decl.ty, .full),
is_internal_linkage,
);
@@ -2016,772 +2890,6 @@ pub const DeclGen = struct {
}
}
fn lowerDebugType(dg: *DeclGen, ty: Type) Allocator.Error!*llvm.DIType {
const gpa = dg.gpa;
// Be careful not to reference this `gop` variable after any recursive calls
// to `lowerDebugType`.
const gop = try dg.object.di_type_map.getOrPut(gpa, ty);
if (gop.found_existing) return gop.value_ptr.*;
errdefer assert(dg.object.di_type_map.remove(ty));
// The Type memory is ephemeral; since we want to store a longer-lived
// reference, we need to copy it here.
gop.key_ptr.* = try ty.copy(dg.object.type_map_arena.allocator());
const target = dg.module.getTarget();
const dib = dg.object.di_builder.?;
switch (ty.zigTypeTag()) {
.Void, .NoReturn => {
gop.value_ptr.* = dib.createBasicType("void", 0, DW.ATE.signed);
return gop.value_ptr.*;
},
.Int => {
const info = ty.intInfo(target);
assert(info.bits != 0);
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const dwarf_encoding: c_uint = switch (info.signedness) {
.signed => DW.ATE.signed,
.unsigned => DW.ATE.unsigned,
};
gop.value_ptr.* = dib.createBasicType(name, info.bits, dwarf_encoding);
return gop.value_ptr.*;
},
.Enum => {
const owner_decl = ty.getOwnerDecl();
if (!ty.hasRuntimeBitsIgnoreComptime()) {
const enum_di_ty = try dg.makeEmptyNamespaceDIType(owner_decl);
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
// means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, enum_di_ty);
return enum_di_ty;
}
const field_names = ty.enumFields().keys();
const enumerators = try gpa.alloc(*llvm.DIEnumerator, field_names.len);
defer gpa.free(enumerators);
var buf_field_index: Value.Payload.U32 = .{
.base = .{ .tag = .enum_field_index },
.data = undefined,
};
const field_index_val = Value.initPayload(&buf_field_index.base);
for (field_names) |field_name, i| {
const field_name_z = try gpa.dupeZ(u8, field_name);
defer gpa.free(field_name_z);
buf_field_index.data = @intCast(u32, i);
var buf_u64: Value.Payload.U64 = undefined;
const field_int_val = field_index_val.enumToInt(ty, &buf_u64);
// See https://github.com/ziglang/zig/issues/645
const field_int = field_int_val.toSignedInt();
enumerators[i] = dib.createEnumerator(field_name_z, field_int);
}
const di_file = try dg.object.getDIFile(gpa, owner_decl.src_namespace.file_scope);
const di_scope = try dg.namespaceToDebugScope(owner_decl.src_namespace);
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
var buffer: Type.Payload.Bits = undefined;
const int_ty = ty.intTagType(&buffer);
const enum_di_ty = dib.createEnumerationType(
di_scope,
name,
di_file,
owner_decl.src_node + 1,
ty.abiSize(target) * 8,
ty.abiAlignment(target) * 8,
enumerators.ptr,
@intCast(c_int, enumerators.len),
try lowerDebugType(dg, int_ty),
"",
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, enum_di_ty);
return enum_di_ty;
},
.Float => {
const bits = ty.floatBits(target);
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
gop.value_ptr.* = dib.createBasicType(name, bits, DW.ATE.float);
return gop.value_ptr.*;
},
.Bool => {
gop.value_ptr.* = dib.createBasicType("bool", 1, DW.ATE.boolean);
return gop.value_ptr.*;
},
.Pointer => {
// Normalize everything that the debug info does not represent.
const ptr_info = ty.ptrInfo().data;
if (ptr_info.sentinel != null or
ptr_info.@"addrspace" != .generic or
ptr_info.bit_offset != 0 or
ptr_info.host_size != 0 or
ptr_info.@"allowzero" or
!ptr_info.mutable or
ptr_info.@"volatile" or
ptr_info.size == .Many or ptr_info.size == .C or
!ptr_info.pointee_type.hasRuntimeBitsIgnoreComptime())
{
var payload: Type.Payload.Pointer = .{
.data = .{
.pointee_type = ptr_info.pointee_type,
.sentinel = null,
.@"align" = ptr_info.@"align",
.@"addrspace" = .generic,
.bit_offset = 0,
.host_size = 0,
.@"allowzero" = false,
.mutable = true,
.@"volatile" = false,
.size = switch (ptr_info.size) {
.Many, .C, .One => .One,
.Slice => .Slice,
},
},
};
if (!ptr_info.pointee_type.hasRuntimeBitsIgnoreComptime()) {
payload.data.pointee_type = Type.anyopaque;
}
const bland_ptr_ty = Type.initPayload(&payload.base);
const ptr_di_ty = try dg.lowerDebugType(bland_ptr_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, ptr_di_ty);
return ptr_di_ty;
}
if (ty.isSlice()) {
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const ptr_ty = ty.slicePtrFieldType(&buf);
const len_ty = Type.usize;
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const di_file: ?*llvm.DIFile = null;
const line = 0;
const compile_unit_scope = dg.object.di_compile_unit.?.toScope();
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
compile_unit_scope,
di_file,
line,
);
gop.value_ptr.* = fwd_decl;
const ptr_size = ptr_ty.abiSize(target);
const ptr_align = ptr_ty.abiAlignment(target);
const len_size = len_ty.abiSize(target);
const len_align = len_ty.abiAlignment(target);
var offset: u64 = 0;
offset += ptr_size;
offset = std.mem.alignForwardGeneric(u64, offset, len_align);
const len_offset = offset;
const fields: [2]*llvm.DIType = .{
dib.createMemberType(
fwd_decl.toScope(),
"ptr",
di_file,
line,
ptr_size * 8, // size in bits
ptr_align * 8, // align in bits
0, // offset in bits
0, // flags
try dg.lowerDebugType(ptr_ty),
),
dib.createMemberType(
fwd_decl.toScope(),
"len",
di_file,
line,
len_size * 8, // size in bits
len_align * 8, // align in bits
len_offset * 8, // offset in bits
0, // flags
try dg.lowerDebugType(len_ty),
),
};
const replacement_di_ty = dib.createStructType(
compile_unit_scope,
name.ptr,
di_file,
line,
ty.abiSize(target) * 8, // size in bits
ty.abiAlignment(target) * 8, // align in bits
0, // flags
null, // derived from
&fields,
fields.len,
0, // run time lang
null, // vtable holder
"", // unique id
);
dib.replaceTemporary(fwd_decl, replacement_di_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, replacement_di_ty);
return replacement_di_ty;
}
const elem_di_ty = try lowerDebugType(dg, ptr_info.pointee_type);
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const ptr_di_ty = dib.createPointerType(
elem_di_ty,
target.cpu.arch.ptrBitWidth(),
ty.ptrAlignment(target) * 8,
name,
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, ptr_di_ty);
return ptr_di_ty;
},
.Opaque => {
if (ty.tag() == .anyopaque) {
gop.value_ptr.* = dib.createBasicType("anyopaque", 0, DW.ATE.signed);
return gop.value_ptr.*;
}
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const owner_decl = ty.getOwnerDecl();
const opaque_di_ty = dib.createForwardDeclType(
DW.TAG.structure_type,
name,
try dg.namespaceToDebugScope(owner_decl.src_namespace),
try dg.object.getDIFile(gpa, owner_decl.src_namespace.file_scope),
owner_decl.src_node + 1,
);
// The recursive call to `lowerDebugType` va `namespaceToDebugScope`
// means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, opaque_di_ty);
return opaque_di_ty;
},
.Array => {
const array_di_ty = dib.createArrayType(
ty.abiSize(target) * 8,
ty.abiAlignment(target) * 8,
try lowerDebugType(dg, ty.childType()),
@intCast(c_int, ty.arrayLen()),
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, array_di_ty);
return array_di_ty;
},
.Vector => {
const vector_di_ty = dib.createVectorType(
ty.abiSize(target) * 8,
ty.abiAlignment(target) * 8,
try lowerDebugType(dg, ty.childType()),
ty.vectorLen(),
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, vector_di_ty);
return vector_di_ty;
},
.Optional => {
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
var buf: Type.Payload.ElemType = undefined;
const child_ty = ty.optionalChild(&buf);
if (!child_ty.hasRuntimeBitsIgnoreComptime()) {
gop.value_ptr.* = dib.createBasicType(name, 1, DW.ATE.boolean);
return gop.value_ptr.*;
}
if (ty.isPtrLikeOptional()) {
const ptr_di_ty = try dg.lowerDebugType(child_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, ptr_di_ty);
return ptr_di_ty;
}
const di_file: ?*llvm.DIFile = null;
const line = 0;
const compile_unit_scope = dg.object.di_compile_unit.?.toScope();
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
compile_unit_scope,
di_file,
line,
);
gop.value_ptr.* = fwd_decl;
const non_null_ty = Type.bool;
const payload_size = child_ty.abiSize(target);
const payload_align = child_ty.abiAlignment(target);
const non_null_size = non_null_ty.abiSize(target);
const non_null_align = non_null_ty.abiAlignment(target);
var offset: u64 = 0;
offset += payload_size;
offset = std.mem.alignForwardGeneric(u64, offset, non_null_align);
const non_null_offset = offset;
const fields: [2]*llvm.DIType = .{
dib.createMemberType(
fwd_decl.toScope(),
"data",
di_file,
line,
payload_size * 8, // size in bits
payload_align * 8, // align in bits
0, // offset in bits
0, // flags
try dg.lowerDebugType(child_ty),
),
dib.createMemberType(
fwd_decl.toScope(),
"some",
di_file,
line,
non_null_size * 8, // size in bits
non_null_align * 8, // align in bits
non_null_offset * 8, // offset in bits
0, // flags
try dg.lowerDebugType(non_null_ty),
),
};
const replacement_di_ty = dib.createStructType(
compile_unit_scope,
name.ptr,
di_file,
line,
ty.abiSize(target) * 8, // size in bits
ty.abiAlignment(target) * 8, // align in bits
0, // flags
null, // derived from
&fields,
fields.len,
0, // run time lang
null, // vtable holder
"", // unique id
);
dib.replaceTemporary(fwd_decl, replacement_di_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, replacement_di_ty);
return replacement_di_ty;
},
.ErrorUnion => {
const err_set_ty = ty.errorUnionSet();
const payload_ty = ty.errorUnionPayload();
if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
const err_set_di_ty = try dg.lowerDebugType(err_set_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, err_set_di_ty);
return err_set_di_ty;
}
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const di_file: ?*llvm.DIFile = null;
const line = 0;
const compile_unit_scope = dg.object.di_compile_unit.?.toScope();
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
compile_unit_scope,
di_file,
line,
);
gop.value_ptr.* = fwd_decl;
const err_set_size = err_set_ty.abiSize(target);
const err_set_align = err_set_ty.abiAlignment(target);
const payload_size = payload_ty.abiSize(target);
const payload_align = payload_ty.abiAlignment(target);
var offset: u64 = 0;
offset += err_set_size;
offset = std.mem.alignForwardGeneric(u64, offset, payload_align);
const payload_offset = offset;
const fields: [2]*llvm.DIType = .{
dib.createMemberType(
fwd_decl.toScope(),
"tag",
di_file,
line,
err_set_size * 8, // size in bits
err_set_align * 8, // align in bits
0, // offset in bits
0, // flags
try dg.lowerDebugType(err_set_ty),
),
dib.createMemberType(
fwd_decl.toScope(),
"value",
di_file,
line,
payload_size * 8, // size in bits
payload_align * 8, // align in bits
payload_offset * 8, // offset in bits
0, // flags
try dg.lowerDebugType(payload_ty),
),
};
const replacement_di_ty = dib.createStructType(
compile_unit_scope,
name.ptr,
di_file,
line,
ty.abiSize(target) * 8, // size in bits
ty.abiAlignment(target) * 8, // align in bits
0, // flags
null, // derived from
&fields,
fields.len,
0, // run time lang
null, // vtable holder
"", // unique id
);
dib.replaceTemporary(fwd_decl, replacement_di_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, replacement_di_ty);
return replacement_di_ty;
},
.ErrorSet => {
// TODO make this a proper enum with all the error codes in it.
// will need to consider how to take incremental compilation into account.
gop.value_ptr.* = dib.createBasicType("anyerror", 16, DW.ATE.unsigned);
return gop.value_ptr.*;
},
.Struct => {
const compile_unit_scope = dg.object.di_compile_unit.?.toScope();
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
compile_unit_scope,
null, // file
0, // line
);
gop.value_ptr.* = fwd_decl;
if (ty.isTupleOrAnonStruct()) {
const tuple = ty.tupleFields();
var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{};
defer di_fields.deinit(gpa);
try di_fields.ensureUnusedCapacity(gpa, tuple.types.len);
comptime assert(struct_layout_version == 2);
var offset: u64 = 0;
for (tuple.types) |field_ty, i| {
const field_val = tuple.values[i];
if (field_val.tag() != .unreachable_value) continue;
const field_size = field_ty.abiSize(target);
const field_align = field_ty.abiAlignment(target);
const field_offset = std.mem.alignForwardGeneric(u64, offset, field_align);
offset = field_offset + field_size;
const field_name = if (ty.castTag(.anon_struct)) |payload|
try gpa.dupeZ(u8, payload.data.names[i])
else
try std.fmt.allocPrintZ(gpa, "{d}", .{i});
defer gpa.free(field_name);
try di_fields.append(gpa, dib.createMemberType(
fwd_decl.toScope(),
field_name,
null, // file
0, // line
field_size * 8, // size in bits
field_align * 8, // align in bits
field_offset * 8, // offset in bits
0, // flags
try dg.lowerDebugType(field_ty),
));
}
const replacement_di_ty = dib.createStructType(
compile_unit_scope,
name.ptr,
null, // file
0, // line
ty.abiSize(target) * 8, // size in bits
ty.abiAlignment(target) * 8, // align in bits
0, // flags
null, // derived from
di_fields.items.ptr,
@intCast(c_int, di_fields.items.len),
0, // run time lang
null, // vtable holder
"", // unique id
);
dib.replaceTemporary(fwd_decl, replacement_di_ty);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, replacement_di_ty);
return replacement_di_ty;
}
const TODO_implement_this = true; // TODO
if (TODO_implement_this or !ty.hasRuntimeBitsIgnoreComptime()) {
const owner_decl = ty.getOwnerDecl();
const struct_di_ty = try dg.makeEmptyNamespaceDIType(owner_decl);
dib.replaceTemporary(fwd_decl, struct_di_ty);
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
// means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, struct_di_ty);
return struct_di_ty;
}
@panic("TODO debug info type for struct");
//const struct_obj = ty.castTag(.@"struct").?.data;
//if (struct_obj.layout == .Packed) {
// var buf: Type.Payload.Bits = undefined;
// const int_ty = struct_obj.packedIntegerType(target, &buf);
// const int_llvm_ty = try dg.llvmType(int_ty);
// gop.value_ptr.* = int_llvm_ty;
// return int_llvm_ty;
//}
//const name = try struct_obj.getFullyQualifiedName(gpa);
//defer gpa.free(name);
//const llvm_struct_ty = dg.context.structCreateNamed(name);
//gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
//assert(struct_obj.haveFieldTypes());
//var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{};
//defer llvm_field_types.deinit(gpa);
//try llvm_field_types.ensureUnusedCapacity(gpa, struct_obj.fields.count());
//comptime assert(struct_layout_version == 2);
//var offset: u64 = 0;
//var big_align: u32 = 0;
//for (struct_obj.fields.values()) |field| {
// if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue;
// const field_align = field.normalAlignment(target);
// big_align = @maximum(big_align, field_align);
// const prev_offset = offset;
// offset = std.mem.alignForwardGeneric(u64, offset, field_align);
// const padding_len = offset - prev_offset;
// if (padding_len > 0) {
// const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len));
// try llvm_field_types.append(gpa, llvm_array_ty);
// }
// const field_llvm_ty = try dg.llvmType(field.ty);
// try llvm_field_types.append(gpa, field_llvm_ty);
// offset += field.ty.abiSize(target);
//}
//{
// const prev_offset = offset;
// offset = std.mem.alignForwardGeneric(u64, offset, big_align);
// const padding_len = offset - prev_offset;
// if (padding_len > 0) {
// const llvm_array_ty = dg.context.intType(8).arrayType(@intCast(c_uint, padding_len));
// try llvm_field_types.append(gpa, llvm_array_ty);
// }
//}
//llvm_struct_ty.structSetBody(
// llvm_field_types.items.ptr,
// @intCast(c_uint, llvm_field_types.items.len),
// .False,
//);
//return llvm_struct_ty;
},
.Union => {
const owner_decl = ty.getOwnerDecl();
const name = try ty.nameAlloc(gpa);
defer gpa.free(name);
const fwd_decl = dib.createReplaceableCompositeType(
DW.TAG.structure_type,
name.ptr,
dg.object.di_compile_unit.?.toScope(),
null, // file
0, // line
);
gop.value_ptr.* = fwd_decl;
const TODO_implement_this = true; // TODO
if (TODO_implement_this or !ty.hasRuntimeBitsIgnoreComptime()) {
const union_di_ty = try dg.makeEmptyNamespaceDIType(owner_decl);
dib.replaceTemporary(fwd_decl, union_di_ty);
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
// means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, union_di_ty);
return union_di_ty;
}
@panic("TODO debug info type for union");
//const gop = try dg.object.type_map.getOrPut(gpa, ty);
//if (gop.found_existing) return gop.value_ptr.*;
//// The Type memory is ephemeral; since we want to store a longer-lived
//// reference, we need to copy it here.
//gop.key_ptr.* = try ty.copy(dg.object.type_map_arena.allocator());
//const layout = ty.unionGetLayout(target);
//const union_obj = ty.cast(Type.Payload.Union).?.data;
//if (layout.payload_size == 0) {
// const enum_tag_llvm_ty = try dg.llvmType(union_obj.tag_ty);
// gop.value_ptr.* = enum_tag_llvm_ty;
// return enum_tag_llvm_ty;
//}
//const name = try union_obj.getFullyQualifiedName(gpa);
//defer gpa.free(name);
//const llvm_union_ty = dg.context.structCreateNamed(name);
//gop.value_ptr.* = llvm_union_ty; // must be done before any recursive calls
//const aligned_field = union_obj.fields.values()[layout.most_aligned_field];
//const llvm_aligned_field_ty = try dg.llvmType(aligned_field.ty);
//const llvm_payload_ty = ty: {
// if (layout.most_aligned_field_size == layout.payload_size) {
// break :ty llvm_aligned_field_ty;
// }
// const padding_len = @intCast(c_uint, layout.payload_size - layout.most_aligned_field_size);
// const fields: [2]*const llvm.Type = .{
// llvm_aligned_field_ty,
// dg.context.intType(8).arrayType(padding_len),
// };
// break :ty dg.context.structType(&fields, fields.len, .True);
//};
//if (layout.tag_size == 0) {
// var llvm_fields: [1]*const llvm.Type = .{llvm_payload_ty};
// llvm_union_ty.structSetBody(&llvm_fields, llvm_fields.len, .False);
// return llvm_union_ty;
//}
//const enum_tag_llvm_ty = try dg.llvmType(union_obj.tag_ty);
//// Put the tag before or after the payload depending on which one's
//// alignment is greater.
//var llvm_fields: [3]*const llvm.Type = undefined;
//var llvm_fields_len: c_uint = 2;
//if (layout.tag_align >= layout.payload_align) {
// llvm_fields = .{ enum_tag_llvm_ty, llvm_payload_ty, undefined };
//} else {
// llvm_fields = .{ llvm_payload_ty, enum_tag_llvm_ty, undefined };
//}
//// Insert padding to make the LLVM struct ABI size match the Zig union ABI size.
//if (layout.padding != 0) {
// llvm_fields[2] = dg.context.intType(8).arrayType(layout.padding);
// llvm_fields_len = 3;
//}
//llvm_union_ty.structSetBody(&llvm_fields, llvm_fields_len, .False);
//return llvm_union_ty;
},
.Fn => {
const fn_info = ty.fnInfo();
const sret = firstParamSRet(fn_info, target);
var param_di_types = std.ArrayList(*llvm.DIType).init(dg.gpa);
defer param_di_types.deinit();
// Return type goes first.
const di_ret_ty = if (sret or !fn_info.return_type.hasRuntimeBitsIgnoreComptime())
Type.void
else
fn_info.return_type;
try param_di_types.append(try dg.lowerDebugType(di_ret_ty));
if (sret) {
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = fn_info.return_type,
};
const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
try param_di_types.append(try dg.lowerDebugType(ptr_ty));
}
for (fn_info.param_types) |param_ty| {
if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue;
if (isByRef(param_ty)) {
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = param_ty,
};
const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
try param_di_types.append(try dg.lowerDebugType(ptr_ty));
} else {
try param_di_types.append(try dg.lowerDebugType(param_ty));
}
}
const fn_di_ty = dib.createSubroutineType(
param_di_types.items.ptr,
@intCast(c_int, param_di_types.items.len),
0,
);
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
try dg.object.di_type_map.put(gpa, ty, fn_di_ty);
return fn_di_ty;
},
.ComptimeInt => unreachable,
.ComptimeFloat => unreachable,
.Type => unreachable,
.Undefined => unreachable,
.Null => unreachable,
.EnumLiteral => unreachable,
.BoundFn => @panic("TODO remove BoundFn from the language"),
.Frame => @panic("TODO implement lowerDebugType for Frame types"),
.AnyFrame => @panic("TODO implement lowerDebugType for AnyFrame types"),
}
}
fn namespaceToDebugScope(dg: *DeclGen, namespace: *const Module.Namespace) !*llvm.DIScope {
if (namespace.parent == null) {
const di_file = try dg.object.getDIFile(dg.gpa, namespace.file_scope);
return di_file.toScope();
}
const di_type = try dg.lowerDebugType(namespace.ty);
return di_type.toScope();
}
/// This is to be used instead of void for debug info types, to avoid tripping
/// Assertion `!isa<DIType>(Scope) && "shouldn't make a namespace scope for a type"'
/// when targeting CodeView (Windows).
fn makeEmptyNamespaceDIType(dg: *DeclGen, decl: *const Module.Decl) !*llvm.DIType {
const fields: [0]*llvm.DIType = .{};
return dg.object.di_builder.?.createStructType(
try dg.namespaceToDebugScope(decl.src_namespace),
decl.name, // TODO use fully qualified name
try dg.object.getDIFile(dg.gpa, decl.src_namespace.file_scope),
decl.src_line + 1,
0, // size in bits
0, // align in bits
0, // flags
null, // derived from
undefined, // TODO should be able to pass &fields,
fields.len,
0, // run time lang
null, // vtable holder
"", // unique id
);
}
const ParentPtr = struct {
ty: Type,
llvm_ptr: *const llvm.Value,
@@ -4085,7 +4193,7 @@ pub const FuncGen = struct {
name.ptr,
self.di_file.?,
self.prev_dbg_line,
try self.dg.lowerDebugType(ptr_ty.childType()),
try self.dg.object.lowerDebugType(ptr_ty.childType(), .full),
true, // always preserve
0, // flags
);
@@ -4107,7 +4215,7 @@ pub const FuncGen = struct {
name.ptr,
self.di_file.?,
self.prev_dbg_line,
try self.dg.lowerDebugType(operand_ty),
try self.dg.object.lowerDebugType(operand_ty, .full),
true, // always preserve
0, // flags
);
@@ -5359,7 +5467,7 @@ pub const FuncGen = struct {
func.getParamName(src_index).ptr, // TODO test 0 bit args
self.di_file.?,
lbrace_line,
try self.dg.lowerDebugType(inst_ty),
try self.dg.object.lowerDebugType(inst_ty, .full),
true, // always preserve
0, // flags
self.arg_index, // includes +1 because 0 is return type
@@ -7107,3 +7215,30 @@ fn backendSupportsF80(target: std.Target) bool {
/// We can do this because for all types, Zig ABI alignment >= LLVM ABI
/// alignment.
const struct_layout_version = 2;
/// We use the least significant bit of the pointer address to tell us
/// whether the type is fully resolved. Types that are only fwd declared
/// have the LSB flipped to a 1.
const AnnotatedDITypePtr = enum(usize) {
_,
fn initFwd(di_type: *llvm.DIType) AnnotatedDITypePtr {
const addr = @ptrToInt(di_type);
assert(@truncate(u1, addr) == 0);
return @intToEnum(AnnotatedDITypePtr, addr | 1);
}
fn initFull(di_type: *llvm.DIType) AnnotatedDITypePtr {
const addr = @ptrToInt(di_type);
return @intToEnum(AnnotatedDITypePtr, addr);
}
fn toDIType(self: AnnotatedDITypePtr) *llvm.DIType {
const fixed_addr = @enumToInt(self) & ~@as(usize, 1);
return @intToPtr(*llvm.DIType, fixed_addr);
}
fn isFwdOnly(self: AnnotatedDITypePtr) bool {
return @truncate(u1, @enumToInt(self)) != 0;
}
};